Imported Upstream version 1.3.0
Dain Nilsson
8 years ago
0 | 2015-10-01 Dain Nilsson <dain@yubico.com> | |
1 | ||
2 | * NEWS, yubico/yubico_version.py: Updated NEWS and version for | |
3 | release. | |
4 | ||
5 | 2015-10-01 Dain Nilsson <dain@yubico.com> | |
6 | ||
7 | * .travis.yml: Use Travis container based infrastructure. | |
8 | ||
9 | 2015-10-01 Dain Nilsson <dain@yubico.com> | |
10 | ||
11 | * MANIFEST.in: Remove release.py from MANIFEST. | |
12 | ||
13 | 2015-10-01 Dain Nilsson <dain@yubico.com> | |
14 | ||
15 | * .gitmodules, README, release.py, setup.py, vendor/yubicommon, | |
16 | yubico/yubicommon: Use python-yubicommon | |
17 | ||
18 | 2015-09-30 Dain Nilsson <dain@yubico.com> | |
19 | ||
20 | * yubico/yubikey_usb_hid.py: Correctly handle cases where | |
21 | programming counter shouldn't increase. | |
22 | ||
23 | 2015-09-14 Dain Nilsson <dain@yubico.com> | |
24 | ||
25 | * yubico/yubikey_neo_usb_hid.py: Add missing import. | |
26 | ||
27 | 2015-09-10 Dain Nilsson <dain@yubico.com> | |
28 | ||
29 | * yubico/yubikey_neo_usb_hid.py: Fix write commands for YubiKey NEO. | |
30 | ||
31 | 2015-09-09 Dain Nilsson <dain@yubico.com> | |
32 | ||
33 | * yubico/yubikey_defs.py: Don't use set literals (not available in | |
34 | python <2.7) | |
35 | ||
36 | 2015-09-09 Dain Nilsson <dain@yubico.com> | |
37 | ||
38 | * yubico/yubikey_4_usb_hid.py, yubico/yubikey_config.py, | |
39 | yubico/yubikey_defs.py, yubico/yubikey_usb_hid.py: Moved some stuff | |
40 | around. | |
41 | ||
42 | 2015-09-09 Dain Nilsson <dain@yubico.com> | |
43 | ||
44 | * yubico/yubikey_config_util.py: Made flags work with YK4. | |
45 | ||
46 | 2015-09-09 Dain Nilsson <dain@yubico.com> | |
47 | ||
48 | * yubico/ykdef.py, yubico/yubikey_4_usb_hid.py, | |
49 | yubico/yubikey_config.py, yubico/yubikey_defs.py, | |
50 | yubico/yubikey_frame.py, yubico/yubikey_neo_usb_hid.py, | |
51 | yubico/yubikey_usb_hid.py: Combined ykdef with yubikey_defs. | |
52 | ||
53 | 2015-09-09 Dain Nilsson <dain@yubico.com> | |
54 | ||
55 | * yubico/yubikey.py, yubico/yubikey_4_usb_hid.py: Added support for | |
56 | YK4-based devices. | |
57 | ||
58 | 2015-09-09 Dain Nilsson <dain@yubico.com> | |
59 | ||
60 | * yubico/ykdef.py, yubico/yubico_util.py, yubico/yubikey_base.py, | |
61 | yubico/yubikey_neo_usb_hid.py, yubico/yubikey_usb_hid.py: Moved | |
62 | constants to yubico/ykdef.py | |
63 | ||
64 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
65 | ||
66 | * README: Updated README. | |
67 | ||
68 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
69 | ||
70 | * NEWS: Updated NEWS. | |
71 | ||
72 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
73 | ||
74 | * yubico/yubikey_neo_usb_hid.py: Added scancode programming for NEO. | |
75 | ||
76 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
77 | ||
78 | * yubico/yubikey_config.py: Added zapping of slots. | |
79 | ||
80 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
81 | ||
82 | * yubico/yubikey_base.py, yubico/yubikey_neo_usb_hid.py: Added | |
83 | have_scanmap and have_usb_mode. | |
84 | ||
85 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
86 | ||
87 | * yubico/yubikey_base.py, yubico/yubikey_neo_usb_hid.py: Added | |
88 | support for programming NDEF slot 2 (fixes #23) | |
89 | ||
90 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
91 | ||
92 | * yubico/yubikey_usb_hid.py: Sleep before read (closes #18) | |
93 | ||
94 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
95 | ||
96 | * yubico/yubico_exception.py, yubico/yubikey_base.py, | |
97 | yubico/yubikey_config.py, yubico/yubikey_config_util.py, | |
98 | yubico/yubikey_neo_usb_hid.py, yubico/yubikey_usb_hid.py: Switch to | |
99 | new-style classes. | |
100 | ||
101 | 2015-09-08 Dain Nilsson <dain@yubico.com> | |
102 | ||
103 | * yubico/yubico_version.py, yubico/yubikey.py, | |
104 | yubico/yubikey_base.py, yubico/yubikey_neo_usb_hid.py, | |
105 | yubico/yubikey_usb_hid.py: Improved support for YubiKey NEO. | |
106 | ||
107 | 2015-08-26 Dain Nilsson <dain@yubico.com> | |
108 | ||
109 | * .travis.yml: Fixed test command in travis. | |
110 | ||
111 | 2015-08-26 Dain Nilsson <dain@yubico.com> | |
112 | ||
113 | * .travis.yml, setup.py, test/__init__.py, test/soft/__init__.py, | |
114 | test/soft/test_yubico.py, test/soft/test_yubikey_config.py, | |
115 | test/soft/test_yubikey_frame.py, test/test_yubico.py, | |
116 | test/test_yubikey_config.py, test/test_yubikey_frame.py, | |
117 | test/test_yubikey_usb_hid.py, test/usb/__init__.py, | |
118 | test/usb/test_yubikey_usb_hid.py: Separated tests that require a | |
119 | YubiKey from those that do not. | |
120 | ||
121 | 2015-07-20 Henrik Stråth <minisu@users.noreply.github.com> | |
122 | ||
123 | * : Merge pull request #25 from Yubico/travis Added Travis CI support | |
124 | ||
125 | 2015-07-20 Henrik Stråth <minisu@users.noreply.github.com> | |
126 | ||
127 | * : Merge pull request #24 from encukou/py3 Add Python3 compatibility to util and examples | |
128 | ||
129 | 2015-07-03 Dain Nilsson <dainzor@gmail.com> | |
130 | ||
131 | * : Merge pull request #20 from encukou/py3 Add support for Python 3 | |
132 | ||
133 | 2015-07-01 Petr Viktorin <pviktori@redhat.com> | |
134 | ||
135 | * test/test_yubico.py, test/test_yubikey_config.py, | |
136 | test/test_yubikey_frame.py, test/test_yubikey_usb_hid.py, | |
137 | yubico/yubico_util.py, yubico/yubikey_config.py, | |
138 | yubico/yubikey_frame.py, yubico/yubikey_neo_usb_hid.py, | |
139 | yubico/yubikey_usb_hid.py: Distinguish text strings and bytestrings | |
140 | for Python 3 compatibility All (potentially binary) data is bytestrings; text (including e.g. | |
141 | hexdumps and exception messages) is text strings. Note that in Python 2, there's no difference between text (str, | |
142 | '...') and bytestrings (bytes, b'...'). | |
143 | ||
144 | 2015-07-01 Petr Viktorin <pviktori@redhat.com> | |
145 | ||
146 | * test/test_yubikey_config.py, test/test_yubikey_frame.py, | |
147 | test/test_yubikey_usb_hid.py, yubico/__init__.py, | |
148 | yubico/yubico_exception.py, yubico/yubico_util.py, | |
149 | yubico/yubikey.py, yubico/yubikey_base.py, | |
150 | yubico/yubikey_config.py, yubico/yubikey_defs.py, | |
151 | yubico/yubikey_frame.py, yubico/yubikey_neo_usb_hid.py, | |
152 | yubico/yubikey_usb_hid.py: Use Python 3-compatible syntax - Use parentheses with print (python-yubico only ever gives one argument to print) - Use 'as' syntax when catching expressions (PEP; Python 2.6+) - Use explicit relative imports (PEP 328; Python 2.4+) - Use range instead of xrange (this is in a debugging tool, the memory overhead is negligible, and the entire result is iterated over) | |
153 | ||
154 | 2015-07-01 Petr Viktorin <pviktori@redhat.com> | |
155 | ||
156 | * yubico/yubikey.py, yubico/yubikey_base.py, | |
157 | yubico/yubikey_config.py, yubico/yubikey_usb_hid.py: Breaking | |
158 | circular imports: Move base classes to yubico_base.py This breaks the yubikey <-> yubikey_usb_hid circular import | |
159 | ||
160 | 2015-07-01 Petr Viktorin <pviktori@redhat.com> | |
161 | ||
162 | * yubico/yubikey_config.py, yubico/yubikey_defs.py, | |
163 | yubico/yubikey_frame.py, yubico/yubikey_usb_hid.py: Breaking | |
164 | circular imports: Move command definitions to yubikey_defs This breaks the yubikey_config <-> yubikey_frame circular import. | |
165 | ||
166 | 2015-07-01 Petr Viktorin <pviktori@redhat.com> | |
167 | ||
168 | * setup.py, yubico/__init__.py, yubico/yubico_exception.py, | |
169 | yubico/yubico_util.py, yubico/yubico_version.py, yubico/yubikey.py, | |
170 | yubico/yubikey_config.py, yubico/yubikey_defs.py, | |
171 | yubico/yubikey_frame.py, yubico/yubikey_neo_usb_hid.py, | |
172 | yubico/yubikey_usb_hid.py: Breaking circular imports: Move | |
173 | __version__ to a dedicated module | |
174 | ||
0 | 175 | 2015-03-23 Dain Nilsson <dain@yubico.com> |
1 | 176 | |
2 | 177 | * NEWS, yubico/__init__.py: Updated version. |
0 | * Version 1.3.0 (released 2015-10-01) | |
1 | ** Added Python 3 compatibility. | |
2 | ** Added the ability to zap a slot. | |
3 | ** Added support for YubiKey NEO. | |
4 | ** Added support for YubiKey 4. | |
5 | ||
0 | 6 | * Version 1.2.3 (released 2015-03-23) |
1 | 7 | ** Added PIDs for newer devices. |
2 | 8 | ** Failure to call setConfiguration is now ignored. |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: python-yubico |
2 | Version: 1.2.3 | |
2 | Version: 1.3.0 | |
3 | 3 | Summary: Python code for talking to Yubico's YubiKeys |
4 | 4 | Home-page: https://github.com/Yubico/python-yubico |
5 | 5 | Author: Yubico Open Source Maintainers |
1 | 1 | Python package for talking to YubiKeys. |
2 | 2 | |
3 | 3 | === Introduction |
4 | The YubiKey is a hardware token for authentication. The main | |
5 | mode of the YubiKey is entering a one time password (or a strong | |
6 | static password) by acting as a USB HID device, but there are | |
7 | things one can do with bi-directional communication: | |
4 | The YubiKey is a hardware token for authentication. The main mode of the | |
5 | YubiKey is entering a one time password (or a strong static password) by acting | |
6 | as a USB HID device, but there are things one can do with bi-directional | |
7 | communication: | |
8 | 8 | |
9 | 1. Configuration. The yubikey_config class should be a feature- | |
10 | wise complete implementation of everything that can be | |
11 | configured on YubiKeys version 1.3 to 2.2 (besides deprecated | |
12 | functions in YubiKey 1.x). | |
9 | 1. Configuration. The yubikey_config class should be a feature-wise complete | |
10 | implementation of everything that can be configured on YubiKeys version 1.3 | |
11 | to 4.x (besides deprecated functions in YubiKey 1.x). | |
13 | 12 | See `examples/configure_nist_test_key` for an example. |
14 | 13 | |
15 | 2. Challenge-response. YubiKey 2.2 supports HMAC-SHA1 or Yubico | |
14 | 2. Challenge-response. YubiKey 2.2 and later supports HMAC-SHA1 or Yubico | |
16 | 15 | challenge-response operations. |
17 | 16 | See `examples/nist_challenge_response` for an example. |
18 | 17 | |
40 | 39 | === Installation |
41 | 40 | |
42 | 41 | ==== Using the Ubuntu/Debian package manager |
43 | If you use a recent Ubuntu release, you should be able to install | |
44 | python-yubico with these commands : | |
42 | If you use a recent Ubuntu release, you should be able to install python-yubico | |
43 | with these commands : | |
45 | 44 | |
46 | 45 | $ sudo add-apt-repository ppa:yubico/stable |
47 | 46 | $ sudo apt-get update |
59 | 58 | $ cd python-yubico-$ver |
60 | 59 | $ python setup.py install |
61 | 60 | |
62 | This requires the `python-setuptools` package. You will also need | |
63 | http://walac.github.io/pyusb[PyUSB], called python-usb in | |
64 | Debian/Ubuntu. `pyusb` is available on PyPI and may be installed | |
65 | with pip: `pip install --pre pyusb` The --pre command-line option | |
66 | indicates that pre-releases of `pyusb` may also be searched (only | |
67 | pre-releases of `pyusb` are available on PyPI, and pip skips | |
68 | pre-releases by default). Note that while both the 0.4 branch and | |
69 | the 1.0 branch are supported, the older 0.4 branch doesn't support | |
70 | re-attaching the kernel device driver on close, which will leave | |
71 | the YubiKey in a state where it is unable to output OTPs until it | |
72 | has been unplugged and plugged back in again. | |
61 | This requires the `python-setuptools` package. You will also need | |
62 | http://walac.github.io/pyusb[PyUSB], called python-usb in Debian/Ubuntu. | |
63 | `pyusb` is available on PyPI and may be installed with pip: `pip install --pre | |
64 | pyusb` The --pre command-line option indicates that pre-releases of `pyusb` | |
65 | may also be searched (only pre-releases of `pyusb` are available on PyPI, and | |
66 | pip skips pre-releases by default). Note that while both the 0.4 branch and the | |
67 | 1.0 branch are supported, the older 0.4 branch doesn't support re-attaching the | |
68 | kernel device driver on close, which will leave the YubiKey in a state where it | |
69 | is unable to output OTPs until it has been unplugged and plugged back in again. | |
70 | ||
71 | ==== Check out the code | |
72 | Run these commands to check out the source code: | |
73 | ||
74 | git clone https://github.com/Yubico/yubico-python.git | |
75 | cd yubico-python | |
76 | git submodules update --init | |
73 | 77 | |
74 | 78 | ==== On Windows |
75 | If you use Windows, you will require a PyUSB backend. Python-yubico | |
76 | has been tested with http://libusbx.org[libusbx] and confirmed working, | |
77 | without the need for replacing the device driver. | |
79 | If you use Windows, you will require a PyUSB backend. Python-yubico has been | |
80 | tested with http://libusbx.org[libusbx] and confirmed working, without the need | |
81 | for replacing the device driver. | |
78 | 82 | |
79 | 83 | === License |
80 | 84 | Copyright (c) Yubico AB. |
4 | 4 | |
5 | 5 | import sys |
6 | 6 | import struct |
7 | import urllib | |
7 | ||
8 | import six | |
8 | 9 | |
9 | 10 | import yubico |
10 | 11 | import yubico.yubikey_neo_usb_hid |
15 | 16 | |
16 | 17 | url = sys.argv[1] |
17 | 18 | |
19 | if sys.version_info >= (3, 0): | |
20 | url = url.encode('utf-8') | |
21 | ||
18 | 22 | try: |
19 | 23 | YK = yubico.yubikey_neo_usb_hid.YubiKeyNEO_USBHID(debug=True) |
20 | print "Version : %s " % YK.version() | |
24 | print("Version : %s " % YK.version()) | |
21 | 25 | |
22 | 26 | ndef = yubico.yubikey_neo_usb_hid.YubiKeyNEO_NDEF(data = url) |
23 | 27 | |
24 | user_input = raw_input('Write configuration to YubiKey? [y/N] : ') | |
28 | user_input = six.moves.input('Write configuration to YubiKey? [y/N] : ') | |
25 | 29 | if user_input in ('y', 'ye', 'yes'): |
26 | 30 | YK.write_ndef(ndef) |
27 | print "\nSuccess!" | |
31 | print("\nSuccess!") | |
28 | 32 | else: |
29 | print "\nAborted" | |
33 | print("\nAborted") | |
30 | 34 | except yubico.yubico_exception.YubicoError as inst: |
31 | print "ERROR: %s" % inst.reason | |
35 | print("ERROR: %s" % inst.reason) | |
32 | 36 | sys.exit(1) |
6 | 6 | import sys |
7 | 7 | import struct |
8 | 8 | import yubico |
9 | import six | |
9 | 10 | |
10 | 11 | slot=2 |
11 | 12 | |
12 | 13 | try: |
13 | 14 | YK = yubico.find_yubikey(debug=True) |
14 | print "Version : %s " % YK.version() | |
15 | print("Version : %s " % YK.version()) | |
15 | 16 | |
16 | 17 | Cfg = YK.init_config() |
17 | key='h:303132333435363738393a3b3c3d3e3f40414243' | |
18 | key = b'h:303132333435363738393a3b3c3d3e3f40414243' | |
18 | 19 | Cfg.mode_challenge_response(key, type='HMAC', variable=True) |
19 | 20 | Cfg.extended_flag('SERIAL_API_VISIBLE', True) |
20 | 21 | |
21 | user_input = raw_input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) | |
22 | user_input = six.moves.input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) | |
22 | 23 | if user_input in ('y', 'ye', 'yes'): |
23 | 24 | YK.write_config(Cfg, slot=slot) |
24 | print "\nSuccess!" | |
25 | print("\nSuccess!") | |
25 | 26 | else: |
26 | print "\nAborted" | |
27 | print("\nAborted") | |
27 | 28 | except yubico.yubico_exception.YubicoError as inst: |
28 | print "ERROR: %s" % inst.reason | |
29 | print("ERROR: %s" % inst.reason) | |
29 | 30 | sys.exit(1) |
7 | 7 | import yubico |
8 | 8 | |
9 | 9 | expected = \ |
10 | '\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82' + \ | |
11 | '\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24' | |
10 | b'\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82' + \ | |
11 | b'\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24' | |
12 | 12 | |
13 | 13 | # turn on YubiKey debug if -v is given as an argument |
14 | 14 | debug = False |
18 | 18 | # Look for and initialize the YubiKey |
19 | 19 | try: |
20 | 20 | YK = yubico.find_yubikey(debug=debug) |
21 | print "Version : %s " % YK.version() | |
22 | print "Serial : %i" % YK.serial() | |
23 | print "" | |
21 | print("Version : %s " % YK.version()) | |
22 | print("Serial : %i" % YK.serial()) | |
23 | print("") | |
24 | 24 | |
25 | 25 | # Do challenge-response |
26 | secret = 'Sample #2'.ljust(64, chr(0x0)) | |
27 | print "Sending challenge : %s\n" % repr(secret) | |
26 | secret = b'Sample #2'.ljust(64, b'\0') | |
27 | print("Sending challenge : %s\n" % repr(secret)) | |
28 | 28 | |
29 | 29 | response = YK.challenge_response(secret, slot=2) |
30 | 30 | except yubico.yubico_exception.YubicoError as inst: |
31 | print "ERROR: %s" % inst.reason | |
31 | print("ERROR: %s" % inst.reason) | |
32 | 32 | sys.exit(1) |
33 | 33 | |
34 | print "Response :\n%s\n" % yubico.yubico_util.hexdump(response) | |
34 | print("Response :\n%s\n" % yubico.yubico_util.hexdump(response)) | |
35 | ||
36 | # Workaround for http://bugs.python.org/issue24596 | |
37 | del YK | |
35 | 38 | |
36 | 39 | # Check if the response matched the expected one |
37 | 40 | if response == expected: |
38 | print "OK! Response matches the NIST PUB 198 A.2 expected response." | |
41 | print("OK! Response matches the NIST PUB 198 A.2 expected response.") | |
39 | 42 | sys.exit(0) |
40 | 43 | else: |
41 | print "ERROR! Response does NOT match the NIST PUB 198 A.2 expected response." | |
44 | print("ERROR! Response does NOT match the NIST PUB 198 A.2 expected response.") | |
42 | 45 | sys.exit(1) |
21 | 21 | import hmac |
22 | 22 | import argparse |
23 | 23 | import hashlib |
24 | import binascii | |
24 | 25 | |
25 | 26 | import yubico |
27 | import six | |
26 | 28 | |
27 | 29 | from Crypto.Cipher import AES |
28 | 30 | |
69 | 71 | |
70 | 72 | def init_demo(args): |
71 | 73 | """ Initializes the demo by asking a few questions and creating a new stat file. """ |
72 | hmac_key = raw_input("Enter HMAC-SHA1 key as 40 chars of hex (or press enter for random key) : ") | |
74 | hmac_key = six.moves.input("Enter HMAC-SHA1 key as 40 chars of hex (or press enter for random key) : ") | |
73 | 75 | if hmac_key: |
74 | 76 | try: |
75 | hmac_key = hmac_key.decode('hex') | |
77 | hmac_key = binascii.unhexlify(hmac_key) | |
76 | 78 | except: |
77 | 79 | sys.stderr.write("Could not decode HMAC-SHA1 key. Please enter 40 hex-chars.\n") |
78 | 80 | sys.exit(1) |
82 | 84 | sys.stderr.write("Decoded HMAC-SHA1 key is %i bytes, expected 20.\n" %( len(hmac_key))) |
83 | 85 | sys.exit(1) |
84 | 86 | |
85 | print "To program a YubiKey >= 2.2 for challenge-response with this key, use :" | |
86 | print "" | |
87 | print " $ ykpersonalize -%i -ochal-resp -ochal-hmac -ohmac-lt64 -a %s" % (args.slot, hmac_key.encode('hex')) | |
88 | print "" | |
89 | ||
90 | passphrase = raw_input("Enter the secret passphrase to protect with the rolling challenges : ") | |
87 | print("To program a YubiKey >= 2.2 for challenge-response with this key, use :") | |
88 | print("") | |
89 | print(" $ ykpersonalize -%i -ochal-resp -ochal-hmac -ohmac-lt64 -a %s" % (args.slot, binascii.hexlify(hmac_key).decode('ascii'))) | |
90 | print("") | |
91 | ||
92 | passphrase = six.moves.input("Enter the secret passphrase to protect with the rolling challenges : ") | |
91 | 93 | |
92 | 94 | secret_dict = {"count": 0, |
93 | 95 | "passphrase": passphrase, |
98 | 100 | """ Send a challenge to the YubiKey and use the result to decrypt the state file. """ |
99 | 101 | outer_j = load_state_file(args) |
100 | 102 | challenge = outer_j["challenge"] |
101 | print "Challenge : %s" % (challenge) | |
102 | response = get_yubikey_response(args, outer_j["challenge"].decode('hex')) | |
103 | print("Challenge : %s" % (challenge)) | |
104 | response = get_yubikey_response(args, binascii.unhexlify(outer_j["challenge"])) | |
103 | 105 | if args.debug or args.verbose: |
104 | print "\nGot %i bytes response %s\n" % (len(response), response.encode('hex')) | |
106 | print("\nGot %i bytes response %s\n" % (len(response), binascii.hexlify(response))) | |
105 | 107 | else: |
106 | print "Response : %s" % (response.encode('hex')) | |
108 | print("Response : %s" % binascii.hexlify(response)) | |
107 | 109 | inner_j = decrypt_with_response(args, outer_j["inner"], response) |
108 | 110 | if args.verbose or args.debug: |
109 | print "\nDecrypted 'inner' :\n%s\n" % (inner_j) | |
111 | print("\nDecrypted 'inner' :\n%s\n" % (inner_j)) | |
110 | 112 | |
111 | 113 | secret_dict = {} |
112 | 114 | try: |
113 | secret_dict = json.loads(inner_j) | |
115 | secret_dict = json.loads(inner_j.decode('ascii')) | |
114 | 116 | except ValueError: |
115 | 117 | sys.stderr.write("\nCould not parse decoded data as JSON, you probably did not produce the right response.\n") |
116 | 118 | sys.exit(1) |
117 | 119 | |
118 | 120 | secret_dict["count"] += 1 |
119 | 121 | |
120 | print "\nThe passphrase protected using rolling challenges is :\n" | |
121 | print "\t%s\n\nAccessed %i times.\n" % (secret_dict["passphrase"], secret_dict["count"]) | |
122 | roll_next_challenge(args, secret_dict["hmac_key"].decode('hex'), secret_dict) | |
122 | print("\nThe passphrase protected using rolling challenges is :\n") | |
123 | print("\t%s\n\nAccessed %i times.\n" % (secret_dict["passphrase"], secret_dict["count"])) | |
124 | roll_next_challenge(args, binascii.unhexlify(secret_dict["hmac_key"]), secret_dict) | |
123 | 125 | |
124 | 126 | def get_yubikey_response(args, challenge): |
125 | 127 | """ |
126 | 128 | Do challenge-response with the YubiKey if one is found. Otherwise prompt user to fake a response. """ |
127 | 129 | try: |
128 | 130 | YK = yubico.find_yubikey(debug = args.debug) |
129 | response = YK.challenge_response(challenge.ljust(64, chr(0x0)), slot = args.slot) | |
131 | response = YK.challenge_response(challenge.ljust(64, b'\0'), slot = args.slot) | |
130 | 132 | return response |
131 | 133 | except yubico.yubico_exception.YubicoError as e: |
132 | print "YubiKey challenge-response failed (%s)" % e.reason | |
133 | print "" | |
134 | response = raw_input("Assuming you do not have a YubiKey. Enter repsonse manually (hex encoded) : ") | |
135 | return response | |
134 | print("YubiKey challenge-response failed (%s)" % e.reason) | |
135 | print("") | |
136 | response = six.moves.input("Assuming you do not have a YubiKey. Enter repsonse manually (hex encoded) : ") | |
137 | return binascii.unhexlify(response) | |
136 | 138 | |
137 | 139 | def roll_next_challenge(args, hmac_key, inner_dict): |
138 | 140 | """ |
139 | 141 | When we have the HMAC-SHA1 key in clear, generate a random challenge and compute the |
140 | 142 | expected response for that challenge. |
141 | """ | |
142 | if len(hmac_key) != 20: | |
143 | hmac_key = hmac_key.decode('hex') | |
143 | ||
144 | hmac_key is a 20-byte bytestring | |
145 | """ | |
146 | if len(hmac_key) != 20 or not isinstance(hmac_key, bytes): | |
147 | hmac_key = binascii.unhexlify(hmac_key) | |
144 | 148 | |
145 | 149 | challenge = os.urandom(args.challenge_length) |
146 | 150 | response = get_response(hmac_key, challenge) |
147 | 151 | |
148 | print "Generated challenge : %s" % (challenge.encode('hex')) | |
149 | print "Expected response : %s (sssh, don't tell anyone)" % (response) | |
150 | print "" | |
152 | print("Generated challenge : %s" % binascii.hexlify(challenge).decode('ascii')) | |
153 | print("Expected response : %s (sssh, don't tell anyone)" % binascii.hexlify(response).decode('ascii')) | |
154 | print("") | |
151 | 155 | if args.debug or args.verbose or args.init: |
152 | print "To manually verify that your YubiKey produces this response, use :" | |
153 | print "" | |
154 | print " $ ykchalresp -%i -x %s" % (args.slot, challenge.encode('hex')) | |
155 | print "" | |
156 | ||
157 | inner_dict["hmac_key"] = hmac_key.encode('hex') | |
156 | print("To manually verify that your YubiKey produces this response, use :") | |
157 | print("") | |
158 | print(" $ ykchalresp -%i -x %s" % (args.slot, binascii.hexlify(challenge).decode('ascii'))) | |
159 | print("") | |
160 | ||
161 | inner_dict["hmac_key"] = binascii.hexlify(hmac_key).decode('ascii') | |
158 | 162 | inner_j = json.dumps(inner_dict, indent = 4) |
159 | 163 | if args.verbose or args.debug: |
160 | print "Inner JSON :\n%s\n" % (inner_j) | |
164 | print("Inner JSON :\n%s\n" % (inner_j)) | |
161 | 165 | inner_ciphertext = encrypt_with_response(args, inner_j, response) |
162 | outer_dict = {"challenge": challenge.encode('hex'), | |
163 | "inner": inner_ciphertext, | |
166 | outer_dict = {"challenge": binascii.hexlify(challenge).decode('ascii'), | |
167 | "inner": inner_ciphertext.decode('ascii'), | |
164 | 168 | } |
165 | 169 | outer_j = json.dumps(outer_dict, indent = 4) |
166 | 170 | if args.verbose or args.debug: |
167 | print "\nOuter JSON :\n%s\n" % (outer_j) | |
168 | ||
169 | print "Saving 'outer' JSON to file '%s'" % (args.filename) | |
171 | print("\nOuter JSON :\n%s\n" % (outer_j)) | |
172 | ||
173 | print("Saving 'outer' JSON to file '%s'" % (args.filename)) | |
170 | 174 | write_state_file(args, outer_j) |
171 | 175 | |
172 | 176 | def get_response(hmac_key, challenge): |
173 | """ Compute the expected response for `challenge'. """ | |
177 | """ Compute the expected response for `challenge', as hexadecimal string """ | |
178 | print(binascii.hexlify(hmac_key), binascii.hexlify(challenge), hashlib.sha1) | |
174 | 179 | h = hmac.new(hmac_key, challenge, hashlib.sha1) |
175 | return h.hexdigest() | |
180 | return h.digest() | |
176 | 181 | |
177 | 182 | def encrypt_with_response(args, data, key): |
178 | 183 | """ |
190 | 195 | data += ' ' * (16 - pad) |
191 | 196 | |
192 | 197 | # need to pad key as well |
193 | aes_key = key.decode('hex') | |
194 | aes_key += chr(0x0) * (32 - len(aes_key)) | |
198 | aes_key = key | |
199 | aes_key += b'\0' * (32 - len(aes_key)) | |
195 | 200 | if args.debug: |
196 | print ("AES-CBC encrypting 'inner' with key (%i bytes) : %s" % (len(aes_key), aes_key.encode('hex'))) | |
197 | ||
198 | obj = AES.new(aes_key, AES.MODE_CBC) | |
201 | print(("AES-CBC encrypting 'inner' with key (%i bytes) : %s" % (len(aes_key), binascii.hexlify(aes_key)))) | |
202 | ||
203 | obj = AES.new(aes_key, AES.MODE_CBC, b'\0' * 16) | |
199 | 204 | ciphertext = obj.encrypt(data) |
200 | return ciphertext.encode('hex') | |
205 | return binascii.hexlify(ciphertext) | |
201 | 206 | |
202 | 207 | def decrypt_with_response(args, data, key): |
203 | 208 | """ |
205 | 210 | """ |
206 | 211 | aes_key = key |
207 | 212 | try: |
208 | aes_key = key.decode('hex') | |
209 | except TypeError: | |
213 | aes_key = binascii.unhexlify(key) | |
214 | except (TypeError, binascii.Error): | |
210 | 215 | # was not hex encoded |
211 | 216 | pass |
212 | 217 | # need to pad key |
213 | aes_key += chr(0x0) * (32 - len(aes_key)) | |
218 | aes_key += b'\0' * (32 - len(aes_key)) | |
214 | 219 | if args.debug: |
215 | print ("AES-CBC decrypting 'inner' using key (%i bytes) : %s" % (len(aes_key), aes_key.encode('hex'))) | |
216 | ||
217 | obj = AES.new(aes_key, AES.MODE_CBC) | |
218 | plaintext = obj.decrypt(data.decode('hex')) | |
220 | print(("AES-CBC decrypting 'inner' using key (%i bytes) : %s" % (len(aes_key), binascii.hexlify(aes_key)))) | |
221 | ||
222 | obj = AES.new(aes_key, AES.MODE_CBC, b'\0' * 16) | |
223 | plaintext = obj.decrypt(binascii.unhexlify(data)) | |
219 | 224 | return plaintext |
220 | 225 | |
221 | 226 | def write_state_file(args, data): |
235 | 240 | else: |
236 | 241 | do_challenge(args) |
237 | 242 | |
238 | print "\nDone\n" | |
243 | print("\nDone\n") | |
239 | 244 | |
240 | 245 | if __name__ == '__main__': |
241 | 246 | main() |
5 | 5 | import sys |
6 | 6 | import struct |
7 | 7 | import yubico |
8 | import six | |
9 | import binascii | |
8 | 10 | |
9 | 11 | slot=2 |
10 | 12 | |
11 | 13 | try: |
12 | 14 | YK = yubico.find_yubikey(debug=True) |
13 | print "Version : %s " % YK.version() | |
14 | print "Status : %s " % YK.status() | |
15 | print("Version : %s " % YK.version()) | |
16 | print("Status : %s " % YK.status()) | |
15 | 17 | |
16 | 18 | Cfg = YK.init_config() |
17 | 19 | Cfg.extended_flag('ALLOW_UPDATE', True) |
18 | 20 | Cfg.ticket_flag('APPEND_CR', True) |
19 | 21 | Cfg.extended_flag('SERIAL_API_VISIBLE', True) |
20 | Cfg.uid = '010203040506'.decode('hex') | |
22 | Cfg.uid = binascii.unhexlify('010203040506') | |
21 | 23 | Cfg.fixed_string("m:ftccftbbftdd") |
22 | 24 | Cfg.aes_key('h:' + 32 * 'a') |
23 | 25 | |
24 | user_input = raw_input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) | |
26 | user_input = six.moves.input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) | |
25 | 27 | if user_input in ('y', 'ye', 'yes'): |
26 | 28 | YK.write_config(Cfg, slot=slot) |
27 | print "\nSuccess!" | |
28 | print "Status : %s " % YK.status() | |
29 | print("\nSuccess!") | |
30 | print("Status : %s " % YK.status()) | |
29 | 31 | else: |
30 | print "\nAborted" | |
32 | print("\nAborted") | |
31 | 33 | sys.exit(0) |
32 | 34 | |
33 | raw_input("Press enter to update...") | |
35 | six.moves.input("Press enter to update...") | |
34 | 36 | |
35 | 37 | Cfg = YK.init_config(update=True) |
36 | 38 | Cfg.ticket_flag('APPEND_CR', False) |
37 | 39 | |
38 | 40 | print ("Updating..."); |
39 | 41 | YK.write_config(Cfg, slot=slot) |
40 | print "\nSuccess!" | |
42 | print("\nSuccess!") | |
41 | 43 | except yubico.yubico_exception.YubicoError as inst: |
42 | print "ERROR: %s" % inst.reason | |
44 | print("ERROR: %s" % inst.reason) | |
43 | 45 | sys.exit(1) |
28 | 28 | keys = get_all_yubikeys(debug) |
29 | 29 | |
30 | 30 | if not keys: |
31 | print "No YubiKey found." | |
31 | print("No YubiKey found.") | |
32 | 32 | else: |
33 | 33 | n = 1 |
34 | 34 | for this in keys: |
35 | print "YubiKey #%02i : %s %s" % (n, this.description, this.status()) | |
35 | print("YubiKey #%02i : %s %s" % (n, this.description, this.status())) | |
36 | 36 | n += 1 |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: python-yubico |
2 | Version: 1.2.3 | |
2 | Version: 1.3.0 | |
3 | 3 | Summary: Python code for talking to Yubico's YubiKeys |
4 | 4 | Home-page: https://github.com/Yubico/python-yubico |
5 | 5 | Author: Yubico Open Source Maintainers |
2 | 2 | MANIFEST.in |
3 | 3 | NEWS |
4 | 4 | README |
5 | release.py | |
6 | 5 | setup.cfg |
7 | 6 | setup.py |
8 | 7 | doc/ykdef.h |
17 | 16 | python_yubico.egg-info/dependency_links.txt |
18 | 17 | python_yubico.egg-info/requires.txt |
19 | 18 | python_yubico.egg-info/top_level.txt |
20 | test/test_yubico.py | |
21 | test/test_yubikey_config.py | |
22 | test/test_yubikey_frame.py | |
23 | test/test_yubikey_usb_hid.py | |
19 | test/__init__.py | |
20 | test/__init__.pyc | |
24 | 21 | util/yubikey-totp |
25 | 22 | util/yubikey-totp.1 |
26 | 23 | yubico/__init__.py |
27 | 24 | yubico/yubico_exception.py |
28 | 25 | yubico/yubico_util.py |
26 | yubico/yubico_version.py | |
29 | 27 | yubico/yubikey.py |
28 | yubico/yubikey_4_usb_hid.py | |
29 | yubico/yubikey_base.py | |
30 | 30 | yubico/yubikey_config.py |
31 | 31 | yubico/yubikey_config_util.py |
32 | 32 | yubico/yubikey_defs.py |
33 | 33 | yubico/yubikey_frame.py |
34 | 34 | yubico/yubikey_neo_usb_hid.py |
35 | yubico/yubikey_usb_hid.py⏎ | |
35 | yubico/yubikey_usb_hid.py | |
36 | yubico/yubicommon/setup/__init__.py | |
37 | yubico/yubicommon/setup/exe.py | |
38 | yubico/yubicommon/setup/pyinstaller_spec.py | |
39 | yubico/yubicommon/setup/qt.py⏎ |
0 | # Copyright (c) 2013 Yubico AB | |
1 | # All rights reserved. | |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or | |
4 | # without modification, are permitted provided that the following | |
5 | # conditions are met: | |
6 | # | |
7 | # 1. Redistributions of source code must retain the above copyright | |
8 | # notice, this list of conditions and the following disclaimer. | |
9 | # 2. Redistributions in binary form must reproduce the above | |
10 | # copyright notice, this list of conditions and the following | |
11 | # disclaimer in the documentation and/or other materials provided | |
12 | # with the distribution. | |
13 | # | |
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
17 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
18 | # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
19 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
24 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | # POSSIBILITY OF SUCH DAMAGE. | |
26 | ||
27 | from distutils import log | |
28 | from distutils.core import Command | |
29 | from distutils.errors import DistutilsSetupError | |
30 | import os | |
31 | import re | |
32 | from datetime import date | |
33 | ||
34 | ||
35 | class release(Command): | |
36 | description = "create and release a new version" | |
37 | user_options = [ | |
38 | ('keyid', None, "GPG key to sign with"), | |
39 | ('skip-tests', None, "skip running the tests"), | |
40 | ('pypi', None, "publish to pypi"), | |
41 | ] | |
42 | boolean_options = ['skip-tests', 'pypi'] | |
43 | ||
44 | def initialize_options(self): | |
45 | self.keyid = None | |
46 | self.skip_tests = 0 | |
47 | self.pypi = 0 | |
48 | ||
49 | def finalize_options(self): | |
50 | self.cwd = os.getcwd() | |
51 | self.fullname = self.distribution.get_fullname() | |
52 | self.name = self.distribution.get_name() | |
53 | self.version = self.distribution.get_version() | |
54 | ||
55 | def _verify_version(self): | |
56 | with open('NEWS', 'r') as news_file: | |
57 | line = news_file.readline() | |
58 | now = date.today().strftime('%Y-%m-%d') | |
59 | if not re.search(r'Version %s \(released %s\)' % (self.version, now), | |
60 | line): | |
61 | raise DistutilsSetupError("Incorrect date/version in NEWS!") | |
62 | ||
63 | def _verify_tag(self): | |
64 | if os.system('git tag | grep -q "^%s\$"' % self.fullname) == 0: | |
65 | raise DistutilsSetupError( | |
66 | "Tag '%s' already exists!" % self.fullname) | |
67 | ||
68 | def _sign(self): | |
69 | if os.path.isfile('dist/%s.tar.gz.asc' % self.fullname): | |
70 | # Signature exists from upload, re-use it: | |
71 | sign_opts = ['--output dist/%s.tar.gz.sig' % self.fullname, | |
72 | '--dearmor dist/%s.tar.gz.asc' % self.fullname] | |
73 | else: | |
74 | # No signature, create it: | |
75 | sign_opts = ['--detach-sign', 'dist/%s.tar.gz' % self.fullname] | |
76 | if self.keyid: | |
77 | sign_opts.insert(1, '--default-key ' + self.keyid) | |
78 | self.execute(os.system, ('gpg ' + (' '.join(sign_opts)),)) | |
79 | ||
80 | if os.system('gpg --verify dist/%s.tar.gz.sig' % self.fullname) != 0: | |
81 | raise DistutilsSetupError("Error verifying signature!") | |
82 | ||
83 | def _tag(self): | |
84 | tag_opts = ['-s', '-m ' + self.fullname, self.fullname] | |
85 | if self.keyid: | |
86 | tag_opts[0] = '-u ' + self.keyid | |
87 | self.execute(os.system, ('git tag ' + (' '.join(tag_opts)),)) | |
88 | ||
89 | def _do_call_publish(self, cmd): | |
90 | self._published = os.system(cmd) == 0 | |
91 | ||
92 | def _publish(self): | |
93 | web_repo = os.getenv('YUBICO_GITHUB_REPO') | |
94 | if web_repo and os.path.isdir(web_repo): | |
95 | artifacts = [ | |
96 | 'dist/%s.tar.gz' % self.fullname, | |
97 | 'dist/%s.tar.gz.sig' % self.fullname | |
98 | ] | |
99 | cmd = '%s/publish %s %s %s' % ( | |
100 | web_repo, self.name, self.version, ' '.join(artifacts)) | |
101 | ||
102 | self.execute(self._do_call_publish, (cmd,)) | |
103 | if self._published: | |
104 | self.announce("Release published! Don't forget to:", log.INFO) | |
105 | self.announce("") | |
106 | self.announce(" (cd %s && git push)" % web_repo, log.INFO) | |
107 | self.announce("") | |
108 | else: | |
109 | self.warn("There was a problem publishing the release!") | |
110 | else: | |
111 | self.warn("YUBICO_GITHUB_REPO not set or invalid!") | |
112 | self.warn("This release will not be published!") | |
113 | ||
114 | def run(self): | |
115 | if os.getcwd() != self.cwd: | |
116 | raise DistutilsSetupError("Must be in package root!") | |
117 | ||
118 | self._verify_version() | |
119 | self._verify_tag() | |
120 | ||
121 | self.execute(os.system, ('git2cl > ChangeLog',)) | |
122 | ||
123 | if not self.skip_tests: | |
124 | self.run_command('check') | |
125 | # Nosetests calls sys.exit(status) | |
126 | try: | |
127 | self.run_command('nosetests') | |
128 | except SystemExit as e: | |
129 | if e.code != 0: | |
130 | raise DistutilsSetupError("There were test failures!") | |
131 | ||
132 | self.run_command('sdist') | |
133 | ||
134 | if self.pypi: | |
135 | cmd_obj = self.distribution.get_command_obj('upload') | |
136 | cmd_obj.sign = True | |
137 | if self.keyid: | |
138 | cmd_obj.identity = self.keyid | |
139 | self.run_command('upload') | |
140 | ||
141 | self._sign() | |
142 | self._tag() | |
143 | ||
144 | self._publish() | |
145 | ||
146 | self.announce("Release complete! Don't forget to:", log.INFO) | |
147 | self.announce("") | |
148 | self.announce(" git push && git push --tags", log.INFO) | |
149 | self.announce("") |
0 | #!/usr/bin/env python | |
0 | # Copyright (c) 2010, 2011, 2012 Yubico AB | |
1 | # See the file COPYING for licence statement. | |
1 | 2 | |
2 | from setuptools import setup | |
3 | from release import release | |
4 | import re | |
5 | ||
6 | VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$") | |
7 | ||
8 | ||
9 | def get_version(): | |
10 | """Return the current version as defined by yubico/__init__.py.""" | |
11 | ||
12 | with open('yubico/__init__.py', 'r') as f: | |
13 | match = VERSION_PATTERN.search(f.read()) | |
14 | return match.group(1) | |
3 | from yubico.yubicommon.setup import setup, get_version | |
15 | 4 | |
16 | 5 | |
17 | 6 | setup( |
18 | 7 | name='python-yubico', |
19 | version=get_version(), | |
20 | 8 | description='Python code for talking to Yubico\'s YubiKeys', |
21 | author='Fredrik Thulin', | |
22 | author_email='fredrik@yubico.com', | |
9 | version=get_version('yubico/yubico_version.py'), | |
10 | author='Dain Nilsson', # Original author: Fredrik Thulin | |
11 | author_email='dain@yubico.com', | |
23 | 12 | maintainer='Yubico Open Source Maintainers', |
24 | 13 | maintainer_email='ossmaint@yubico.com', |
25 | 14 | url='https://github.com/Yubico/python-yubico', |
26 | 15 | license='BSD 2 clause', |
27 | 16 | packages=['yubico'], |
28 | 17 | install_requires=['pyusb'], |
29 | tests_require=['nose>=1.0'], | |
30 | test_suite='nose.collector', | |
31 | cmdclass={'release': release}, | |
18 | test_suite='test', | |
32 | 19 | classifiers=[ |
33 | 20 | 'License :: OSI Approved :: BSD License', |
34 | 21 | 'Operating System :: OS Independent', |
Binary diff not shown
0 | #!/usr/bin/env python | |
1 | # | |
2 | # Simple test cases for a Python version of the yubikey_crc16() function in ykcrc.c. | |
3 | # | |
4 | ||
5 | import struct | |
6 | import unittest | |
7 | import yubico.yubico_util as yubico_util | |
8 | from yubico.yubico_util import crc16 | |
9 | ||
10 | CRC_OK_RESIDUAL=0xf0b8 | |
11 | ||
12 | class TestCRC(unittest.TestCase): | |
13 | ||
14 | def test_first(self): | |
15 | """ Test CRC16 trivial case """ | |
16 | buffer = '\x01\x02\x03\x04' | |
17 | crc = crc16(buffer) | |
18 | self.assertEqual(crc, 0xc66e) | |
19 | return buffer,crc | |
20 | ||
21 | def test_second(self): | |
22 | """ Test CRC16 residual calculation """ | |
23 | buffer,crc = self.test_first() | |
24 | # Append 1st complement for a "self-verifying" block - | |
25 | # from example in Yubikey low level interface | |
26 | crc_inv = 0xffff - crc | |
27 | buffer += struct.pack('<H', crc_inv) | |
28 | crc2 = crc16(buffer) | |
29 | self.assertEqual(crc2, CRC_OK_RESIDUAL) | |
30 | ||
31 | def test_hexdump(self): | |
32 | """ Test hexdump function, normal use """ | |
33 | bytes = '\x01\x02\x03\x04\x05\x06\x07\x08' | |
34 | self.assertEqual(yubico_util.hexdump(bytes, length=4), \ | |
35 | '0000 01 02 03 04\n0004 05 06 07 08\n') | |
36 | ||
37 | def test_hexdump(self): | |
38 | """ Test hexdump function, with colors """ | |
39 | bytes = '\x01\x02\x03\x04\x05\x06\x07\x08' | |
40 | self.assertEqual(yubico_util.hexdump(bytes, length=4, colorize=True), \ | |
41 | '0000 \x1b[0m01 02 03\x1b[0m 04\n0004 \x1b[0m05 06 07\x1b[0m 08\n') | |
42 | ||
43 | def test_modhex_decode(self): | |
44 | """ Test modhex decoding """ | |
45 | self.assertEqual("0123456789abcdef", yubico_util.modhex_decode("cbdefghijklnrtuv")) | |
46 | ||
47 | if __name__ == '__main__': | |
48 | unittest.main() |
0 | #!/usr/bin/env python | |
1 | ||
2 | import unittest | |
3 | import yubico | |
4 | import yubico.yubikey_config | |
5 | from yubico.yubikey_usb_hid import YubiKeyConfigUSBHID | |
6 | import yubico.yubico_util | |
7 | import yubico.yubico_exception | |
8 | ||
9 | class YubiKeyTests(unittest.TestCase): | |
10 | ||
11 | def setUp(self): | |
12 | version = (2, 2, 0,) | |
13 | capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \ | |
14 | model = 'YubiKey', version = version, default_answer = False) | |
15 | self.Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa) | |
16 | ||
17 | def test_static_ticket(self): | |
18 | """ Test static ticket """ | |
19 | ||
20 | #fixed: m: | |
21 | #uid: h:000000000000 | |
22 | #key: h:e2bee9a36568a00d026a02f85e61e6fb | |
23 | #acc_code: h:000000000000 | |
24 | #ticket_flags: APPEND_CR | |
25 | #config_flags: STATIC_TICKET | |
26 | ||
27 | expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
28 | '\x00\xe2\xbe\xe9\xa3\x65\x68\x83', | |
29 | '\xa0\x0d\x02\x6a\x02\xf8\x5e\x84', | |
30 | '\x61\xe6\xfb\x00\x00\x00\x00\x85', | |
31 | '\x00\x00\x00\x00\x20\x20\x00\x86', | |
32 | '\x00\x5a\x93\x00\x00\x00\x00\x87', | |
33 | '\x00\x01\x95\x56\x00\x00\x00\x89' | |
34 | ] | |
35 | ||
36 | Config = self.Config | |
37 | Config.aes_key('h:e2bee9a36568a00d026a02f85e61e6fb') | |
38 | Config.ticket_flag('APPEND_CR', True) | |
39 | Config.config_flag('STATIC_TICKET', True) | |
40 | ||
41 | data = Config.to_frame(slot=1).to_feature_reports() | |
42 | ||
43 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
44 | yubico.yubico_util.hexdump(''.join(data))) | |
45 | ||
46 | self.assertEqual(data, expected) | |
47 | ||
48 | ||
49 | def test_static_ticket_with_access_code(self): | |
50 | """ Test static ticket with unlock code """ | |
51 | ||
52 | #fixed: m: | |
53 | #uid: h:000000000000 | |
54 | #key: h:e2bee9a36568a00d026a02f85e61e6fb | |
55 | #acc_code: h:010203040506 | |
56 | #ticket_flags: APPEND_CR | |
57 | #config_flags: STATIC_TICKET | |
58 | ||
59 | expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
60 | '\x00\xe2\xbe\xe9\xa3\x65\x68\x83', | |
61 | '\xa0\x0d\x02\x6a\x02\xf8\x5e\x84', | |
62 | '\x61\xe6\xfb\x01\x02\x03\x04\x85', | |
63 | '\x05\x06\x00\x00\x20\x20\x00\x86', | |
64 | '\x00\x0d\x39\x01\x02\x03\x04\x87', | |
65 | '\x05\x06\x00\x00\x00\x00\x00\x88', | |
66 | '\x00\x01\xc2\xfc\x00\x00\x00\x89', | |
67 | ] | |
68 | ||
69 | Config = self.Config | |
70 | Config.aes_key('h:e2bee9a36568a00d026a02f85e61e6fb') | |
71 | Config.ticket_flag('APPEND_CR', True) | |
72 | Config.config_flag('STATIC_TICKET', True) | |
73 | Config.unlock_key('h:010203040506') | |
74 | ||
75 | data = Config.to_frame(slot=1).to_feature_reports() | |
76 | ||
77 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
78 | yubico.yubico_util.hexdump(''.join(data))) | |
79 | ||
80 | self.assertEqual(data, expected) | |
81 | ||
82 | def test_fixed_and_oath_hotp(self): | |
83 | """ Test OATH HOTP with a fixed prefix-string """ | |
84 | ||
85 | #fixed: m:ftftftft | |
86 | #uid: h:000000000000 | |
87 | #key: h:523d7ce7e7b6ee853517a3e3cc1985c7 | |
88 | #acc_code: h:000000000000 | |
89 | #ticket_flags: APPEND_CR|OATH_HOTP | |
90 | #config_flags: OATH_FIXED_MODHEX1|OATH_FIXED_MODHEX2|STATIC_TICKET | |
91 | ||
92 | expected = ['\x4d\x4d\x4d\x4d\x00\x00\x00\x80', | |
93 | '\x00\x52\x3d\x7c\xe7\xe7\xb6\x83', | |
94 | '\xee\x85\x35\x17\xa3\xe3\xcc\x84', | |
95 | '\x19\x85\xc7\x00\x00\x00\x00\x85', | |
96 | '\x00\x00\x04\x00\x60\x70\x00\x86', | |
97 | '\x00\x72\xad\xaa\xbb\xcc\xdd\x87', | |
98 | '\xee\xff\x00\x00\x00\x00\x00\x88', | |
99 | '\x00\x03\xfe\xc4\x00\x00\x00\x89', | |
100 | ] | |
101 | ||
102 | Config = self.Config | |
103 | Config.aes_key('h:523d7ce7e7b6ee853517a3e3cc1985c7') | |
104 | Config.fixed_string('m:ftftftft') | |
105 | Config.ticket_flag('APPEND_CR', True) | |
106 | Config.ticket_flag('OATH_HOTP', True) | |
107 | Config.config_flag('OATH_FIXED_MODHEX1', True) | |
108 | Config.config_flag('OATH_FIXED_MODHEX2', True) | |
109 | Config.config_flag('STATIC_TICKET', True) | |
110 | Config.unlock_key('h:aabbccddeeff') | |
111 | Config.access_key('h:000000000000') | |
112 | ||
113 | data = Config.to_frame(slot=2).to_feature_reports() | |
114 | ||
115 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
116 | yubico.yubico_util.hexdump(''.join(data))) | |
117 | ||
118 | self.assertEqual(data, expected) | |
119 | ||
120 | def test_challenge_response_hmac_nist(self): | |
121 | """ Test HMAC challenge response with NIST test vector """ | |
122 | ||
123 | expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
124 | '\x00\x00\x40\x41\x42\x43\x00\x82', | |
125 | '\x00\x30\x31\x32\x33\x34\x35\x83', | |
126 | '\x36\x37\x38\x39\x3a\x3b\x3c\x84', | |
127 | '\x3d\x3e\x3f\x00\x00\x00\x00\x85', | |
128 | '\x00\x00\x00\x04\x40\x26\x00\x86', | |
129 | '\x00\x98\x41\x00\x00\x00\x00\x87', | |
130 | '\x00\x03\x95\x56\x00\x00\x00\x89', | |
131 | ] | |
132 | ||
133 | Config = self.Config | |
134 | secret = 'h:303132333435363738393a3b3c3d3e3f40414243' | |
135 | Config.mode_challenge_response(secret, type='HMAC', variable=True) | |
136 | Config.extended_flag('SERIAL_API_VISIBLE', True) | |
137 | ||
138 | data = Config.to_frame(slot=2).to_feature_reports() | |
139 | ||
140 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
141 | yubico.yubico_util.hexdump(''.join(data))) | |
142 | ||
143 | self.assertEqual(data, expected) | |
144 | ||
145 | def test_unknown_ticket_flag(self): | |
146 | """ Test setting unknown ticket flag """ | |
147 | Config = self.Config | |
148 | self.assertRaises(yubico.yubico_exception.InputError, Config.ticket_flag, 'YK_UNIT_TEST123', True) | |
149 | ||
150 | def test_unknown_ticket_flag_integer(self): | |
151 | """ Test setting unknown ticket flag as integer """ | |
152 | future_flag = 0xff | |
153 | Config = self.Config | |
154 | Config.ticket_flag(future_flag, True) | |
155 | self.assertEqual(future_flag, Config.ticket_flags.to_integer()) | |
156 | ||
157 | def test_too_long_fixed_string(self): | |
158 | """ Test too long fixed string, and set as plain string """ | |
159 | Config = self.Config | |
160 | self.assertRaises(yubico.yubico_exception.InputError, Config.ticket_flag, 'YK_UNIT_TEST123', True) | |
161 | ||
162 | def test_default_flags(self): | |
163 | """ Test that no flags get set by default """ | |
164 | Config = self.Config | |
165 | self.assertEqual(0x0, Config.ticket_flags.to_integer()) | |
166 | self.assertEqual(0x0, Config.config_flags.to_integer()) | |
167 | self.assertEqual(0x0, Config.extended_flags.to_integer()) | |
168 | ||
169 | def test_oath_hotp_like_windows(self): | |
170 | """ Test plain OATH-HOTP with NIST test vector """ | |
171 | ||
172 | expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
173 | '\x00\x00\x40\x41\x42\x43\x00\x82', | |
174 | '\x00\x30\x31\x32\x33\x34\x35\x83', | |
175 | '\x36\x37\x38\x39\x3a\x3b\x3c\x84', | |
176 | '\x3d\x3e\x3f\x00\x00\x00\x00\x85', | |
177 | '\x00\x00\x00\x00\x40\x00\x00\x86', | |
178 | '\x00\x6a\xb9\x00\x00\x00\x00\x87', | |
179 | '\x00\x03\x95\x56\x00\x00\x00\x89', | |
180 | ] | |
181 | ||
182 | Config = self.Config | |
183 | secret = 'h:303132333435363738393a3b3c3d3e3f40414243' | |
184 | Config.mode_oath_hotp(secret) | |
185 | ||
186 | data = Config.to_frame(slot=2).to_feature_reports() | |
187 | ||
188 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
189 | yubico.yubico_util.hexdump(''.join(data))) | |
190 | ||
191 | self.assertEqual(data, expected) | |
192 | ||
193 | def test_oath_hotp_like_windows2(self): | |
194 | """ Test OATH-HOTP with NIST test vector and token identifier """ | |
195 | ||
196 | expected = ['\x01\x02\x03\x04\x05\x06\x00\x80', | |
197 | '\x00\x00\x40\x41\x42\x43\x00\x82', | |
198 | '\x00\x30\x31\x32\x33\x34\x35\x83', | |
199 | '\x36\x37\x38\x39\x3a\x3b\x3c\x84', | |
200 | '\x3d\x3e\x3f\x00\x00\x00\x00\x85', | |
201 | '\x00\x00\x06\x00\x40\x42\x00\x86', | |
202 | '\x00\x0e\xec\x00\x00\x00\x00\x87', | |
203 | '\x00\x03\x95\x56\x00\x00\x00\x89', | |
204 | ] | |
205 | ||
206 | Config = self.Config | |
207 | secret = 'h:303132333435363738393a3b3c3d3e3f40414243' | |
208 | Config.mode_oath_hotp(secret, digits=8, factor_seed='', omp=0x01, tt=0x02, mui='\x03\x04\x05\x06') | |
209 | Config.config_flag('OATH_FIXED_MODHEX2', True) | |
210 | ||
211 | data = Config.to_frame(slot=2).to_feature_reports() | |
212 | ||
213 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
214 | yubico.yubico_util.hexdump(''.join(data))) | |
215 | ||
216 | self.assertEqual(data, expected) | |
217 | ||
218 | def test_oath_hotp_like_windows_factory_seed(self): | |
219 | """ Test OATH-HOTP factor_seed """ | |
220 | ||
221 | expected = ['\x01\x02\x03\x04\x05\x06\x00\x80', | |
222 | '\x00\x00\x40\x41\x42\x43\x01\x82', | |
223 | '\x21\x30\x31\x32\x33\x34\x35\x83', | |
224 | '\x36\x37\x38\x39\x3a\x3b\x3c\x84', | |
225 | '\x3d\x3e\x3f\x00\x00\x00\x00\x85', | |
226 | '\x00\x00\x06\x00\x40\x42\x00\x86', | |
227 | '\x00\x03\xea\x00\x00\x00\x00\x87', | |
228 | '\x00\x03\x95\x56\x00\x00\x00\x89', | |
229 | ] | |
230 | ||
231 | Config = self.Config | |
232 | secret = 'h:303132333435363738393a3b3c3d3e3f40414243' | |
233 | Config.mode_oath_hotp(secret, digits=8, factor_seed=0x2101, omp=0x01, tt=0x02, mui='\x03\x04\x05\x06') | |
234 | Config.config_flag('OATH_FIXED_MODHEX2', True) | |
235 | ||
236 | data = Config.to_frame(slot=2).to_feature_reports() | |
237 | ||
238 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
239 | yubico.yubico_util.hexdump(''.join(data))) | |
240 | ||
241 | self.assertEqual(data, expected) | |
242 | ||
243 | def test_fixed_length_hmac_like_windows(self): | |
244 | """ Test fixed length HMAC SHA1 """ | |
245 | ||
246 | expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
247 | '\x00\x00\x40\x41\x42\x43\x00\x82', | |
248 | '\x00\x30\x31\x32\x33\x34\x35\x83', | |
249 | '\x36\x37\x38\x39\x3a\x3b\x3c\x84', | |
250 | '\x3d\x3e\x3f\x00\x00\x00\x00\x85', | |
251 | '\x00\x00\x00\x00\x40\x22\x00\x86', | |
252 | '\x00\xe9\x0f\x00\x00\x00\x00\x87', | |
253 | '\x00\x03\x95\x56\x00\x00\x00\x89', | |
254 | ] | |
255 | ||
256 | Config = self.Config | |
257 | secret = 'h:303132333435363738393a3b3c3d3e3f40414243' | |
258 | Config.mode_challenge_response(secret, type='HMAC', variable=False) | |
259 | ||
260 | data = Config.to_frame(slot=2).to_feature_reports() | |
261 | ||
262 | print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), | |
263 | yubico.yubico_util.hexdump(''.join(data))) | |
264 | ||
265 | self.assertEqual(data, expected) | |
266 | ||
267 | def test_version_required_1(self): | |
268 | """ Test YubiKey 1 with v2 option """ | |
269 | version = (1, 3, 0,) | |
270 | capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \ | |
271 | model = 'YubiKey', version = version, default_answer = False) | |
272 | Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa) | |
273 | self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.config_flag, 'SHORT_TICKET', True) | |
274 | ||
275 | def test_version_required_2(self): | |
276 | """ Test YubiKey 2 with v2 option """ | |
277 | ||
278 | Config = self.Config | |
279 | Config.config_flag('SHORT_TICKET', True) | |
280 | self.assertEqual((2, 0), Config.version_required()) | |
281 | ||
282 | def test_version_required_3(self): | |
283 | """ Test YubiKey 2 with v1 option """ | |
284 | ||
285 | Config = self.Config | |
286 | self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.config_flag, 'TICKET_FIRST', True) | |
287 | ||
288 | def test_version_required_4(self): | |
289 | """ Test YubiKey 2.1 with v2.2 mode """ | |
290 | version = (2, 1, 0,) | |
291 | capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \ | |
292 | model = 'YubiKey', version = version, default_answer = False) | |
293 | Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa) | |
294 | secret = 'h:303132333435363738393a3b3c3d3e3f40414243' | |
295 | self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.mode_challenge_response, secret) | |
296 | ||
297 | def test_version_required_5(self): | |
298 | """ Test YubiKey 2.2 with v2.2 mode """ | |
299 | ||
300 | Config = self.Config | |
301 | secret = 'h:303132333435363738393a3b3c3d3e3f' | |
302 | Config.mode_challenge_response(secret, type='OTP') | |
303 | self.assertEqual('CHAL_RESP', Config._mode) | |
304 | ||
305 | if __name__ == '__main__': | |
306 | unittest.main() |
0 | #!/usr/bin/env python | |
1 | ||
2 | from yubico import * | |
3 | from yubico.yubikey_frame import * | |
4 | import yubico.yubico_exception | |
5 | import unittest | |
6 | import struct | |
7 | import re | |
8 | ||
9 | class YubiKeyTests(unittest.TestCase): | |
10 | ||
11 | def test_get_ykframe(self): | |
12 | """ Test normal use """ | |
13 | buffer = YubiKeyFrame(command=0x01).to_string() | |
14 | ||
15 | # check number of bytes returned | |
16 | self.assertEqual(len(buffer), 70, "yubikey command buffer should always be 70 bytes") | |
17 | ||
18 | # check that empty payload works (64 * '\x00') | |
19 | all_zeros = '\x00' * 64 | |
20 | ||
21 | self.assertTrue(buffer.startswith(all_zeros)) | |
22 | ||
23 | ||
24 | def test_get_ykframe_feature_reports(self): | |
25 | """ Test normal use """ | |
26 | res = YubiKeyFrame(command=0x32).to_feature_reports() | |
27 | ||
28 | self.assertEqual(res, ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
29 | '\x00\x32\x6b\x5b\x00\x00\x00\x89' | |
30 | ]) | |
31 | ||
32 | ||
33 | def test_get_ykframe_feature_reports2(self): | |
34 | """ Test one serie of non-zero bytes in the middle of the payload """ | |
35 | payload = '\x00' * 38 | |
36 | payload += '\x01\x02\x03' | |
37 | payload += '\x00' * 23 | |
38 | res = YubiKeyFrame(command=0x32, payload=payload).to_feature_reports() | |
39 | ||
40 | self.assertEqual(res, ['\x00\x00\x00\x00\x00\x00\x00\x80', | |
41 | '\x00\x00\x00\x01\x02\x03\x00\x85', | |
42 | '\x002\x01s\x00\x00\x00\x89']) | |
43 | ||
44 | def test_bad_payload(self): | |
45 | """ Test that we get an exception for four bytes payload """ | |
46 | self.assertRaises(yubico_exception.InputError, YubiKeyFrame, command=0x32, payload='test') | |
47 | ||
48 | def test_repr(self): | |
49 | """ Test string representation of object """ | |
50 | # to achieve 100% test coverage ;) | |
51 | frame = YubiKeyFrame(command=0x4d) | |
52 | print "Frame is represented as %s" % frame | |
53 | re_match = re.search("YubiKeyFrame instance at .*: 77.$", str(frame)) | |
54 | self.assertNotEqual(re_match, None) | |
55 | ||
56 | if __name__ == '__main__': | |
57 | unittest.main() |
0 | #!/usr/bin/env python | |
1 | # | |
2 | # Test cases for talking to a USB HID YubiKey. | |
3 | # | |
4 | ||
5 | import struct | |
6 | import unittest | |
7 | import yubico | |
8 | import yubico.yubikey_usb_hid | |
9 | from yubico.yubikey_usb_hid import * | |
10 | import re | |
11 | ||
12 | class TestYubiKeyUSBHID(unittest.TestCase): | |
13 | ||
14 | YK = None | |
15 | ||
16 | def setUp(self): | |
17 | """ Test connecting to the YubiKey """ | |
18 | if self.YK is None: | |
19 | try: | |
20 | print "open key" | |
21 | self.YK = YubiKeyUSBHID() | |
22 | return | |
23 | except YubiKeyUSBHIDError, err: | |
24 | self.fail("No YubiKey connected (?) : %s" % str(err)) | |
25 | ||
26 | def tearDown(self): | |
27 | if self.YK is not None: | |
28 | del self.YK | |
29 | ||
30 | #@unittest.skipIf(YK is None, "No USB HID YubiKey found") | |
31 | def test_status(self): | |
32 | """ Test the simplest form of communication : a status read request """ | |
33 | status = self.YK.status() | |
34 | version = self.YK.version() | |
35 | print "Version returned: %s" % version | |
36 | re_match = re.match("\d+\.\d+\.\d+$", version) | |
37 | self.assertNotEqual(re_match, None) | |
38 | ||
39 | #@unittest.skipIf(self.YK is None, "No USB HID YubiKey found") | |
40 | def test_challenge_response(self): | |
41 | """ Test challenge-response, assumes a NIST PUB 198 A.2 20 bytes test vector in Slot 2 (variable input) """ | |
42 | ||
43 | secret = struct.pack('64s', 'Sample #2') | |
44 | response = self.YK.challenge_response(secret, mode='HMAC', slot=2) | |
45 | self.assertEqual(response, '\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24') | |
46 | ||
47 | #@unittest.skipIf(self.YK is None, "No USB HID YubiKey found") | |
48 | def test_serial(self): | |
49 | """ Test serial number retrieval (requires YubiKey 2) """ | |
50 | serial = self.YK.serial() | |
51 | print "Serial returned : %s" % serial | |
52 | self.assertEqual(type(serial), type(1)) | |
53 | ||
54 | if __name__ == '__main__': | |
55 | unittest.main() |
37 | 37 | import struct |
38 | 38 | import yubico |
39 | 39 | import argparse |
40 | import binascii | |
40 | 41 | |
41 | 42 | default_slot=2 |
42 | 43 | default_time=int(time.time()) |
96 | 97 | """ |
97 | 98 | YK = yubico.find_yubikey(debug=args.debug) |
98 | 99 | if args.debug or args.verbose: |
99 | print "Version : %s " % YK.version() | |
100 | print("Version : %s " % YK.version()) | |
100 | 101 | if args.debug: |
101 | print "Serial : %i" % YK.serial() | |
102 | print "" | |
102 | print("Serial : %i" % YK.serial()) | |
103 | print("") | |
103 | 104 | # Do challenge-response |
104 | 105 | secret = struct.pack("> Q", args.time / args.step).ljust(64, chr(0x0)) |
105 | 106 | if args.debug: |
106 | print "Sending challenge : %s\n" % (secret.encode('hex')) | |
107 | print("Sending challenge : %s\n" % (binascii.hexlify(secret))) | |
107 | 108 | response = YK.challenge_response(secret, slot=args.slot) |
108 | 109 | # format with appropriate number of leading zeros |
109 | fmt = "%." + str(args.digits) + "i" | |
110 | totp_str = fmt % (yubico.yubico_util.hotp_truncate(response, length=args.digits)) | |
110 | totp_str = '%.*i' % (args.digits, yubico.yubico_util.hotp_truncate(response, length=args.digits)) | |
111 | 111 | return totp_str |
112 | 112 | |
113 | 113 | def main(): |
117 | 117 | otp = None |
118 | 118 | try: |
119 | 119 | otp = make_totp(args) |
120 | except yubico.yubico_exception.YubicoError, e: | |
121 | print "ERROR: %s" % (e.reason) | |
120 | except yubico.yubico_exception.YubicoError as e: | |
121 | print("ERROR: %s" % (e.reason)) | |
122 | 122 | return 1 |
123 | 123 | |
124 | 124 | if not otp: |
125 | 125 | return 1 |
126 | 126 | |
127 | print otp | |
127 | print(otp) | |
128 | 128 | return 0 |
129 | 129 | |
130 | 130 | if __name__ == '__main__': |
19 | 19 | # Copyright (c) 2010, 2011, 2012 Yubico AB |
20 | 20 | # See the file COPYING for licence statement. |
21 | 21 | |
22 | __version__ = "1.2.3" | |
22 | from .yubico_version import __version__ | |
23 | 23 | |
24 | 24 | __all__ = [ |
25 | 25 | # classes |
39 | 39 | ] |
40 | 40 | |
41 | 41 | # to not have to import yubico.yubikey |
42 | from yubikey import YubiKey | |
43 | from yubikey import find_key as find_yubikey | |
42 | from .yubikey import YubiKey | |
43 | from .yubikey import find_key as find_yubikey |
21 | 21 | 'YubiKeyTimeout', |
22 | 22 | ] |
23 | 23 | |
24 | from yubico import __version__ | |
24 | from .yubico_version import __version__ | |
25 | ||
25 | 26 | |
26 | 27 | class YubicoError(Exception): |
27 | 28 | """ |
43 | 44 | |
44 | 45 | pass |
45 | 46 | |
47 | ||
46 | 48 | class InputError(YubicoError): |
47 | 49 | """ |
48 | 50 | Exception raised for errors in an input to some function. |
49 | 51 | """ |
50 | 52 | def __init__(self, reason='input validation error'): |
51 | YubicoError.__init__(self, reason) | |
53 | super(InputError, self).__init__(reason) |
14 | 14 | # classes |
15 | 15 | ] |
16 | 16 | |
17 | from yubico import __version__ | |
18 | import yubikey_defs | |
19 | import yubico_exception | |
17 | import sys | |
20 | 18 | import string |
19 | ||
20 | from .yubico_version import __version__ | |
21 | from . import yubikey_defs | |
22 | from . import yubico_exception | |
21 | 23 | |
22 | 24 | _CRC_OK_RESIDUAL = 0xf0b8 |
23 | 25 | |
26 | def ord_byte(byte): | |
27 | """Convert a byte to its integer value""" | |
28 | if sys.version_info < (3, 0): | |
29 | return ord(byte) | |
30 | else: | |
31 | # In Python 3, single bytes are represented as integers | |
32 | return int(byte) | |
33 | ||
34 | def chr_byte(number): | |
35 | """Convert an integer value to a length-1 bytestring""" | |
36 | if sys.version_info < (3, 0): | |
37 | return chr(number) | |
38 | else: | |
39 | return bytes([number]) | |
40 | ||
24 | 41 | def crc16(data): |
25 | 42 | """ |
26 | Calculate an ISO13239 CRC checksum of the input buffer. | |
43 | Calculate an ISO13239 CRC checksum of the input buffer (bytestring). | |
27 | 44 | """ |
28 | 45 | m_crc = 0xffff |
29 | 46 | for this in data: |
30 | m_crc ^= ord(this) | |
47 | m_crc ^= ord_byte(this) | |
31 | 48 | for _ in range(8): |
32 | 49 | j = m_crc & 1 |
33 | 50 | m_crc >>= 1 |
38 | 55 | def validate_crc16(data): |
39 | 56 | """ |
40 | 57 | Validate that the CRC of the contents of buffer is the residual OK value. |
58 | ||
59 | The input is a bytestring. | |
41 | 60 | """ |
42 | 61 | return crc16(data) == _CRC_OK_RESIDUAL |
43 | 62 | |
73 | 92 | self.enabled = False |
74 | 93 | |
75 | 94 | def hexdump(src, length=8, colorize=False): |
76 | """ Produce a string hexdump of src, for debug output.""" | |
95 | """ Produce a string hexdump of src, for debug output. | |
96 | ||
97 | Input: bytestring; output: text string | |
98 | """ | |
77 | 99 | if not src: |
78 | 100 | return str(src) |
79 | if type(src) is not str: | |
80 | raise yubico_exception.InputError('Hexdump \'src\' must be string (got %s)' % type(src)) | |
101 | if type(src) is not bytes: | |
102 | raise yubico_exception.InputError('Hexdump \'src\' must be bytestring (got %s)' % type(src)) | |
81 | 103 | offset = 0 |
82 | 104 | result = '' |
83 | 105 | for this in group(src, length): |
84 | 106 | if colorize: |
85 | last, this = this[-1:], this[:-1] | |
107 | last, this = this[-1], this[:-1] | |
86 | 108 | colors = DumpColors() |
87 | 109 | color = colors.get('RESET') |
88 | if ord(last) & yubikey_defs.RESP_PENDING_FLAG: | |
110 | if ord_byte(last) & yubikey_defs.RESP_PENDING_FLAG: | |
89 | 111 | # write to key |
90 | 112 | color = colors.get('BLUE') |
91 | elif ord(last) & yubikey_defs.SLOT_WRITE_FLAG: | |
113 | elif ord_byte(last) & yubikey_defs.SLOT_WRITE_FLAG: | |
92 | 114 | color = colors.get('GREEN') |
93 | hex_s = color + ' '.join(["%02x" % ord(x) for x in this]) + colors.get('RESET') | |
94 | hex_s += " %02x" % ord(last) | |
115 | hex_s = color + ' '.join(["%02x" % ord_byte(x) for x in this]) + colors.get('RESET') | |
116 | hex_s += " %02x" % ord_byte(last) | |
95 | 117 | else: |
96 | hex_s = ' '.join(["%02x" % ord(x) for x in this]) | |
118 | hex_s = ' '.join(["%02x" % ord_byte(x) for x in this]) | |
97 | 119 | result += "%04X %s\n" % (offset, hex_s) |
98 | 120 | offset += length |
99 | 121 | return result |
100 | 122 | |
101 | 123 | def group(data, num): |
102 | 124 | """ Split data into chunks of num chars each """ |
103 | return [data[i:i+num] for i in xrange(0, len(data), num)] | |
125 | return [data[i:i+num] for i in range(0, len(data), num)] | |
104 | 126 | |
105 | 127 | def modhex_decode(data): |
106 | """ Convert a modhex string to ordinary hex. """ | |
107 | t_map = string.maketrans("cbdefghijklnrtuv", "0123456789abcdef") | |
128 | """ Convert a modhex bytestring to ordinary hex. """ | |
129 | try: | |
130 | maketrans = string.maketrans | |
131 | except AttributeError: | |
132 | # Python 3 | |
133 | maketrans = bytes.maketrans | |
134 | t_map = maketrans(b"cbdefghijklnrtuv", b"0123456789abcdef") | |
108 | 135 | return data.translate(t_map) |
109 | 136 | |
110 | 137 | def hotp_truncate(hmac_result, length=6): |
111 | """ Perform the HOTP Algorithm truncating. """ | |
138 | """ Perform the HOTP Algorithm truncating. | |
139 | ||
140 | Input is a bytestring. | |
141 | """ | |
112 | 142 | if len(hmac_result) != 20: |
113 | 143 | raise yubico_exception.YubicoError("HMAC-SHA-1 not 20 bytes long") |
114 | offset = ord(hmac_result[19]) & 0xf | |
115 | bin_code = (ord(hmac_result[offset]) & 0x7f) << 24 \ | |
116 | | (ord(hmac_result[offset+1]) & 0xff) << 16 \ | |
117 | | (ord(hmac_result[offset+2]) & 0xff) << 8 \ | |
118 | | (ord(hmac_result[offset+3]) & 0xff) | |
144 | offset = ord_byte(hmac_result[19]) & 0xf | |
145 | bin_code = (ord_byte(hmac_result[offset]) & 0x7f) << 24 \ | |
146 | | (ord_byte(hmac_result[offset+1]) & 0xff) << 16 \ | |
147 | | (ord_byte(hmac_result[offset+2]) & 0xff) << 8 \ | |
148 | | (ord_byte(hmac_result[offset+3]) & 0xff) | |
119 | 149 | return bin_code % (10 ** length) |
150 | ||
151 | def tlv_parse(data): | |
152 | """ Parses a bytestring of TLV values into a dict with the tags as keys.""" | |
153 | parsed = {} | |
154 | while data: | |
155 | t, l, data = ord_byte(data[0]), ord_byte(data[1]), data[2:] | |
156 | parsed[t], data = data[:l], data[l:] | |
157 | return parsed |
0 | __version__ = "1.3.0" |
0 | # Copyright (c) 2013 Yubico AB | |
1 | # All rights reserved. | |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or | |
4 | # without modification, are permitted provided that the following | |
5 | # conditions are met: | |
6 | # | |
7 | # 1. Redistributions of source code must retain the above copyright | |
8 | # notice, this list of conditions and the following disclaimer. | |
9 | # 2. Redistributions in binary form must reproduce the above | |
10 | # copyright notice, this list of conditions and the following | |
11 | # disclaimer in the documentation and/or other materials provided | |
12 | # with the distribution. | |
13 | # | |
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
17 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
18 | # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
19 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
24 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | # POSSIBILITY OF SUCH DAMAGE. | |
26 | ||
27 | from __future__ import absolute_import | |
28 | ||
29 | ||
30 | __dependencies__ = [] | |
31 | __all__ = ['get_version', 'setup', 'release'] | |
32 | ||
33 | ||
34 | from setuptools import setup as _setup, find_packages, Command | |
35 | from setuptools.command.sdist import sdist | |
36 | from distutils import log | |
37 | from distutils.errors import DistutilsSetupError | |
38 | from datetime import date | |
39 | from glob import glob | |
40 | import os | |
41 | import re | |
42 | ||
43 | VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$") | |
44 | DEPENDENCY_PATTERN = re.compile( | |
45 | r"(?m)__dependencies__\s*=\s*\[((['\"].+['\"]\s*(,\s*)?)+)\]") | |
46 | ||
47 | base_module = __name__.rsplit('.', 1)[0] | |
48 | ||
49 | ||
50 | def get_version(module_name_or_file=None): | |
51 | """Return the current version as defined by the given module/file.""" | |
52 | ||
53 | if module_name_or_file is None: | |
54 | parts = base_module.split('.') | |
55 | module_name_or_file = parts[0] if len(parts) > 1 else \ | |
56 | find_packages(exclude=['test', 'test.*'])[0] | |
57 | ||
58 | if os.path.isdir(module_name_or_file): | |
59 | module_name_or_file = os.path.join(module_name_or_file, '__init__.py') | |
60 | ||
61 | with open(module_name_or_file, 'r') as f: | |
62 | match = VERSION_PATTERN.search(f.read()) | |
63 | return match.group(1) | |
64 | ||
65 | ||
66 | def get_dependencies(module): | |
67 | basedir = os.path.dirname(__file__) | |
68 | fn = os.path.join(basedir, module + '.py') | |
69 | if os.path.isfile(fn): | |
70 | with open(fn, 'r') as f: | |
71 | match = DEPENDENCY_PATTERN.search(f.read()) | |
72 | if match: | |
73 | return [s.strip().strip('"\'') | |
74 | for s in match.group(1).split(',')] | |
75 | return [] | |
76 | ||
77 | ||
78 | def get_package(module): | |
79 | return base_module + '.' + module | |
80 | ||
81 | ||
82 | def setup(**kwargs): | |
83 | # TODO: Find a better way to pass this to a command. | |
84 | os.environ['setup_long_name'] = kwargs.pop('long_name', kwargs.get('name')) | |
85 | ||
86 | if 'version' not in kwargs: | |
87 | kwargs['version'] = get_version() | |
88 | packages = kwargs.setdefault( | |
89 | 'packages', | |
90 | find_packages(exclude=['test', 'test.*', base_module + '.*'])) | |
91 | packages.append(__name__) | |
92 | install_requires = kwargs.setdefault('install_requires', []) | |
93 | for yc_module in kwargs.pop('yc_requires', []): | |
94 | packages.append(get_package(yc_module)) | |
95 | for dep in get_dependencies(yc_module): | |
96 | if dep not in install_requires: | |
97 | install_requires.append(dep) | |
98 | cmdclass = kwargs.setdefault('cmdclass', {}) | |
99 | cmdclass.setdefault('release', release) | |
100 | cmdclass.setdefault('build_man', build_man) | |
101 | cmdclass.setdefault('sdist', custom_sdist) | |
102 | return _setup(**kwargs) | |
103 | ||
104 | ||
105 | class custom_sdist(sdist): | |
106 | def run(self): | |
107 | self.run_command('build_man') | |
108 | ||
109 | # Run if available: | |
110 | if 'qt_resources' in self.distribution.cmdclass: | |
111 | self.run_command('qt_resources') | |
112 | ||
113 | sdist.run(self) | |
114 | ||
115 | ||
116 | class build_man(Command): | |
117 | description = "create man pages from asciidoc source" | |
118 | user_options = [] | |
119 | boolean_options = [] | |
120 | ||
121 | def initialize_options(self): | |
122 | pass | |
123 | ||
124 | def finalize_options(self): | |
125 | self.cwd = os.getcwd() | |
126 | self.fullname = self.distribution.get_fullname() | |
127 | self.name = self.distribution.get_name() | |
128 | self.version = self.distribution.get_version() | |
129 | ||
130 | def run(self): | |
131 | if os.getcwd() != self.cwd: | |
132 | raise DistutilsSetupError("Must be in package root!") | |
133 | ||
134 | for fname in glob(os.path.join('man', '*.adoc')): | |
135 | self.announce("Converting: " + fname, log.INFO) | |
136 | self.execute(os.system, | |
137 | ('a2x -d manpage -f manpage "%s"' % fname,)) | |
138 | ||
139 | ||
140 | class release(Command): | |
141 | description = "create and release a new version" | |
142 | user_options = [ | |
143 | ('keyid', None, "GPG key to sign with"), | |
144 | ('skip-tests', None, "skip running the tests"), | |
145 | ('pypi', None, "publish to pypi"), | |
146 | ] | |
147 | boolean_options = ['skip-tests', 'pypi'] | |
148 | ||
149 | def initialize_options(self): | |
150 | self.keyid = None | |
151 | self.skip_tests = 0 | |
152 | self.pypi = 0 | |
153 | ||
154 | def finalize_options(self): | |
155 | self.cwd = os.getcwd() | |
156 | self.fullname = self.distribution.get_fullname() | |
157 | self.name = self.distribution.get_name() | |
158 | self.version = self.distribution.get_version() | |
159 | ||
160 | def _verify_version(self): | |
161 | with open('NEWS', 'r') as news_file: | |
162 | line = news_file.readline() | |
163 | now = date.today().strftime('%Y-%m-%d') | |
164 | if not re.search(r'Version %s \(released %s\)' % (self.version, now), | |
165 | line): | |
166 | raise DistutilsSetupError("Incorrect date/version in NEWS!") | |
167 | ||
168 | def _verify_tag(self): | |
169 | if os.system('git tag | grep -q "^%s\$"' % self.fullname) == 0: | |
170 | raise DistutilsSetupError( | |
171 | "Tag '%s' already exists!" % self.fullname) | |
172 | ||
173 | def _verify_not_dirty(self): | |
174 | if os.system('git diff --shortstat | grep -q "."') == 0: | |
175 | raise DistutilsSetupError("Git has uncommitted changes!") | |
176 | ||
177 | def _sign(self): | |
178 | if os.path.isfile('dist/%s.tar.gz.asc' % self.fullname): | |
179 | # Signature exists from upload, re-use it: | |
180 | sign_opts = ['--output dist/%s.tar.gz.sig' % self.fullname, | |
181 | '--dearmor dist/%s.tar.gz.asc' % self.fullname] | |
182 | else: | |
183 | # No signature, create it: | |
184 | sign_opts = ['--detach-sign', 'dist/%s.tar.gz' % self.fullname] | |
185 | if self.keyid: | |
186 | sign_opts.insert(1, '--default-key ' + self.keyid) | |
187 | self.execute(os.system, ('gpg ' + (' '.join(sign_opts)),)) | |
188 | ||
189 | if os.system('gpg --verify dist/%s.tar.gz.sig' % self.fullname) != 0: | |
190 | raise DistutilsSetupError("Error verifying signature!") | |
191 | ||
192 | def _tag(self): | |
193 | tag_opts = ['-s', '-m ' + self.fullname, self.fullname] | |
194 | if self.keyid: | |
195 | tag_opts[0] = '-u ' + self.keyid | |
196 | self.execute(os.system, ('git tag ' + (' '.join(tag_opts)),)) | |
197 | ||
198 | def run(self): | |
199 | if os.getcwd() != self.cwd: | |
200 | raise DistutilsSetupError("Must be in package root!") | |
201 | ||
202 | self._verify_version() | |
203 | self._verify_tag() | |
204 | self._verify_not_dirty() | |
205 | self.run_command('check') | |
206 | ||
207 | self.execute(os.system, ('git2cl > ChangeLog',)) | |
208 | ||
209 | self.run_command('sdist') | |
210 | ||
211 | if not self.skip_tests: | |
212 | try: | |
213 | self.run_command('test') | |
214 | except SystemExit as e: | |
215 | if e.code != 0: | |
216 | raise DistutilsSetupError("There were test failures!") | |
217 | ||
218 | if self.pypi: | |
219 | cmd_obj = self.distribution.get_command_obj('upload') | |
220 | cmd_obj.sign = True | |
221 | if self.keyid: | |
222 | cmd_obj.identity = self.keyid | |
223 | self.run_command('upload') | |
224 | ||
225 | self._sign() | |
226 | self._tag() | |
227 | ||
228 | self.announce("Release complete! Don't forget to:", log.INFO) | |
229 | self.announce("") | |
230 | self.announce(" git push && git push --tags", log.INFO) | |
231 | self.announce("") |
0 | # Copyright (c) 2013 Yubico AB | |
1 | # All rights reserved. | |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or | |
4 | # without modification, are permitted provided that the following | |
5 | # conditions are met: | |
6 | # | |
7 | # 1. Redistributions of source code must retain the above copyright | |
8 | # notice, this list of conditions and the following disclaimer. | |
9 | # 2. Redistributions in binary form must reproduce the above | |
10 | # copyright notice, this list of conditions and the following | |
11 | # disclaimer in the documentation and/or other materials provided | |
12 | # with the distribution. | |
13 | # | |
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
17 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
18 | # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
19 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
24 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | # POSSIBILITY OF SUCH DAMAGE. | |
26 | ||
27 | from __future__ import absolute_import | |
28 | ||
29 | from setuptools import Command | |
30 | from distutils.errors import DistutilsSetupError | |
31 | import os | |
32 | import json | |
33 | import tempfile | |
34 | ||
35 | ||
36 | class executable(Command): | |
37 | description = "create an executable" | |
38 | user_options = [ | |
39 | ('debug', None, "build with debug flag"), | |
40 | ] | |
41 | boolean_options = ['debug'] | |
42 | ||
43 | def initialize_options(self): | |
44 | self.debug = 0 | |
45 | ||
46 | def finalize_options(self): | |
47 | self.cwd = os.getcwd() | |
48 | ||
49 | def run(self): | |
50 | if os.getcwd() != self.cwd: | |
51 | raise DistutilsSetupError("Must be in package root!") | |
52 | ||
53 | from PyInstaller.main import run as pyinst_run | |
54 | ||
55 | os.environ['pyinstaller_data'] = json.dumps({ | |
56 | 'debug': self.debug, | |
57 | 'name': self.distribution.get_name(), | |
58 | 'long_name': os.environ['setup_long_name'] | |
59 | }) | |
60 | ||
61 | spec = tempfile.NamedTemporaryFile(suffix='.spec', delete=False) | |
62 | source = os.path.join(os.path.dirname(__file__), 'pyinstaller_spec.py') | |
63 | with open(source) as f: | |
64 | spec.write(f.read()) | |
65 | spec_name = spec.name | |
66 | spec.close() | |
67 | pyinst_run([spec_name]) | |
68 | os.unlink(spec_name) | |
69 | ||
70 | self.announce("Executable created!") |
0 | # -*- mode: python -*- | |
1 | # -*- encoding: utf-8 -*- | |
2 | ||
3 | from __future__ import absolute_import | |
4 | from __future__ import print_function | |
5 | ||
6 | import os | |
7 | import sys | |
8 | import json | |
9 | import errno | |
10 | import pkg_resources | |
11 | from glob import glob | |
12 | ||
13 | ||
14 | VS_VERSION_INFO = """ | |
15 | VSVersionInfo( | |
16 | ffi=FixedFileInfo( | |
17 | # filevers and prodvers should be always a tuple with four | |
18 | # items: (1, 2, 3, 4) | |
19 | # Set not needed items to zero 0. | |
20 | filevers=%(ver_tup)r, | |
21 | prodvers=%(ver_tup)r, | |
22 | # Contains a bitmask that specifies the valid bits 'flags'r | |
23 | mask=0x0, | |
24 | # Contains a bitmask that specifies the Boolean attributes | |
25 | # of the file. | |
26 | flags=0x0, | |
27 | # The operating system for which this file was designed. | |
28 | # 0x4 - NT and there is no need to change it. | |
29 | OS=0x4, | |
30 | # The general type of file. | |
31 | # 0x1 - the file is an application. | |
32 | fileType=0x1, | |
33 | # The function of the file. | |
34 | # 0x0 - the function is not defined for this fileType | |
35 | subtype=0x0, | |
36 | # Creation date and time stamp. | |
37 | date=(0, 0) | |
38 | ), | |
39 | kids=[ | |
40 | StringFileInfo( | |
41 | [ | |
42 | StringTable( | |
43 | u'040904E4', | |
44 | [StringStruct(u'FileDescription', u'%(name)s'), | |
45 | StringStruct(u'FileVersion', u'%(ver_str)s'), | |
46 | StringStruct(u'InternalName', u'%(internal_name)s'), | |
47 | StringStruct(u'LegalCopyright', u'Copyright © 2015 Yubico'), | |
48 | StringStruct(u'OriginalFilename', u'%(exe_name)s'), | |
49 | StringStruct(u'ProductName', u'%(name)s'), | |
50 | StringStruct(u'ProductVersion', u'%(ver_str)s')]) | |
51 | ]), | |
52 | VarFileInfo([VarStruct(u'Translation', [1033, 1252])]) | |
53 | ] | |
54 | )""" | |
55 | ||
56 | data = json.loads(os.environ['pyinstaller_data']) | |
57 | try: | |
58 | data = dict((k, v.encode('ascii') if isinstance(v, unicode) else v) | |
59 | for k, v in data.items()) | |
60 | except NameError: | |
61 | pass # Python 3, encode not needed. | |
62 | dist = pkg_resources.get_distribution(data['name']) | |
63 | ver_str = dist.version | |
64 | ||
65 | DEBUG = bool(data['debug']) | |
66 | NAME = data['long_name'] | |
67 | ||
68 | WIN = sys.platform in ['win32', 'cygwin'] | |
69 | OSX = sys.platform in ['darwin'] | |
70 | ||
71 | file_ext = '.exe' if WIN else '' | |
72 | ||
73 | if WIN: | |
74 | icon_ext = 'ico' | |
75 | elif OSX: | |
76 | icon_ext = 'icns' | |
77 | else: | |
78 | icon_ext = 'png' | |
79 | ICON = os.path.join('resources', '%s.%s' % (data['name'], icon_ext)) | |
80 | ||
81 | if not os.path.isfile(ICON): | |
82 | ICON = None | |
83 | ||
84 | # Generate scripts from entry_points. | |
85 | merge = [] | |
86 | entry_map = dist.get_entry_map() | |
87 | console_scripts = entry_map.get('console_scripts', {}) | |
88 | gui_scripts = entry_map.get('gui_scripts', {}) | |
89 | ||
90 | for ep in list(gui_scripts.values()) + list(console_scripts.values()): | |
91 | script_path = os.path.join(WORKPATH, ep.name + '-script.py') | |
92 | with open(script_path, 'w') as fh: | |
93 | fh.write("import %s\n" % ep.module_name) | |
94 | fh.write("%s.%s()\n" % (ep.module_name, '.'.join(ep.attrs))) | |
95 | merge.append( | |
96 | (Analysis([script_path], [dist.location], None, None, None, None), | |
97 | ep.name, ep.name + file_ext) | |
98 | ) | |
99 | ||
100 | ||
101 | MERGE(*merge) | |
102 | ||
103 | ||
104 | # Read version information on Windows. | |
105 | VERSION = None | |
106 | if WIN: | |
107 | VERSION = 'build/file_version_info.txt' | |
108 | ||
109 | global int_or_zero # Needed due to how this script is invoked | |
110 | ||
111 | def int_or_zero(v): | |
112 | try: | |
113 | return int(v) | |
114 | except ValueError: | |
115 | return 0 | |
116 | ||
117 | ver_tup = tuple(int_or_zero(v) for v in ver_str.split('.')) | |
118 | # Windows needs 4-tuple. | |
119 | if len(ver_tup) < 4: | |
120 | ver_tup += (0,) * (4-len(ver_tup)) | |
121 | elif len(ver_tup) > 4: | |
122 | ver_tup = ver_tup[:4] | |
123 | ||
124 | # Write version info. | |
125 | with open(VERSION, 'w') as f: | |
126 | f.write(VS_VERSION_INFO % { | |
127 | 'name': NAME, | |
128 | 'internal_name': data['name'], | |
129 | 'ver_tup': ver_tup, | |
130 | 'ver_str': ver_str, | |
131 | 'exe_name': NAME + file_ext | |
132 | }) | |
133 | ||
134 | pyzs = [PYZ(m[0].pure) for m in merge] | |
135 | ||
136 | exes = [] | |
137 | for (a, a_name, a_name_ext), pyz in zip(merge, pyzs): | |
138 | exe = EXE(pyz, | |
139 | a.scripts, | |
140 | exclude_binaries=True, | |
141 | name=a_name_ext, | |
142 | debug=DEBUG, | |
143 | strip=None, | |
144 | upx=True, | |
145 | console=DEBUG or a_name in console_scripts, | |
146 | append_pkg=not OSX, | |
147 | version=VERSION, | |
148 | icon=ICON) | |
149 | exes.append(exe) | |
150 | ||
151 | # Sign the executable | |
152 | if WIN: | |
153 | os.system("signtool.exe sign /t http://timestamp.verisign.com/scripts/timstamp.dll \"%s\"" % | |
154 | (exe.name)) | |
155 | ||
156 | collect = [] | |
157 | for (a, _, a_name), exe in zip(merge, exes): | |
158 | collect += [exe, a.binaries, a.zipfiles, a.datas] | |
159 | ||
160 | # DLLs, dylibs and executables should go here. | |
161 | collect.append([(fn[4:], fn, 'BINARY') for fn in glob('lib/*')]) | |
162 | ||
163 | coll = COLLECT(*collect, strip=None, upx=True, name=NAME) | |
164 | ||
165 | # Create .app for OSX | |
166 | if OSX: | |
167 | app = BUNDLE(coll, | |
168 | name="%s.app" % NAME, | |
169 | version=ver_str, | |
170 | icon=ICON) | |
171 | ||
172 | qt_conf = 'dist/%s.app/Contents/Resources/qt.conf' % NAME | |
173 | qt_conf_dir = os.path.dirname(qt_conf) | |
174 | try: | |
175 | os.makedirs(qt_conf_dir) | |
176 | except OSError as e: | |
177 | if not (e.errno == errno.EEXIST and os.path.isdir(qt_conf_dir)): | |
178 | raise | |
179 | with open(qt_conf, 'w') as f: | |
180 | f.write('[Path]\nPlugins = plugins') | |
181 | ||
182 | # Create Windows installer | |
183 | if WIN: | |
184 | installer_cfg = 'resources/win-installer.nsi' | |
185 | if os.path.isfile(installer_cfg): | |
186 | os.system('makensis.exe -D"VERSION=%s" %s' % (ver_str, installer_cfg)) | |
187 | installer = "dist/%s-%s-win.exe" % (data['name'], ver_str) | |
188 | os.system("signtool.exe sign /t http://timestamp.verisign.com/scripts/timstamp.dll \"%s\"" % | |
189 | (installer)) | |
190 | print("Installer created: %s" % installer) |
0 | # Copyright (c) 2013 Yubico AB | |
1 | # All rights reserved. | |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or | |
4 | # without modification, are permitted provided that the following | |
5 | # conditions are met: | |
6 | # | |
7 | # 1. Redistributions of source code must retain the above copyright | |
8 | # notice, this list of conditions and the following disclaimer. | |
9 | # 2. Redistributions in binary form must reproduce the above | |
10 | # copyright notice, this list of conditions and the following | |
11 | # disclaimer in the documentation and/or other materials provided | |
12 | # with the distribution. | |
13 | # | |
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
15 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
16 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
17 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
18 | # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
19 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
24 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | # POSSIBILITY OF SUCH DAMAGE. | |
26 | ||
27 | from __future__ import absolute_import | |
28 | ||
29 | from setuptools import Command | |
30 | from distutils.errors import DistutilsSetupError | |
31 | import os | |
32 | ||
33 | ||
34 | __dependencies__ = ['PySide'] | |
35 | __all__ = ['qt_resources'] | |
36 | ||
37 | ||
38 | class _qt_resources(Command): | |
39 | description = "convert file resources into code" | |
40 | user_options = [] | |
41 | boolean_options = [] | |
42 | _source = 'qt_resources' | |
43 | _target = '' | |
44 | ||
45 | def initialize_options(self): | |
46 | pass | |
47 | ||
48 | def finalize_options(self): | |
49 | self.cwd = os.getcwd() | |
50 | self.source = os.path.join(self.cwd, self._source) | |
51 | self.target = os.path.join(self.cwd, self._target) | |
52 | ||
53 | def _create_qrc(self): | |
54 | qrc = os.path.join(self.source, 'qt_resources.qrc') | |
55 | with open(qrc, 'w') as f: | |
56 | f.write('<RCC>\n<qresource>\n') | |
57 | for fname in os.listdir(self.source): | |
58 | f.write('<file>%s</file>\n' % fname) | |
59 | f.write('</qresource>\n</RCC>\n') | |
60 | return qrc | |
61 | ||
62 | def run(self): | |
63 | if os.getcwd() != self.cwd: | |
64 | raise DistutilsSetupError("Must be in package root!") | |
65 | ||
66 | qrc = self._create_qrc() | |
67 | self.execute(os.system, | |
68 | ('pyside-rcc "%s" -o "%s"' % (qrc, self.target),)) | |
69 | os.unlink(qrc) | |
70 | ||
71 | self.announce("QT resources compiled into %s" % self.target) | |
72 | ||
73 | ||
74 | def qt_resources(target, sourcedir='qt_resources'): | |
75 | target = target.replace('.', os.path.sep) | |
76 | if os.path.isdir(target): | |
77 | target = os.path.join(target, 'qt_resources.py') | |
78 | else: | |
79 | target += '.py' | |
80 | ||
81 | return type('qt_resources', (_qt_resources, object), { | |
82 | '_source': sourcedir, | |
83 | '_target': target | |
84 | }) |
30 | 30 | 'YubiKeyTimeout', |
31 | 31 | ] |
32 | 32 | |
33 | from yubico import __version__ | |
34 | import yubico_exception | |
33 | from .yubico_version import __version__ | |
34 | from .yubikey_base import YubiKeyError, YubiKeyTimeout, YubiKeyVersionError, YubiKeyCapabilities, YubiKey | |
35 | from .yubikey_usb_hid import YubiKeyUSBHID, YubiKeyHIDDevice, YubiKeyUSBHIDError | |
36 | from .yubikey_neo_usb_hid import YubiKeyNEO_USBHID | |
37 | from .yubikey_4_usb_hid import YubiKey4_USBHID | |
35 | 38 | |
36 | class YubiKeyError(yubico_exception.YubicoError): | |
37 | """ | |
38 | Exception raised concerning YubiKey operations. | |
39 | ||
40 | Attributes: | |
41 | reason -- explanation of the error | |
42 | """ | |
43 | def __init__(self, reason='no details'): | |
44 | yubico_exception.YubicoError.__init__(self, reason) | |
45 | ||
46 | class YubiKeyTimeout(YubiKeyError): | |
47 | """ | |
48 | Exception raised when a YubiKey operation timed out. | |
49 | ||
50 | Attributes: | |
51 | reason -- explanation of the error | |
52 | """ | |
53 | def __init__(self, reason='no details'): | |
54 | YubiKeyError.__init__(self, reason) | |
55 | ||
56 | class YubiKeyVersionError(YubiKeyError): | |
57 | """ | |
58 | Exception raised when the YubiKey is not capable of something requested. | |
59 | ||
60 | Attributes: | |
61 | reason -- explanation of the error | |
62 | """ | |
63 | def __init__(self, reason='no details'): | |
64 | YubiKeyError.__init__(self, reason) | |
65 | ||
66 | ||
67 | class YubiKeyCapabilities(): | |
68 | """ | |
69 | Class expressing the functionality of a YubiKey. | |
70 | ||
71 | This base class should be the superset of all sub-classes. | |
72 | ||
73 | In this base class, we lie and say 'yes' to all capabilities. | |
74 | ||
75 | If the base class is used (such as when creating a YubiKeyConfig() | |
76 | before getting a YubiKey()), errors must be handled at runtime | |
77 | (or later, when the user is unable to use the YubiKey). | |
78 | """ | |
79 | ||
80 | model = 'Unknown' | |
81 | version = (0, 0, 0,) | |
82 | version_num = 0x0 | |
83 | default_answer = True | |
84 | ||
85 | def __init__(self, model = None, version = None, default_answer = None): | |
86 | self.model = model | |
87 | if default_answer is not None: | |
88 | self.default_answer = default_answer | |
89 | if version is not None: | |
90 | self.version = version | |
91 | (major, minor, build,) = version | |
92 | # convert 2.1.3 to 0x00020103 | |
93 | self.version_num = (major << 24) | (minor << 16) | build | |
94 | return None | |
95 | ||
96 | def __repr__(self): | |
97 | return '<%s instance at %s: Device %s %s (default: %s)>' % ( | |
98 | self.__class__.__name__, | |
99 | hex(id(self)), | |
100 | self.model, | |
101 | self.version, | |
102 | self.default_answer, | |
103 | ) | |
104 | ||
105 | def have_yubico_OTP(self): | |
106 | return self.default_answer | |
107 | ||
108 | def have_OATH(self, mode): | |
109 | return self.default_answer | |
110 | ||
111 | def have_challenge_response(self, mode): | |
112 | return self.default_answer | |
113 | ||
114 | def have_serial_number(self): | |
115 | return self.default_answer | |
116 | ||
117 | def have_ticket_flag(self, flag): | |
118 | return self.default_answer | |
119 | ||
120 | def have_config_flag(self, flag): | |
121 | return self.default_answer | |
122 | ||
123 | def have_extended_flag(self, flag): | |
124 | return self.default_answer | |
125 | ||
126 | def have_extended_scan_code_mode(self): | |
127 | return self.default_answer | |
128 | ||
129 | def have_shifted_1_mode(self): | |
130 | return self.default_answer | |
131 | ||
132 | def have_nfc_ndef(self): | |
133 | return self.default_answer | |
134 | ||
135 | def have_configuration_slot(self): | |
136 | return self.default_answer | |
137 | ||
138 | class YubiKey(): | |
139 | """ | |
140 | Base class for accessing YubiKeys | |
141 | """ | |
142 | ||
143 | debug = None | |
144 | capabilities = None | |
145 | ||
146 | def __init__(self, debug, capabilities = None): | |
147 | self.debug = debug | |
148 | if capabilities is None: | |
149 | self.capabilities = YubiKeyCapabilities(default_answer = False) | |
150 | else: | |
151 | self.capabilities = capabilities | |
152 | return None | |
153 | ||
154 | def version(self): | |
155 | """ Get the connected YubiKey's version as a string. """ | |
156 | pass | |
157 | ||
158 | def serial(self, may_block=True): | |
159 | """ | |
160 | Get the connected YubiKey's serial number. | |
161 | ||
162 | Note that since version 2.?.? this requires the YubiKey to be | |
163 | configured with the extended flag SERIAL_API_VISIBLE. | |
164 | ||
165 | If the YubiKey is configured with SERIAL_BTN_VISIBLE set to True, | |
166 | it will start blinking and require a button press before revealing | |
167 | the serial number, with a 15 seconds timeout. Set `may_block' | |
168 | to False to abort if this is the case. | |
169 | """ | |
170 | pass | |
171 | ||
172 | def challenge(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): | |
173 | """ | |
174 | Get the response to a challenge from a connected YubiKey. | |
175 | ||
176 | `mode' is either 'HMAC' or 'OTP'. | |
177 | `slot' is 1 or 2. | |
178 | `variable' is only relevant for mode == HMAC. | |
179 | ||
180 | If variable is True, challenge will be padded such that the | |
181 | YubiKey will compute the HMAC as if there were no padding. | |
182 | If variable is False, challenge will always be NULL-padded | |
183 | to 64 bytes. | |
184 | ||
185 | The special case of no input will be HMACed by the YubiKey | |
186 | (in variable HMAC mode) as data = 0x00, length = 1. | |
187 | ||
188 | In mode 'OTP', the challenge should be exactly 6 bytes. The | |
189 | response will be a YubiKey "ticket" with the 6-byte challenge | |
190 | in the ticket.uid field. The rest of the "ticket" will contain | |
191 | timestamp and counter information, so two identical challenges | |
192 | will NOT result in the same responses. The response is | |
193 | decryptable using AES ECB if you have access to the AES key | |
194 | programmed into the YubiKey. | |
195 | """ | |
196 | pass | |
197 | ||
198 | def init_config(self): | |
199 | """ | |
200 | Return a YubiKey configuration object for this type of YubiKey. | |
201 | """ | |
202 | pass | |
203 | ||
204 | def write_config(self, cfg, slot): | |
205 | """ | |
206 | Configure a YubiKey using a configuration object. | |
207 | """ | |
208 | pass | |
209 | ||
210 | # Since YubiKeyUSBHID is a subclass of YubiKey (defined here above), | |
211 | # the import must be after the declaration of YubiKey. We also carefully | |
212 | # import only what we need to not get a circular import of modules. | |
213 | from yubikey_usb_hid import YubiKeyUSBHID, YubiKeyUSBHIDError | |
214 | from yubikey_neo_usb_hid import YubiKeyNEO_USBHID, YubiKeyNEO_USBHIDError | |
215 | 39 | |
216 | 40 | def find_key(debug=False, skip=0): |
217 | 41 | """ |
225 | 49 | debug -- True or False |
226 | 50 | """ |
227 | 51 | try: |
228 | YK = YubiKeyUSBHID(debug=debug, skip=skip) | |
229 | if (YK.version_num() >= (2, 1, 4,)) and \ | |
230 | (YK.version_num() <= (2, 1, 9,)): | |
231 | # YubiKey NEO BETA, re-detect | |
232 | YK2 = YubiKeyNEO_USBHID(debug=debug, skip=skip) | |
233 | if YK2.version_num() == YK.version_num(): | |
234 | # XXX not guaranteed to be the same one I guess | |
235 | return YK2 | |
236 | raise YubiKeyError('Found YubiKey NEO BETA, but failed on rescan.') | |
237 | return YK | |
52 | hid_device = YubiKeyHIDDevice(debug, skip) | |
53 | yk_version = hid_device.status().ykver() | |
54 | if (2, 1, 4) <= yk_version <= (2, 1, 9): | |
55 | return YubiKeyNEO_USBHID(debug, skip, hid_device) | |
56 | if yk_version < (3, 0, 0): | |
57 | return YubiKeyUSBHID(debug, skip, hid_device) | |
58 | if yk_version < (4, 0, 0): | |
59 | return YubiKeyNEO_USBHID(debug, skip, hid_device) | |
60 | return YubiKey4_USBHID(debug, skip, hid_device) | |
238 | 61 | except YubiKeyUSBHIDError as inst: |
239 | 62 | if 'No USB YubiKey found' in str(inst): |
240 | 63 | # generalize this error |
0 | """ | |
1 | module for accessing a USB HID YubiKey 4 | |
2 | """ | |
3 | ||
4 | # Copyright (c) 2012 Yubico AB | |
5 | # See the file COPYING for licence statement. | |
6 | ||
7 | __all__ = [ | |
8 | # constants | |
9 | # functions | |
10 | # classes | |
11 | 'YubiKey4_USBHID', | |
12 | 'YubiKey4_USBHIDError' | |
13 | ] | |
14 | ||
15 | from .yubikey_defs import SLOT, MODE, YK4_CAPA | |
16 | from . import yubikey_frame | |
17 | from . import yubikey_base | |
18 | from . import yubico_exception | |
19 | from . import yubico_util | |
20 | from . import yubikey_neo_usb_hid | |
21 | ||
22 | MODE_CAPABILITIES = { # Required capabilities to support USB mode. | |
23 | MODE.OTP : [YK4_CAPA.OTP], | |
24 | MODE.CCID : [YK4_CAPA.CCID], | |
25 | MODE.OTP_CCID : [YK4_CAPA.OTP, YK4_CAPA.CCID], | |
26 | MODE.U2F : [YK4_CAPA.U2F], | |
27 | MODE.OTP_U2F : [YK4_CAPA.OTP, YK4_CAPA.U2F], | |
28 | MODE.U2F_CCID : [YK4_CAPA.U2F, YK4_CAPA.CCID], | |
29 | MODE.OTP_U2F_CCID : [YK4_CAPA.OTP, YK4_CAPA.U2F, YK4_CAPA.CCID] | |
30 | } | |
31 | ||
32 | ||
33 | class YubiKey4_USBHIDError(yubico_exception.YubicoError): | |
34 | """ Exception raised for errors with the YK4 USB HID communication. """ | |
35 | ||
36 | ||
37 | class YubiKey4_USBHIDCapabilities(yubikey_neo_usb_hid.YubiKeyNEO_USBHIDCapabilities): | |
38 | """ | |
39 | Capabilities of current YubiKey 4. | |
40 | """ | |
41 | _yk4_capa = 0 | |
42 | ||
43 | def _set_yk4_capa(self, yk4_capa): | |
44 | int_val = 0 | |
45 | for b in yk4_capa: | |
46 | int_val <<= 8 | |
47 | int_val += yubico_util.ord_byte(b) | |
48 | self._yk4_capa = int_val | |
49 | ||
50 | def have_nfc_ndef(self, slot=1): | |
51 | return False | |
52 | ||
53 | def have_usb_mode(self, mode): | |
54 | mode &= ~MODE.FLAG_EJECT # Mask away eject flag | |
55 | if self.version < (4, 1, 0): # YK Plus is locked in OTP+U2F | |
56 | return mode == MODE.OTP_U2F | |
57 | for cap_req in MODE_CAPABILITIES.get(mode, [0]): | |
58 | if not self.have_capability(cap_req): | |
59 | return False | |
60 | return True | |
61 | ||
62 | def have_capabilities(self): | |
63 | return self.version >= (4, 1, 0) | |
64 | ||
65 | def have_capability(self, capability): | |
66 | return self._yk4_capa & capability != 0 | |
67 | ||
68 | ||
69 | class YubiKey4_USBHID(yubikey_neo_usb_hid.YubiKeyNEO_USBHID): | |
70 | """ | |
71 | Class for accessing a YubiKey 4 over USB HID. | |
72 | ||
73 | """ | |
74 | ||
75 | model = 'YubiKey 4' | |
76 | description = 'YubiKey 4' | |
77 | _capabilities_cls = YubiKey4_USBHIDCapabilities | |
78 | ||
79 | def __init__(self, debug=False, skip=0, hid_device=None): | |
80 | """ | |
81 | Find and connect to a YubiKey 4 (USB HID). | |
82 | ||
83 | Attributes : | |
84 | skip -- number of YubiKeys to skip | |
85 | debug -- True or False | |
86 | """ | |
87 | super(YubiKey4_USBHID, self).__init__(debug, skip, hid_device) | |
88 | if self.version_num() < (4, 0, 0): | |
89 | raise yubikey_base.YubiKeyVersionError( | |
90 | "Incorrect version for YubiKey 4 %s" % self.version()) | |
91 | elif self.version_num() < (4, 1, 0): | |
92 | self.description = 'YubiKey Plus' | |
93 | elif self.version_num() < (4, 2, 0): | |
94 | self.description = 'YubiKey Edge/Edge-n' | |
95 | ||
96 | if self.capabilities.have_capabilities(): | |
97 | data = yubico_util.tlv_parse(self._read_capabilities()) | |
98 | self.capabilities._set_yk4_capa(data.get(YK4_CAPA.TAG.CAPA, b'')) | |
99 | ||
100 | def _read_capabilities(self): | |
101 | """ Read the capabilities list from a YubiKey >= 4.0.0 """ | |
102 | ||
103 | frame = yubikey_frame.YubiKeyFrame(command=SLOT.YK4_CAPABILITIES) | |
104 | self._device._write(frame) | |
105 | response = self._device._read_response() | |
106 | r_len = ord(response[0]) | |
107 | ||
108 | # 1 byte length, 2 byte CRC. | |
109 | if not yubico_util.validate_crc16(response[:r_len+3]): | |
110 | raise YubiKey4_USBHIDError("Read from device failed CRC check") | |
111 | ||
112 | return response[1:r_len+1] |
0 | """ | |
1 | module for Yubikey base classes | |
2 | """ | |
3 | # Copyright (c) 2010, 2011, 2012 Yubico AB | |
4 | # See the file COPYING for licence statement. | |
5 | ||
6 | from .yubico_version import __version__ | |
7 | from . import yubico_exception | |
8 | ||
9 | class YubiKeyError(yubico_exception.YubicoError): | |
10 | """ | |
11 | Exception raised concerning YubiKey operations. | |
12 | ||
13 | Attributes: | |
14 | reason -- explanation of the error | |
15 | """ | |
16 | def __init__(self, reason='no details'): | |
17 | super(YubiKeyError, self).__init__(reason) | |
18 | ||
19 | class YubiKeyTimeout(YubiKeyError): | |
20 | """ | |
21 | Exception raised when a YubiKey operation timed out. | |
22 | ||
23 | Attributes: | |
24 | reason -- explanation of the error | |
25 | """ | |
26 | def __init__(self, reason='no details'): | |
27 | super(YubiKeyTimeout, self).__init__(reason) | |
28 | ||
29 | class YubiKeyVersionError(YubiKeyError): | |
30 | """ | |
31 | Exception raised when the YubiKey is not capable of something requested. | |
32 | ||
33 | Attributes: | |
34 | reason -- explanation of the error | |
35 | """ | |
36 | def __init__(self, reason='no details'): | |
37 | super(YubiKeyVersionError, self).__init__(reason) | |
38 | ||
39 | ||
40 | class YubiKeyCapabilities(object): | |
41 | """ | |
42 | Class expressing the functionality of a YubiKey. | |
43 | ||
44 | This base class should be the superset of all sub-classes. | |
45 | ||
46 | In this base class, we lie and say 'yes' to all capabilities. | |
47 | ||
48 | If the base class is used (such as when creating a YubiKeyConfig() | |
49 | before getting a YubiKey()), errors must be handled at runtime | |
50 | (or later, when the user is unable to use the YubiKey). | |
51 | """ | |
52 | ||
53 | model = 'Unknown' | |
54 | version = (0, 0, 0,) | |
55 | version_num = 0x0 | |
56 | default_answer = True | |
57 | ||
58 | def __init__(self, model = None, version = None, default_answer = None): | |
59 | self.model = model | |
60 | if default_answer is not None: | |
61 | self.default_answer = default_answer | |
62 | if version is not None: | |
63 | self.version = version | |
64 | (major, minor, build,) = version | |
65 | # convert 2.1.3 to 0x00020103 | |
66 | self.version_num = (major << 24) | (minor << 16) | build | |
67 | return None | |
68 | ||
69 | def __repr__(self): | |
70 | return '<%s instance at %s: Device %s %s (default: %s)>' % ( | |
71 | self.__class__.__name__, | |
72 | hex(id(self)), | |
73 | self.model, | |
74 | self.version, | |
75 | self.default_answer, | |
76 | ) | |
77 | ||
78 | def have_yubico_OTP(self): | |
79 | return self.default_answer | |
80 | ||
81 | def have_OATH(self, mode): | |
82 | return self.default_answer | |
83 | ||
84 | def have_challenge_response(self, mode): | |
85 | return self.default_answer | |
86 | ||
87 | def have_serial_number(self): | |
88 | return self.default_answer | |
89 | ||
90 | def have_ticket_flag(self, flag): | |
91 | return self.default_answer | |
92 | ||
93 | def have_config_flag(self, flag): | |
94 | return self.default_answer | |
95 | ||
96 | def have_extended_flag(self, flag): | |
97 | return self.default_answer | |
98 | ||
99 | def have_extended_scan_code_mode(self): | |
100 | return self.default_answer | |
101 | ||
102 | def have_shifted_1_mode(self): | |
103 | return self.default_answer | |
104 | ||
105 | def have_nfc_ndef(self, slot=1): | |
106 | return self.default_answer | |
107 | ||
108 | def have_configuration_slot(self): | |
109 | return self.default_answer | |
110 | ||
111 | def have_device_config(self): | |
112 | return self.default_answer | |
113 | ||
114 | def have_usb_mode(self, mode): | |
115 | return self.default_answer | |
116 | ||
117 | def have_scanmap(self): | |
118 | return self.default_answer | |
119 | ||
120 | def have_capabilities(self): | |
121 | return self.default_answer | |
122 | ||
123 | def have_capability(self, capability): | |
124 | return self.default_answer | |
125 | ||
126 | ||
127 | class YubiKey(object): | |
128 | """ | |
129 | Base class for accessing YubiKeys | |
130 | """ | |
131 | ||
132 | debug = None | |
133 | capabilities = None | |
134 | ||
135 | def __init__(self, debug, capabilities = None): | |
136 | self.debug = debug | |
137 | if capabilities is None: | |
138 | self.capabilities = YubiKeyCapabilities(default_answer = False) | |
139 | else: | |
140 | self.capabilities = capabilities | |
141 | return None | |
142 | ||
143 | def version(self): | |
144 | """ Get the connected YubiKey's version as a string. """ | |
145 | pass | |
146 | ||
147 | def serial(self, may_block=True): | |
148 | """ | |
149 | Get the connected YubiKey's serial number. | |
150 | ||
151 | Note that since version 2.?.? this requires the YubiKey to be | |
152 | configured with the extended flag SERIAL_API_VISIBLE. | |
153 | ||
154 | If the YubiKey is configured with SERIAL_BTN_VISIBLE set to True, | |
155 | it will start blinking and require a button press before revealing | |
156 | the serial number, with a 15 seconds timeout. Set `may_block' | |
157 | to False to abort if this is the case. | |
158 | """ | |
159 | pass | |
160 | ||
161 | def challenge(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): | |
162 | """ | |
163 | Get the response to a challenge from a connected YubiKey. | |
164 | ||
165 | `mode' is either 'HMAC' or 'OTP'. | |
166 | `slot' is 1 or 2. | |
167 | `variable' is only relevant for mode == HMAC. | |
168 | ||
169 | If variable is True, challenge will be padded such that the | |
170 | YubiKey will compute the HMAC as if there were no padding. | |
171 | If variable is False, challenge will always be NULL-padded | |
172 | to 64 bytes. | |
173 | ||
174 | The special case of no input will be HMACed by the YubiKey | |
175 | (in variable HMAC mode) as data = 0x00, length = 1. | |
176 | ||
177 | In mode 'OTP', the challenge should be exactly 6 bytes. The | |
178 | response will be a YubiKey "ticket" with the 6-byte challenge | |
179 | in the ticket.uid field. The rest of the "ticket" will contain | |
180 | timestamp and counter information, so two identical challenges | |
181 | will NOT result in the same responses. The response is | |
182 | decryptable using AES ECB if you have access to the AES key | |
183 | programmed into the YubiKey. | |
184 | """ | |
185 | pass | |
186 | ||
187 | def init_config(self): | |
188 | """ | |
189 | Return a YubiKey configuration object for this type of YubiKey. | |
190 | """ | |
191 | pass | |
192 | ||
193 | def write_config(self, cfg, slot): | |
194 | """ | |
195 | Configure a YubiKey using a configuration object. | |
196 | """ | |
197 | pass |
14 | 14 | 'YubiKeyConfig', |
15 | 15 | ] |
16 | 16 | |
17 | from yubico import __version__ | |
18 | ||
17 | from .yubico_version import __version__ | |
18 | ||
19 | import sys | |
19 | 20 | import struct |
20 | 21 | import binascii |
21 | import yubico_util | |
22 | import yubikey_defs | |
23 | import yubikey_frame | |
24 | import yubico_exception | |
25 | import yubikey_config_util | |
26 | from yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag | |
27 | import yubikey | |
22 | from . import yubico_util | |
23 | from . import yubikey_defs | |
24 | from . import yubikey_frame | |
25 | from . import yubico_exception | |
26 | from . import yubikey_base | |
27 | from .yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag | |
28 | from .yubikey_defs import SLOT | |
29 | ||
30 | ||
31 | def command2str(num): | |
32 | """ Turn command number into name """ | |
33 | for attr in SLOT.__dict__.keys(): | |
34 | if not attr.startswith('_') and attr == attr.upper(): | |
35 | if getattr(SLOT, attr) == num: | |
36 | return 'SLOT_%s' % attr | |
37 | ||
38 | return "0x%02x" % (num) | |
39 | ||
40 | ### BEGIN DEPRECATED | |
41 | ### These are here for backwards compatibility, DO NOT USE! | |
42 | SLOT_CONFIG = SLOT.CONFIG | |
43 | SLOT_CONFIG2 = SLOT.CONFIG2 | |
44 | SLOT_UPDATE1 = SLOT.UPDATE1 | |
45 | SLOT_UPDATE2 = SLOT.UPDATE2 | |
46 | SLOT_SWAP = SLOT.SWAP | |
47 | ### END DEPRECATED | |
48 | ||
28 | 49 | |
29 | 50 | TicketFlags = [ |
30 | 51 | YubiKeyTicketFlag('TAB_FIRST', 0x01, min_ykver=(1, 0), doc='Send TAB before first part'), |
78 | 99 | YubiKeyExtendedFlag('DORMANT', 0x40, min_ykver=(2, 3), doc='Dormant configuration (can be woken up and flag removed = requires update flag)'), |
79 | 100 | ] |
80 | 101 | |
81 | SLOT_CONFIG = 0x01 # First (default / V1) configuration | |
82 | SLOT_CONFIG2 = 0x03 # Second (V2) configuration | |
83 | SLOT_UPDATE1 = 0x04 # Update slot 1 | |
84 | SLOT_UPDATE2 = 0x05 # Update slot 2 | |
85 | SLOT_SWAP = 0x06 # Swap slot 1 and 2 | |
86 | ||
87 | def command2str(num): | |
88 | """ Turn command number into name """ | |
89 | known = {0x01: "SLOT_CONFIG", | |
90 | 0x03: "SLOT_CONFIG2", | |
91 | 0x04: "SLOT_UPDATE1", | |
92 | 0x05: "SLOT_UPDATE2", | |
93 | 0x06: "SLOT_SWAP", | |
94 | } | |
95 | if num in known: | |
96 | return known[num] | |
97 | return "0x%02x" % (num) | |
98 | 102 | |
99 | 103 | class YubiKeyConfigError(yubico_exception.YubicoError): |
100 | 104 | """ |
101 | 105 | Exception raised for YubiKey configuration errors. |
102 | 106 | """ |
103 | 107 | |
104 | class YubiKeyConfig(): | |
108 | ||
109 | class YubiKeyConfig(object): | |
105 | 110 | """ |
106 | 111 | Base class for configuration of all current types of YubiKeys. |
107 | 112 | """ |
108 | def __init__(self, ykver=None, capabilities=None, update=False, swap=False): | |
113 | def __init__(self, ykver=None, capabilities=None, update=False, swap=False, | |
114 | zap=False): | |
109 | 115 | """ |
110 | 116 | `ykver' is a tuple (major, minor) with the version number of the key |
111 | 117 | you are planning to apply this configuration to. Not mandated, but |
121 | 127 | |
122 | 128 | YubiKey >= 2.3 also supports swapping the configurations, making |
123 | 129 | slot 1 be slot 2 and vice versa. Set swap=True for this. |
130 | ||
131 | YubiKeys support deleting a configuration, setting it in an | |
132 | unprogrammed state. Set zap=True for this. | |
124 | 133 | """ |
125 | 134 | if capabilities is None: |
126 | self.capabilities = yubikey.YubiKeyCapabilities(default_answer = True) | |
135 | self.capabilities = yubikey_base.YubiKeyCapabilities(default_answer = True) | |
127 | 136 | else: |
128 | 137 | self.capabilities = capabilities |
129 | 138 | |
131 | 140 | self.yk_req_version = (0, 0) |
132 | 141 | self.ykver = ykver |
133 | 142 | |
134 | self.fixed = '' | |
135 | self.uid = '' | |
136 | self.key = '' | |
137 | self.access_code = '' | |
143 | self.fixed = b'' | |
144 | self.uid = b'' | |
145 | self.key = b'' | |
146 | self.access_code = b'' | |
138 | 147 | |
139 | 148 | self.ticket_flags = YubiKeyConfigBits(0x0) |
140 | 149 | self.config_flags = YubiKeyConfigBits(0x0) |
141 | 150 | self.extended_flags = YubiKeyConfigBits(0x0) |
142 | 151 | |
143 | self.unlock_code = '' | |
152 | self.unlock_code = b'' | |
144 | 153 | self._mode = '' |
145 | 154 | if update or swap: |
146 | 155 | self._require_version(major=2, minor=3) |
147 | 156 | self._update_config = update |
148 | 157 | self._swap_slots = swap |
158 | self._zap = zap | |
149 | 159 | |
150 | 160 | return None |
151 | 161 | |
152 | 162 | def __repr__(self): |
153 | return '<%s instance at %s: mode %s, v=%s/%s, lf=%i, lu=%i, lk=%i, lac=%i, tf=%x, cf=%x, ef=%x, lu=%i, up=%s, sw=%s>' % ( | |
163 | return '<%s instance at %s: mode %s, v=%s/%s, lf=%i, lu=%i, lk=%i, lac=%i, tf=%x, cf=%x, ef=%x, lu=%i, up=%s, sw=%s, z=%s>' % ( | |
154 | 164 | self.__class__.__name__, |
155 | 165 | hex(id(self)), |
156 | 166 | self._mode, |
165 | 175 | len(self.unlock_code), |
166 | 176 | self._update_config, |
167 | 177 | self._swap_slots, |
178 | self._zap | |
168 | 179 | ) |
169 | 180 | |
170 | 181 | def version_required(self): |
246 | 257 | """ |
247 | 258 | Access code to allow re-programming of your YubiKey. |
248 | 259 | |
249 | Supply data as either a raw string, or a hexlified string prefixed by 'h:'. | |
260 | Supply data as either a raw bytestring, or a hexlified bytestring prefixed by 'h:'. | |
250 | 261 | The result, after any hex decoding, must be 6 bytes. |
251 | 262 | """ |
252 | if data.startswith('h:'): | |
263 | if data.startswith(b'h:'): | |
253 | 264 | new = binascii.unhexlify(data[2:]) |
254 | 265 | else: |
255 | 266 | new = data |
269 | 280 | Supply data as either a raw string, or a hexlified string prefixed by 'h:'. |
270 | 281 | The result, after any hex decoding, must be 6 bytes. |
271 | 282 | """ |
272 | if data.startswith('h:'): | |
283 | if data.startswith(b'h:'): | |
273 | 284 | new = binascii.unhexlify(data[2:]) |
274 | 285 | else: |
275 | 286 | new = data |
283 | 294 | Set the YubiKey up for standard OTP validation. |
284 | 295 | """ |
285 | 296 | if not self.capabilities.have_yubico_OTP(): |
286 | raise yubikey.YubiKeyVersionError('Yubico OTP not available in %s version %d.%d' \ | |
287 | % (self.capabilities.model, self.ykver[0], self.ykver[1])) | |
288 | if private_uid.startswith('h:'): | |
297 | raise yubikey_base.YubiKeyVersionError('Yubico OTP not available in %s version %d.%d' \ | |
298 | % (self.capabilities.model, self.ykver[0], self.ykver[1])) | |
299 | if private_uid.startswith(b'h:'): | |
289 | 300 | private_uid = binascii.unhexlify(private_uid[2:]) |
290 | 301 | if len(private_uid) != yubikey_defs.UID_SIZE: |
291 | 302 | raise yubico_exception.InputError('Private UID must be %i bytes' % (yubikey_defs.UID_SIZE)) |
301 | 312 | Requires YubiKey 2.1. |
302 | 313 | """ |
303 | 314 | if not self.capabilities.have_OATH('HOTP'): |
304 | raise yubikey.YubiKeyVersionError('OATH HOTP not available in %s version %d.%d' \ | |
305 | % (self.capabilities.model, self.ykver[0], self.ykver[1])) | |
315 | raise yubikey_base.YubiKeyVersionError('OATH HOTP not available in %s version %d.%d' \ | |
316 | % (self.capabilities.model, self.ykver[0], self.ykver[1])) | |
306 | 317 | if digits != 6 and digits != 8: |
307 | 318 | raise yubico_exception.InputError('OATH-HOTP digits must be 6 or 8') |
308 | 319 | |
312 | 323 | self.config_flag('OATH_HOTP8', True) |
313 | 324 | if omp or tt or mui: |
314 | 325 | decoded_mui = self._decode_input_string(mui) |
315 | fixed = chr(omp) + chr(tt) + decoded_mui | |
326 | fixed = yubico_util.chr_byte(omp) + yubico_util.chr_byte(tt) + decoded_mui | |
316 | 327 | self.fixed_string(fixed) |
317 | 328 | if factor_seed: |
318 | 329 | self.uid = self.uid + struct.pack('<H', factor_seed) |
333 | 344 | if not type.upper() in ['HMAC', 'OTP']: |
334 | 345 | raise yubico_exception.InputError('Invalid \'type\' (%s)' % type) |
335 | 346 | if not self.capabilities.have_challenge_response(type.upper()): |
336 | raise yubikey.YubiKeyVersionError('%s Challenge-Response not available in %s version %d.%d' \ | |
337 | % (type.upper(), self.capabilities.model, \ | |
338 | self.ykver[0], self.ykver[1])) | |
347 | raise yubikey_base.YubiKeyVersionError('%s Challenge-Response not available in %s version %d.%d' \ | |
348 | % (type.upper(), self.capabilities.model, \ | |
349 | self.ykver[0], self.ykver[1])) | |
339 | 350 | self._change_mode('CHAL_RESP', major=2, minor=2) |
340 | 351 | if type.upper() == 'HMAC': |
341 | 352 | self.config_flag('CHAL_HMAC', True) |
357 | 368 | flag = _get_flag(which, TicketFlags) |
358 | 369 | if flag: |
359 | 370 | if not self.capabilities.have_ticket_flag(flag): |
360 | raise yubikey.YubiKeyVersionError('Ticket flag %s requires %s, and this is %s %d.%d' | |
361 | % (which, flag.req_string(self.capabilities.model), \ | |
371 | raise yubikey_base.YubiKeyVersionError('Ticket flag %s requires %s, and this is %s %d.%d' | |
372 | % (which, flag.req_string(self.capabilities.model), \ | |
362 | 373 | self.capabilities.model, self.ykver[0], self.ykver[1])) |
363 | 374 | req_major, req_minor = flag.req_version() |
364 | 375 | self._require_version(major=req_major, minor=req_minor) |
380 | 391 | flag = _get_flag(which, ConfigFlags) |
381 | 392 | if flag: |
382 | 393 | if not self.capabilities.have_config_flag(flag): |
383 | raise yubikey.YubiKeyVersionError('Config flag %s requires %s, and this is %s %d.%d' | |
384 | % (which, flag.req_string(self.capabilities.model), \ | |
394 | raise yubikey_base.YubiKeyVersionError('Config flag %s requires %s, and this is %s %d.%d' | |
395 | % (which, flag.req_string(self.capabilities.model), \ | |
385 | 396 | self.capabilities.model, self.ykver[0], self.ykver[1])) |
386 | 397 | req_major, req_minor = flag.req_version() |
387 | 398 | self._require_version(major=req_major, minor=req_minor) |
403 | 414 | flag = _get_flag(which, ExtendedFlags) |
404 | 415 | if flag: |
405 | 416 | if not self.capabilities.have_extended_flag(flag): |
406 | raise yubikey.YubiKeyVersionError('Extended flag %s requires %s, and this is %s %d.%d' | |
407 | % (which, flag.req_string(self.capabilities.model), \ | |
417 | raise yubikey_base.YubiKeyVersionError('Extended flag %s requires %s, and this is %s %d.%d' | |
418 | % (which, flag.req_string(self.capabilities.model), \ | |
408 | 419 | self.capabilities.model, self.ykver[0], self.ykver[1])) |
409 | 420 | req_major, req_minor = flag.req_version() |
410 | 421 | self._require_version(major=req_major, minor=req_minor) |
418 | 429 | |
419 | 430 | def to_string(self): |
420 | 431 | """ |
421 | Return the current configuration as a string (always 64 bytes). | |
432 | Return the current configuration as a bytestring (always 64 bytes). | |
422 | 433 | """ |
423 | 434 | #define UID_SIZE 6 /* Size of secret ID field */ |
424 | 435 | #define FIXED_SIZE 16 /* Max size of fixed field */ |
462 | 473 | Return the current configuration as a YubiKeyFrame object. |
463 | 474 | """ |
464 | 475 | data = self.to_string() |
465 | payload = data.ljust(64, chr(0x0)) | |
476 | payload = data.ljust(64, yubico_util.chr_byte(0x0)) | |
466 | 477 | if slot is 1: |
467 | 478 | if self._update_config: |
468 | command = SLOT_UPDATE1 | |
479 | command = SLOT.UPDATE1 | |
469 | 480 | else: |
470 | command = SLOT_CONFIG | |
481 | command = SLOT.CONFIG | |
471 | 482 | elif slot is 2: |
472 | 483 | if self._update_config: |
473 | command = SLOT_UPDATE2 | |
484 | command = SLOT.UPDATE2 | |
474 | 485 | else: |
475 | command = SLOT_CONFIG2 | |
486 | command = SLOT.CONFIG2 | |
476 | 487 | else: |
477 | 488 | assert() |
478 | 489 | |
479 | 490 | if self._swap_slots: |
480 | command = SLOT_SWAP | |
491 | command = SLOT.SWAP | |
492 | ||
493 | if self._zap: | |
494 | payload = b'' | |
481 | 495 | |
482 | 496 | return yubikey_frame.YubiKeyFrame(command=command, payload=payload) |
483 | 497 | |
485 | 499 | """ Update the minimum version of YubiKey this configuration can be applied to. """ |
486 | 500 | new_ver = (major, minor) |
487 | 501 | if self.ykver and new_ver > self.ykver: |
488 | raise yubikey.YubiKeyVersionError('Configuration requires YubiKey %d.%d, and this is %d.%d' | |
502 | raise yubikey_base.YubiKeyVersionError('Configuration requires YubiKey %d.%d, and this is %d.%d' | |
489 | 503 | % (major, minor, self.ykver[0], self.ykver[1])) |
490 | 504 | if new_ver > self.yk_req_version: |
491 | 505 | self.yk_req_version = new_ver |
492 | 506 | |
493 | 507 | def _decode_input_string(self, data): |
494 | if data.startswith('m:'): | |
495 | data = 'h:' + yubico_util.modhex_decode(data[2:]) | |
496 | if data.startswith('h:'): | |
508 | if sys.version_info >= (3, 0) and isinstance(data, str): | |
509 | data = data.encode('ascii') | |
510 | if data.startswith(b'm:'): | |
511 | data = b'h:' + yubico_util.modhex_decode(data[2:]) | |
512 | if data.startswith(b'h:'): | |
497 | 513 | return(binascii.unhexlify(data[2:])) |
498 | 514 | else: |
499 | 515 | return(data) |
516 | 532 | """ |
517 | 533 | Set a 20 bytes key. This is used in CHAL_HMAC and OATH_HOTP mode. |
518 | 534 | |
519 | Supply data as either a raw string, or a hexlified string prefixed by 'h:'. | |
535 | Supply data as either a raw bytestring, or a hexlified bytestring prefixed by 'h:'. | |
520 | 536 | The result, after any hex decoding, must be 20 bytes. |
521 | 537 | """ |
522 | if data.startswith('h:'): | |
538 | if data.startswith(b'h:'): | |
523 | 539 | new = binascii.unhexlify(data[2:]) |
524 | 540 | else: |
525 | 541 | new = data |
528 | 544 | self.uid = new[16:] |
529 | 545 | else: |
530 | 546 | raise yubico_exception.InputError('HMAC key must be exactly 20 bytes') |
547 | ||
531 | 548 | |
532 | 549 | def _get_flag(which, flags): |
533 | 550 | """ Find 'which' entry in 'flags'. """ |
13 | 13 | 'YubiKeyTicketFlag', |
14 | 14 | ] |
15 | 15 | |
16 | class YubiKeyFlag(): | |
16 | ||
17 | class YubiKeyFlag(object): | |
17 | 18 | """ |
18 | 19 | A flag value, and associated metadata. |
19 | 20 | """ |
20 | 21 | |
21 | def __init__(self, key, value, doc=None, min_ykver=(0, 0), max_ykver=None, models=['YubiKey', 'YubiKey NEO']): | |
22 | def __init__(self, key, value, doc=None, min_ykver=(0, 0), max_ykver=None, models=['YubiKey', 'YubiKey NEO', 'YubiKey 4']): | |
22 | 23 | """ |
23 | 24 | Metadata about a ticket/config/extended flag bit. |
24 | 25 | |
91 | 92 | else: |
92 | 93 | return version >= self.min_ykver |
93 | 94 | |
95 | ||
94 | 96 | class YubiKeyTicketFlag(YubiKeyFlag): |
95 | 97 | """ |
96 | 98 | A ticket flag value, and associated metadata. |
97 | 99 | """ |
100 | ||
98 | 101 | |
99 | 102 | class YubiKeyConfigFlag(YubiKeyFlag): |
100 | 103 | """ |
106 | 109 | assert() |
107 | 110 | self.mode = mode |
108 | 111 | |
109 | YubiKeyFlag.__init__(self, key, value, doc=doc, min_ykver=min_ykver, max_ykver=max_ykver) | |
112 | super(YubiKeyConfigFlag, self).__init__(key, value, doc=doc, min_ykver=min_ykver, max_ykver=max_ykver) | |
113 | ||
110 | 114 | |
111 | 115 | class YubiKeyExtendedFlag(YubiKeyFlag): |
112 | 116 | """ |
118 | 122 | assert() |
119 | 123 | self.mode = mode |
120 | 124 | |
121 | YubiKeyFlag.__init__(self, key, value, doc=doc, min_ykver=min_ykver, max_ykver=max_ykver) | |
125 | super(YubiKeyExtendedFlag, self).__init__(key, value, doc=doc, min_ykver=min_ykver, max_ykver=max_ykver) | |
122 | 126 | |
123 | class YubiKeyConfigBits(): | |
127 | ||
128 | class YubiKeyConfigBits(object): | |
124 | 129 | """ |
125 | 130 | Class to hold bit values for configuration. |
126 | 131 | """ |
13 | 13 | 'SHA1_DIGEST_SIZE', |
14 | 14 | 'OTP_CHALRESP_SIZE', |
15 | 15 | 'UID_SIZE', |
16 | 'YUBICO_VID', | |
16 | 17 | # functions |
17 | 18 | # classes |
19 | 'SLOT', | |
20 | 'MODE', | |
21 | 'PID', | |
22 | 'YK4_CAPA' | |
18 | 23 | ] |
19 | 24 | |
20 | from yubico import __version__ | |
25 | from .yubico_version import __version__ | |
21 | 26 | |
22 | 27 | # Yubikey Low level interface #2.3 |
23 | 28 | RESP_TIMEOUT_WAIT_MASK = 0x1f # Mask to get timeout value |
30 | 35 | OTP_CHALRESP_SIZE = 16 # Number of bytes returned for an Yubico-OTP challenge (not from ykdef.h) |
31 | 36 | |
32 | 37 | UID_SIZE = 6 # Size of secret ID field |
38 | ||
39 | ||
40 | class SLOT(object): | |
41 | """Slot entries""" | |
42 | CONFIG = 0x01 # First (default / V1) configuration | |
43 | CONFIG2 = 0x03 # Second (V2) configuration | |
44 | ||
45 | UPDATE1 = 0x04 # Update slot 1 | |
46 | UPDATE2 = 0x05 # Update slot 2 | |
47 | SWAP = 0x06 # Swap slot 1 and 2 | |
48 | ||
49 | NDEF = 0x08 # Write NDEF record | |
50 | NDEF2 = 0x09 # Write NDEF record for slot 2 | |
51 | ||
52 | DEVICE_SERIAL = 0x10 # Device serial number | |
53 | DEVICE_CONFIG = 0x11 # Write device configuration record | |
54 | SCAN_MAP = 0x12 # Write scancode map | |
55 | YK4_CAPABILITIES = 0x13 # Read YK4 capabilities list | |
56 | ||
57 | CHAL_OTP1 = 0x20 # Write 6 byte challenge to slot 1, get Yubico OTP response | |
58 | CHAL_OTP2 = 0x28 # Write 6 byte challenge to slot 2, get Yubico OTP response | |
59 | ||
60 | CHAL_HMAC1 = 0x30 # Write 64 byte challenge to slot 1, get HMAC-SHA1 response | |
61 | CHAL_HMAC2 = 0x38 # Write 64 byte challenge to slot 2, get HMAC-SHA1 response | |
62 | ||
63 | ||
64 | class MODE(object): | |
65 | """USB modes""" | |
66 | OTP = 0x00 # OTP only | |
67 | CCID = 0x01 # CCID only, no eject | |
68 | OTP_CCID = 0x02 # OTP + CCID composite | |
69 | U2F = 0x03 # U2F mode | |
70 | OTP_U2F = 0x04 # OTP + U2F composite | |
71 | U2F_CCID = 0x05 # U2F + U2F composite | |
72 | OTP_U2F_CCID = 0x06 # OTP + U2F + CCID composite | |
73 | MASK = 0x07 # Mask for mode bits | |
74 | FLAG_EJECT = 0x80 # CCID device supports eject (CCID) / OTP force eject (CCID composite) | |
75 | ||
76 | @classmethod | |
77 | def all(cls, otp=False, ccid=False, u2f=False): | |
78 | """Returns a set of all USB modes, with optional filtering""" | |
79 | modes = set([ | |
80 | cls.OTP, | |
81 | cls.CCID, | |
82 | cls.OTP_CCID, | |
83 | cls.U2F, | |
84 | cls.OTP_U2F, | |
85 | cls.U2F_CCID, | |
86 | cls.OTP_U2F_CCID | |
87 | ]) | |
88 | ||
89 | if otp: | |
90 | modes.difference_update(set([ | |
91 | cls.CCID, | |
92 | cls.U2F, | |
93 | cls.U2F_CCID | |
94 | ])) | |
95 | ||
96 | if ccid: | |
97 | modes.difference_update(set([ | |
98 | cls.OTP, | |
99 | cls.U2F, | |
100 | cls.OTP_U2F | |
101 | ])) | |
102 | ||
103 | if u2f: | |
104 | modes.difference_update(set([ | |
105 | cls.OTP, | |
106 | cls.CCID, | |
107 | cls.OTP_CCID | |
108 | ])) | |
109 | ||
110 | return modes | |
111 | ||
112 | ||
113 | YUBICO_VID = 0x1050 # Global vendor ID | |
114 | ||
115 | ||
116 | class PID(object): | |
117 | """USB Product IDs""" | |
118 | YUBIKEY = 0x0010 # Yubikey (version 1 and 2) | |
119 | ||
120 | NEO_OTP = 0x0110 # Yubikey NEO - OTP only | |
121 | NEO_OTP_CCID = 0x0111 # Yubikey NEO - OTP and CCID | |
122 | NEO_CCID = 0x0112 # Yubikey NEO - CCID only | |
123 | NEO_U2F = 0x0113 # Yubikey NEO - U2F only | |
124 | NEO_OTP_U2F = 0x0114 # Yubikey NEO - OTP and U2F | |
125 | NEO_U2F_CCID = 0x0115 # Yubikey NEO - U2F and CCID | |
126 | NEO_OTP_U2F_CCID = 0x0116 # Yubikey NEO - OTP, U2F and CCID | |
127 | ||
128 | NEO_SKY = 0x0120 # Security Key by Yubico | |
129 | ||
130 | YK4_OTP = 0x0401 # Yubikey 4 - OTP only | |
131 | YK4_U2F = 0x0402 # Yubikey 4 - U2F only | |
132 | YK4_OTP_U2F = 0x0403 # Yubikey 4 - OTP and U2F | |
133 | YK4_CCID = 0x0404 # Yubikey 4 - CCID only | |
134 | YK4_OTP_CCID = 0x0405 # Yubikey 4 - OTP and CCID | |
135 | YK4_U2F_CCID = 0x0406 # Yubikey 4 - U2F and CCID | |
136 | YK4_OTP_U2F_CCID = 0x0407 # Yubikey 4 - OTP, U2F and CCID | |
137 | ||
138 | PLUS_U2F_OTP = 0x0410 # Yubikey plus - OTP+U2F | |
139 | ||
140 | @classmethod | |
141 | def all(cls, otp=False, ccid=False, u2f=False): | |
142 | """Returns a set of all PIDs, with optional filtering""" | |
143 | pids = set([ | |
144 | cls.YUBIKEY, | |
145 | cls.NEO_OTP, | |
146 | cls.NEO_OTP_CCID, | |
147 | cls.NEO_CCID, | |
148 | cls.NEO_U2F, | |
149 | cls.NEO_OTP_U2F, | |
150 | cls.NEO_U2F_CCID, | |
151 | cls.NEO_OTP_U2F_CCID, | |
152 | cls.NEO_SKY, | |
153 | cls.YK4_OTP, | |
154 | cls.YK4_U2F, | |
155 | cls.YK4_OTP_U2F, | |
156 | cls.YK4_CCID, | |
157 | cls.YK4_OTP_CCID, | |
158 | cls.YK4_U2F_CCID, | |
159 | cls.YK4_OTP_U2F_CCID, | |
160 | cls.PLUS_U2F_OTP | |
161 | ]) | |
162 | ||
163 | if otp: | |
164 | pids.difference_update(set([ | |
165 | cls.NEO_CCID, | |
166 | cls.NEO_U2F, | |
167 | cls.NEO_U2F_CCID, | |
168 | cls.NEO_SKY, | |
169 | cls.YK4_U2F, | |
170 | cls.YK4_CCID, | |
171 | cls.YK4_U2F_CCID | |
172 | ])) | |
173 | ||
174 | if ccid: | |
175 | pids.difference_update(set([ | |
176 | cls.YUBIKEY, | |
177 | cls.NEO_OTP, | |
178 | cls.NEO_U2F, | |
179 | cls.NEO_OTP_U2F, | |
180 | cls.NEO_SKY, | |
181 | cls.YK4_OTP, | |
182 | cls.YK4_U2F, | |
183 | cls.YK4_OTP_U2F, | |
184 | cls.PLUS_U2F_OTP | |
185 | ])) | |
186 | ||
187 | if u2f: | |
188 | pids.difference_update(set([ | |
189 | cls.YUBIKEY, | |
190 | cls.NEO_OTP, | |
191 | cls.NEO_OTP_CCID, | |
192 | cls.NEO_CCID, | |
193 | cls.YK4_OTP, | |
194 | cls.YK4_CCID, | |
195 | cls.YK4_OTP_CCID | |
196 | ])) | |
197 | ||
198 | return pids | |
199 | ||
200 | ||
201 | class YK4_CAPA(object): | |
202 | """Capability bits in the YK4_CAPA field""" | |
203 | OTP = 0x01 # OTP functionality | |
204 | U2F = 0x02 # U2F functionality | |
205 | CCID = 0x04 # CCID functionality | |
206 | ||
207 | class TAG(object): | |
208 | """Tags for TLV data read from the YK4_CAPABILITIES slot""" | |
209 | CAPA = 0x01 # capabilities | |
210 | SERIAL = 0x02 # serial number |
12 | 12 | |
13 | 13 | import struct |
14 | 14 | |
15 | import yubico_util | |
16 | import yubikey_defs | |
17 | import yubico_exception | |
18 | import yubikey_config | |
19 | from yubico import __version__ | |
15 | from . import yubico_util | |
16 | from . import yubikey_defs | |
17 | from . import yubico_exception | |
18 | from .yubico_version import __version__ | |
19 | ||
20 | from .yubikey_defs import SLOT | |
20 | 21 | |
21 | 22 | class YubiKeyFrame: |
22 | 23 | """ |
28 | 29 | flags. |
29 | 30 | """ |
30 | 31 | |
31 | def __init__(self, command, payload=''): | |
32 | if payload is '': | |
33 | payload = '\x00' * 64 | |
32 | def __init__(self, command, payload=b''): | |
33 | if not payload: | |
34 | payload = b'\x00' * 64 | |
34 | 35 | if len(payload) != 64: |
35 | 36 | raise yubico_exception.InputError('payload must be empty or 64 bytes') |
37 | if not isinstance(payload, bytes): | |
38 | raise yubico_exception.InputError('payload must be a bytestring') | |
36 | 39 | self.payload = payload |
37 | 40 | self.command = command |
38 | 41 | self.crc = yubico_util.crc16(payload) |
59 | 62 | # unsigned short crc; |
60 | 63 | # unsigned char filler[3]; |
61 | 64 | # } YKFRAME; |
62 | filler = '' | |
65 | filler = b'' | |
63 | 66 | return struct.pack('<64sBH3s', |
64 | 67 | self.payload, self.command, self.crc, filler) |
65 | 68 | |
77 | 80 | this, rest = rest[:7], rest[7:] |
78 | 81 | if seq > 0 and rest: |
79 | 82 | # never skip first or last serie |
80 | if this != '\x00\x00\x00\x00\x00\x00\x00': | |
81 | this += chr(yubikey_defs.SLOT_WRITE_FLAG + seq) | |
83 | if this != b'\x00\x00\x00\x00\x00\x00\x00': | |
84 | this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq) | |
82 | 85 | out.append(self._debug_string(debug, this)) |
83 | 86 | else: |
84 | this += chr(yubikey_defs.SLOT_WRITE_FLAG + seq) | |
87 | this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq) | |
85 | 88 | out.append(self._debug_string(debug, this)) |
86 | 89 | seq += 1 |
87 | 90 | return out |
92 | 95 | """ |
93 | 96 | if not debug: |
94 | 97 | return data |
95 | if self.command in [yubikey_config.SLOT_CONFIG, | |
96 | yubikey_config.SLOT_CONFIG2, | |
97 | yubikey_config.SLOT_UPDATE1, | |
98 | yubikey_config.SLOT_UPDATE2, | |
99 | yubikey_config.SLOT_SWAP, | |
100 | ]: | |
101 | # annotate according to config_st (see yubikey_config.to_string()) | |
102 | if ord(data[-1]) == 0x80: | |
98 | if self.command in [ | |
99 | SLOT.CONFIG, | |
100 | SLOT.CONFIG2, | |
101 | SLOT.UPDATE1, | |
102 | SLOT.UPDATE2, | |
103 | SLOT.SWAP, | |
104 | ]: | |
105 | # annotate according to config_st (see yubikey_defs.to_string()) | |
106 | if yubico_util.ord_byte(data[-1]) == 0x80: | |
103 | 107 | return (data, "FFFFFFF") |
104 | if ord(data[-1]) == 0x81: | |
108 | if yubico_util.ord_byte(data[-1]) == 0x81: | |
105 | 109 | return (data, "FFFFFFF") |
106 | if ord(data[-1]) == 0x82: | |
110 | if yubico_util.ord_byte(data[-1]) == 0x82: | |
107 | 111 | return (data, "FFUUUUU") |
108 | if ord(data[-1]) == 0x83: | |
112 | if yubico_util.ord_byte(data[-1]) == 0x83: | |
109 | 113 | return (data, "UKKKKKK") |
110 | if ord(data[-1]) == 0x84: | |
114 | if yubico_util.ord_byte(data[-1]) == 0x84: | |
111 | 115 | return (data, "KKKKKKK") |
112 | if ord(data[-1]) == 0x85: | |
116 | if yubico_util.ord_byte(data[-1]) == 0x85: | |
113 | 117 | return (data, "KKKAAAA") |
114 | if ord(data[-1]) == 0x86: | |
118 | if yubico_util.ord_byte(data[-1]) == 0x86: | |
115 | 119 | return (data, "AAlETCr") |
116 | if ord(data[-1]) == 0x87: | |
120 | if yubico_util.ord_byte(data[-1]) == 0x87: | |
117 | 121 | return (data, "rCR") |
118 | 122 | # after payload |
119 | if ord(data[-1]) == 0x89: | |
123 | if yubico_util.ord_byte(data[-1]) == 0x89: | |
120 | 124 | return (data, " Scr") |
121 | 125 | else: |
122 | 126 | return (data, '') |
14 | 14 | ] |
15 | 15 | |
16 | 16 | import struct |
17 | ||
18 | from yubico import __version__ | |
19 | import yubikey_usb_hid | |
20 | import yubikey_frame | |
21 | import yubico_exception | |
17 | import binascii | |
18 | ||
19 | from .yubico_version import __version__ | |
20 | from .yubikey_defs import SLOT, MODE | |
21 | from . import yubikey_usb_hid | |
22 | from . import yubikey_base | |
23 | from . import yubikey_frame | |
24 | from . import yubico_exception | |
25 | from . import yubico_util | |
22 | 26 | |
23 | 27 | # commands from ykdef.h |
24 | _SLOT_NDEF = 0x08 # Write YubiKey NEO NDEF | |
25 | _ACC_CODE_SIZE = 6 # Size of access code to re-program device | |
28 | _ACC_CODE_SIZE = 6 # Size of access code to re-program device | |
26 | 29 | _NDEF_DATA_SIZE = 54 |
27 | 30 | |
28 | 31 | # from nfcdef.h |
68 | 71 | (0x23, "urn:nfc:",), |
69 | 72 | ] |
70 | 73 | |
74 | _NDEF_SLOTS = { | |
75 | 1: SLOT.NDEF, | |
76 | 2: SLOT.NDEF2 | |
77 | } | |
78 | ||
79 | ||
71 | 80 | class YubiKeyNEO_USBHIDError(yubico_exception.YubicoError): |
72 | 81 | """ Exception raised for errors with the NEO USB HID communication. """ |
73 | 82 | |
83 | ||
74 | 84 | class YubiKeyNEO_USBHIDCapabilities(yubikey_usb_hid.YubiKeyUSBHIDCapabilities): |
75 | 85 | """ |
76 | Capabilities of current YubiKey NEO BETA firmwares 2.1.4 and 2.1.5. | |
86 | Capabilities of current YubiKey NEO. | |
77 | 87 | """ |
78 | 88 | |
79 | 89 | def have_challenge_response(self, mode): |
80 | return False | |
90 | return self.version >= (3, 0, 0) | |
81 | 91 | |
82 | 92 | def have_configuration_slot(self, slot): |
83 | return (slot == 1) | |
84 | ||
85 | def have_nfc_ndef(self): | |
86 | return True | |
93 | if self.version < (3, 0, 0): | |
94 | return (slot == 1) | |
95 | return slot in [1, 2] | |
96 | ||
97 | def have_nfc_ndef(self, slot=1): | |
98 | if self.version < (3, 0, 0): | |
99 | return slot == 1 | |
100 | return slot in [1, 2] | |
101 | ||
102 | def have_scanmap(self): | |
103 | return self.version >= (3, 0, 0) | |
104 | ||
105 | def have_device_config(self): | |
106 | return self.version >= (3, 0, 0) | |
107 | ||
108 | def have_usb_mode(self, mode): | |
109 | if not self.have_device_config(): | |
110 | return False | |
111 | mode &= ~MODE.FLAG_EJECT # Mask away eject flag | |
112 | return mode in [0, 1, 2, 3, 4, 5, 6] | |
113 | ||
87 | 114 | |
88 | 115 | class YubiKeyNEO_USBHID(yubikey_usb_hid.YubiKeyUSBHID): |
89 | 116 | """ |
97 | 124 | |
98 | 125 | model = 'YubiKey NEO' |
99 | 126 | description = 'YubiKey NEO' |
100 | ||
101 | def __init__(self, debug=False, skip=0): | |
127 | _capabilities_cls = YubiKeyNEO_USBHIDCapabilities | |
128 | ||
129 | def __init__(self, debug=False, skip=0, hid_device=None): | |
102 | 130 | """ |
103 | 131 | Find and connect to a YubiKey NEO (USB HID). |
104 | 132 | |
106 | 134 | skip -- number of YubiKeys to skip |
107 | 135 | debug -- True or False |
108 | 136 | """ |
109 | yubikey_usb_hid.YubiKeyUSBHID.__init__(self, debug, skip) | |
137 | super(YubiKeyNEO_USBHID, self).__init__(debug, skip, hid_device) | |
110 | 138 | if self.version_num() >= (2, 1, 4,) and \ |
111 | 139 | self.version_num() <= (2, 1, 9,): |
112 | 140 | self.description = 'YubiKey NEO BETA' |
113 | ||
114 | def write_ndef(self, ndef): | |
115 | """ | |
116 | ||
117 | ||
141 | elif self.version_num() < (3, 0, 0): | |
142 | raise yubikey_base.YubiKeyVersionError("Incorrect version for %s" % self) | |
143 | ||
144 | def write_ndef(self, ndef, slot=1): | |
145 | """ | |
118 | 146 | Write an NDEF tag configuration to the YubiKey NEO. |
119 | 147 | """ |
120 | return self._write_config(ndef, _SLOT_NDEF) | |
121 | ||
122 | class YubiKeyNEO_NDEF(): | |
148 | if not self.capabilities.have_nfc_ndef(slot): | |
149 | raise yubikey_base.YubiKeyVersionError("NDEF slot %i unsupported in %s" % (slot, self)) | |
150 | ||
151 | return self._device._write_config(ndef, _NDEF_SLOTS[slot]) | |
152 | ||
153 | def init_device_config(self, **kwargs): | |
154 | return YubiKeyNEO_DEVICE_CONFIG(**kwargs) | |
155 | ||
156 | def write_device_config(self, device_config): | |
157 | """ | |
158 | Write a DEVICE_CONFIG to the YubiKey NEO. | |
159 | """ | |
160 | if not self.capabilities.have_usb_mode(device_config._mode): | |
161 | raise yubikey_base.YubiKeyVersionError("USB mode: %02x not supported for %s" % (device_config._mode, self)) | |
162 | return self._device._write_config(device_config, SLOT.DEVICE_CONFIG) | |
163 | ||
164 | def write_scan_map(self, scanmap=None): | |
165 | if not self.capabilities.have_scanmap(): | |
166 | raise yubikey_base.YubiKeyVersionError("Scanmap not supported in %s" % self) | |
167 | return self._device._write_config(YubiKeyNEO_SCAN_MAP(scanmap), SLOT.SCAN_MAP) | |
168 | ||
169 | ||
170 | class YubiKeyNEO_NDEF(object): | |
123 | 171 | """ |
124 | 172 | Class allowing programming of a YubiKey NEO NDEF. |
125 | 173 | """ |
126 | 174 | |
127 | 175 | ndef_type = _NDEF_URI_TYPE |
128 | 176 | ndef_str = None |
129 | access_code = chr(0x0) * _ACC_CODE_SIZE | |
177 | access_code = yubico_util.chr_byte(0x0) * _ACC_CODE_SIZE | |
130 | 178 | # For _NDEF_URI_TYPE |
131 | 179 | ndef_uri_rt = 0x0 # No prepending |
132 | 180 | # For _NDEF_TEXT_TYPE |
133 | ndef_text_lang = 'en' | |
181 | ndef_text_lang = b'en' | |
134 | 182 | ndef_text_enc = 'UTF-8' |
135 | 183 | |
136 | 184 | def __init__(self, data, access_code = None): |
186 | 234 | first = struct.pack(fmt, |
187 | 235 | len(data), |
188 | 236 | self.ndef_type, |
189 | data.ljust(_NDEF_DATA_SIZE, chr(0x0)), | |
237 | data.ljust(_NDEF_DATA_SIZE, b'\0'), | |
190 | 238 | self.access_code, |
191 | 239 | ) |
192 | 240 | #crc = 0xffff - yubico_util.crc16(first) |
193 | 241 | #second = first + struct.pack('<H', crc) + self.unlock_code |
194 | 242 | return first |
195 | 243 | |
196 | def to_frame(self, slot=_SLOT_NDEF): | |
244 | def to_frame(self, slot=SLOT.NDEF): | |
197 | 245 | """ |
198 | 246 | Return the current configuration as a YubiKeyFrame object. |
199 | 247 | """ |
200 | 248 | data = self.to_string() |
201 | payload = data.ljust(64, chr(0x0)) | |
249 | payload = data.ljust(64, b'\0') | |
202 | 250 | return yubikey_frame.YubiKeyFrame(command = slot, payload = payload) |
203 | 251 | |
204 | 252 | def _encode_ndef_uri_type(self, data): |
210 | 258 | """ |
211 | 259 | t = 0x0 |
212 | 260 | for (code, prefix) in uri_identifiers: |
213 | if data[:len(prefix)].lower() == prefix: | |
261 | if data[:len(prefix)].decode('latin-1').lower() == prefix: | |
214 | 262 | t = code |
215 | 263 | data = data[len(prefix):] |
216 | 264 | break |
217 | data = chr(t) + data | |
265 | data = yubico_util.chr_byte(t) + data | |
218 | 266 | return data |
219 | 267 | |
220 | 268 | def _encode_ndef_text_params(self, data): |
225 | 273 | status = len(self.ndef_text_lang) |
226 | 274 | if self.ndef_text_enc == 'UTF16': |
227 | 275 | status = status & 0b10000000 |
228 | return chr(status) + self.ndef_text_lang + data | |
276 | return yubico_util.chr_byte(status) + self.ndef_text_lang + data | |
277 | ||
278 | ||
279 | class YubiKeyNEO_DEVICE_CONFIG(object): | |
280 | """ | |
281 | Class allowing programming of a YubiKey NEO DEVICE_CONFIG. | |
282 | """ | |
283 | ||
284 | _mode = MODE.OTP | |
285 | _cr_timeout = 0 | |
286 | _auto_eject_time = 0 | |
287 | ||
288 | ||
289 | def __init__(self, mode=MODE.OTP): | |
290 | self._mode = mode | |
291 | ||
292 | def cr_timeout(self, timeout = 0): | |
293 | """ | |
294 | Configure the challenge-response timeout in seconds. | |
295 | """ | |
296 | self._cr_timeout = timeout | |
297 | return self | |
298 | ||
299 | def auto_eject_time(self, auto_eject_time = 0): | |
300 | """ | |
301 | Configure the auto eject time in 10x seconds. | |
302 | """ | |
303 | self._auto_eject_time = auto_eject_time | |
304 | return self | |
305 | ||
306 | def to_string(self): | |
307 | """ | |
308 | Return the current DEVICE_CONFIG as a string (always 4 bytes). | |
309 | """ | |
310 | fmt = '<BBH' | |
311 | first = struct.pack( | |
312 | fmt, | |
313 | self._mode, | |
314 | self._cr_timeout, | |
315 | self._auto_eject_time | |
316 | ) | |
317 | ||
318 | #crc = 0xffff - yubico_util.crc16(first) | |
319 | #second = first + struct.pack('<H', crc) | |
320 | return first | |
321 | ||
322 | def to_frame(self, slot=SLOT.DEVICE_CONFIG): | |
323 | """ | |
324 | Return the current configuration as a YubiKeyFrame object. | |
325 | """ | |
326 | data = self.to_string() | |
327 | payload = data.ljust(64, b'\0') | |
328 | return yubikey_frame.YubiKeyFrame(command=slot, payload=payload) | |
329 | ||
330 | ||
331 | class YubiKeyNEO_SCAN_MAP(object): | |
332 | """ | |
333 | Class allowing programming of a YubiKey NEO scan map. | |
334 | """ | |
335 | ||
336 | def __init__(self, scanmap=None): | |
337 | if scanmap: | |
338 | if scanmap.startswith(b'h:'): | |
339 | scanmap = binascii.unhexlify(scanmap[2:]) | |
340 | if len(scanmap) != 45: | |
341 | raise yubico_exception.InputError('Scan map must be exactly 45 bytes') | |
342 | self.scanmap = scanmap | |
343 | ||
344 | def to_frame(self, slot=SLOT.SCAN_MAP): | |
345 | """ | |
346 | Return the current configuration as a YubiKeyFrame object. | |
347 | """ | |
348 | payload = self.scanmap.ljust(64, b'\0') | |
349 | return yubikey_frame.YubiKeyFrame(command=slot, payload=payload) |
13 | 13 | 'YubiKeyUSBHIDStatus', |
14 | 14 | ] |
15 | 15 | |
16 | from yubico import __version__ | |
17 | ||
18 | import yubico_util | |
19 | import yubico_exception | |
20 | import yubikey_frame | |
21 | import yubikey_config | |
22 | import yubikey_defs | |
23 | import yubikey | |
24 | from yubikey import YubiKey | |
16 | from .yubico_version import __version__ | |
17 | ||
18 | from . import yubico_util | |
19 | from . import yubico_exception | |
20 | from . import yubikey_frame | |
21 | from . import yubikey_config | |
22 | from . import yubikey_defs | |
23 | from . import yubikey_base | |
24 | from .yubikey_defs import SLOT, YUBICO_VID, PID | |
25 | from .yubikey_base import YubiKey | |
25 | 26 | import struct |
26 | 27 | import time |
27 | 28 | import sys |
41 | 42 | # from ykcore_backend.h |
42 | 43 | _FEATURE_RPT_SIZE = 8 |
43 | 44 | _REPORT_TYPE_FEATURE = 0x03 |
44 | # from ykdef.h | |
45 | _YUBICO_VID = 0x1050 | |
46 | _YUBIKEY_PID = 0x0010 | |
47 | _NEO_OTP_PID = 0x0110 | |
48 | _NEO_OTP_CCID_PID = 0x0111 | |
49 | _NEO_OTP_U2F_PID = 0x0114 | |
50 | _NEO_OTP_U2F_CCID_PID = 0x0116 | |
51 | ||
52 | _YK4_OTP_PID = 0x0401 | |
53 | _YK4_OTP_U2F_PID = 0x0403 | |
54 | _YK4_OTP_CCID_PID = 0x0405 | |
55 | _YK4_OTP_U2F_CCID_PID = 0x0407 | |
56 | ||
57 | _PLUS_U2F_OTP_PID = 0x0410 | |
58 | ||
59 | _YK_PIDS = [ | |
60 | _YUBIKEY_PID, | |
61 | _NEO_OTP_PID, | |
62 | _NEO_OTP_CCID_PID, | |
63 | _NEO_OTP_U2F_PID, | |
64 | _NEO_OTP_U2F_CCID_PID, | |
65 | _YK4_OTP_PID, | |
66 | _YK4_OTP_U2F_PID, | |
67 | _YK4_OTP_CCID_PID, | |
68 | _YK4_OTP_U2F_CCID_PID, | |
69 | _PLUS_U2F_OTP_PID | |
70 | ] | |
71 | ||
72 | # commands from ykdef.h | |
73 | _SLOT_DEVICE_SERIAL = 0x10 # Device serial number | |
74 | _SLOT_CHAL_OTP1 = 0x20 # Write 6 byte challenge to slot 1, get Yubico OTP response | |
75 | _SLOT_CHAL_OTP2 = 0x28 # Write 6 byte challenge to slot 2, get Yubico OTP response | |
76 | _SLOT_CHAL_HMAC1 = 0x30 # Write 64 byte challenge to slot 1, get HMAC-SHA1 response | |
77 | _SLOT_CHAL_HMAC2 = 0x38 # Write 64 byte challenge to slot 2, get HMAC-SHA1 response | |
78 | 45 | |
79 | 46 | # dict used to select command for mode+slot in _challenge_response |
80 | _CMD_CHALLENGE = {'HMAC': {1: _SLOT_CHAL_HMAC1, 2: _SLOT_CHAL_HMAC2}, | |
81 | 'OTP': {1: _SLOT_CHAL_OTP1, 2: _SLOT_CHAL_OTP2}, | |
47 | _CMD_CHALLENGE = {'HMAC': {1: SLOT.CHAL_HMAC1, 2: SLOT.CHAL_HMAC2}, | |
48 | 'OTP': {1: SLOT.CHAL_OTP1, 2: SLOT.CHAL_OTP2}, | |
82 | 49 | } |
83 | 50 | |
84 | 51 | class YubiKeyUSBHIDError(yubico_exception.YubicoError): |
85 | 52 | """ Exception raised for errors with the USB HID communication. """ |
86 | 53 | |
87 | class YubiKeyUSBHIDCapabilities(yubikey.YubiKeyCapabilities): | |
54 | ||
55 | class YubiKeyUSBHIDCapabilities(yubikey_base.YubiKeyCapabilities): | |
88 | 56 | """ |
89 | 57 | Capture the capabilities of the various versions of YubiKeys. |
90 | 58 | |
92 | 60 | in one or more versions, leaving the other ones at False through default_answer. |
93 | 61 | """ |
94 | 62 | def __init__(self, model, version, default_answer): |
95 | yubikey.YubiKeyCapabilities.__init__(self, model = model, \ | |
96 | version = version, \ | |
97 | default_answer = default_answer) | |
63 | super(YubiKeyUSBHIDCapabilities, self).__init__( | |
64 | model=model, | |
65 | version=version, | |
66 | default_answer=default_answer) | |
98 | 67 | |
99 | 68 | def have_yubico_OTP(self): |
100 | 69 | """ Yubico OTP support has always been available in the standard YubiKey. """ |
134 | 103 | def have_configuration_slot(self, slot): |
135 | 104 | return (slot in [1, 2]) |
136 | 105 | |
137 | class YubiKeyUSBHID(YubiKey): | |
138 | """ | |
139 | Class for accessing a YubiKey over USB HID. | |
140 | ||
141 | This class is for communicating specifically with standard YubiKeys | |
142 | (USB vendor id = 0x1050, product id = 0x10) using USB HID. | |
143 | ||
144 | There is another class for the YubiKey NEO BETA, even though that | |
145 | product also goes by product id 0x10 for the BETA versions. The | |
146 | expectation is that the final YubiKey NEO will have it's own product id. | |
147 | ||
148 | Tested with YubiKey versions 1.3 and 2.2. | |
149 | """ | |
150 | ||
151 | model = 'YubiKey' | |
152 | description = 'YubiKey (or YubiKey NANO)' | |
106 | ||
107 | class YubiKeyHIDDevice(object): | |
108 | """ | |
109 | High-level wrapper for low-level HID commands for a HID based YubiKey. | |
110 | """ | |
153 | 111 | |
154 | 112 | def __init__(self, debug=False, skip=0): |
155 | 113 | """ |
159 | 117 | skip -- number of YubiKeys to skip |
160 | 118 | debug -- True or False |
161 | 119 | """ |
162 | YubiKey.__init__(self, debug) | |
120 | self.debug = debug | |
163 | 121 | self._usb_handle = None |
164 | 122 | if not self._open(skip): |
165 | 123 | raise YubiKeyUSBHIDError('YubiKey USB HID initialization failed') |
166 | 124 | self.status() |
167 | self.capabilities = \ | |
168 | YubiKeyUSBHIDCapabilities(model = self.model, \ | |
169 | version = self.version_num(), \ | |
170 | default_answer = False) | |
125 | ||
126 | def status(self): | |
127 | """ | |
128 | Poll YubiKey for status. | |
129 | """ | |
130 | data = self._read() | |
131 | self._status = YubiKeyUSBHIDStatus(data) | |
132 | return self._status | |
171 | 133 | |
172 | 134 | def __del__(self): |
173 | 135 | try: |
175 | 137 | self._close() |
176 | 138 | except IOError: |
177 | 139 | pass |
178 | ||
179 | def __repr__(self): | |
180 | return '<%s instance at %s: YubiKey version %s>' % ( | |
181 | self.__class__.__name__, | |
182 | hex(id(self)), | |
183 | self.version() | |
184 | ) | |
185 | ||
186 | def status(self): | |
187 | """ | |
188 | Poll YubiKey for status. | |
189 | """ | |
190 | data = self._read() | |
191 | self._status = YubiKeyUSBHIDStatus(data) | |
192 | return self._status | |
193 | ||
194 | def version_num(self): | |
195 | """ Get the YubiKey version as a tuple (major, minor, build). """ | |
196 | return self._status.ykver() | |
197 | ||
198 | def version(self): | |
199 | """ Get the YubiKey version. """ | |
200 | return self._status.version() | |
201 | ||
202 | def serial(self, may_block=True): | |
203 | """ Get the YubiKey serial number (requires YubiKey 2.2). """ | |
204 | if not self.capabilities.have_serial_number(): | |
205 | raise yubikey.YubiKeyVersionError("Serial number unsupported in YubiKey %s" % self.version() ) | |
206 | return self._read_serial(may_block) | |
207 | ||
208 | def challenge_response(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): | |
209 | """ Issue a challenge to the YubiKey and return the response (requires YubiKey 2.2). """ | |
210 | if not self.capabilities.have_challenge_response(mode): | |
211 | raise yubikey.YubiKeyVersionError("%s challenge-response unsupported in YubiKey %s" % (mode, self.version()) ) | |
212 | return self._challenge_response(challenge, mode, slot, variable, may_block) | |
213 | ||
214 | def init_config(self, **kw): | |
215 | """ Get a configuration object for this type of YubiKey. """ | |
216 | return YubiKeyConfigUSBHID(ykver=self.version_num(), \ | |
217 | capabilities = self.capabilities, \ | |
218 | **kw) | |
219 | ||
220 | def write_config(self, cfg, slot=1): | |
221 | """ Write a configuration to the YubiKey. """ | |
222 | cfg_req_ver = cfg.version_required() | |
223 | if cfg_req_ver > self.version_num(): | |
224 | raise yubikey.YubiKeyVersionError('Configuration requires YubiKey version %i.%i (this is %s)' % \ | |
225 | (cfg_req_ver[0], cfg_req_ver[1], self.version())) | |
226 | if not self.capabilities.have_configuration_slot(slot): | |
227 | raise YubiKeyUSBHIDError("Can't write configuration to slot %i" % (slot)) | |
228 | return self._write_config(cfg, slot) | |
229 | ||
230 | def _read_serial(self, may_block): | |
231 | """ Read the serial number from a YubiKey > 2.2. """ | |
232 | ||
233 | frame = yubikey_frame.YubiKeyFrame(command = _SLOT_DEVICE_SERIAL) | |
234 | self._write(frame) | |
235 | response = self._read_response(may_block=may_block) | |
236 | if not yubico_util.validate_crc16(response[:6]): | |
237 | raise YubiKeyUSBHIDError("Read from device failed CRC check") | |
238 | # the serial number is big-endian, although everything else is little-endian | |
239 | serial = struct.unpack('>lxxx', response) | |
240 | return serial[0] | |
241 | ||
242 | def _challenge_response(self, challenge, mode, slot, variable, may_block): | |
243 | """ Do challenge-response with a YubiKey > 2.0. """ | |
244 | # Check length and pad challenge if appropriate | |
245 | if mode == 'HMAC': | |
246 | if len(challenge) > yubikey_defs.SHA1_MAX_BLOCK_SIZE: | |
247 | raise yubico_exception.InputError('Mode HMAC challenge too big (%i/%i)' \ | |
248 | % (yubikey_defs.SHA1_MAX_BLOCK_SIZE, len(challenge))) | |
249 | if len(challenge) < yubikey_defs.SHA1_MAX_BLOCK_SIZE: | |
250 | pad_with = chr(0x0) | |
251 | if variable and challenge[-1] == pad_with: | |
252 | pad_with = chr(0xff) | |
253 | challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, pad_with) | |
254 | response_len = yubikey_defs.SHA1_DIGEST_SIZE | |
255 | elif mode == 'OTP': | |
256 | if len(challenge) != yubikey_defs.UID_SIZE: | |
257 | raise yubico_exception.InputError('Mode OTP challenge must be %i bytes (got %i)' \ | |
258 | % (yubikey_defs.UID_SIZE, len(challenge))) | |
259 | challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, chr(0x0)) | |
260 | response_len = 16 | |
261 | else: | |
262 | raise yubico_exception.InputError('Invalid mode supplied (%s, valid values are HMAC and OTP)' \ | |
263 | % (mode)) | |
264 | ||
265 | try: | |
266 | command = _CMD_CHALLENGE[mode][slot] | |
267 | except: | |
268 | raise yubico_exception.InputError('Invalid slot specified (%s)' % (slot)) | |
269 | ||
270 | frame = yubikey_frame.YubiKeyFrame(command=command, payload=challenge) | |
271 | self._write(frame) | |
272 | response = self._read_response(may_block=may_block) | |
273 | if not yubico_util.validate_crc16(response[:response_len + 2]): | |
274 | raise YubiKeyUSBHIDError("Read from device failed CRC check") | |
275 | return response[:response_len] | |
276 | 140 | |
277 | 141 | def _write_config(self, cfg, slot): |
278 | 142 | """ Write configuration to YubiKey. """ |
285 | 149 | # make sure we have a fresh pgm_seq value |
286 | 150 | self.status() |
287 | 151 | self._debug("Programmed slot %i, sequence %i -> %i\n" % (slot, old_pgm_seq, self._status.pgm_seq)) |
288 | if self._status.pgm_seq != old_pgm_seq + 1: | |
289 | raise YubiKeyUSBHIDError('YubiKey programming failed (seq %i not increased (%i))' % \ | |
290 | (old_pgm_seq, self._status.pgm_seq)) | |
152 | ||
153 | if slot in [SLOT.CONFIG, SLOT.CONFIG2] or old_pgm_seq != 0: | |
154 | if self._status.pgm_seq == old_pgm_seq + 1: | |
155 | return | |
156 | elif self._status.pgm_seq == 1: | |
157 | return | |
158 | ||
159 | raise YubiKeyUSBHIDError('YubiKey programming failed (seq %i not increased (%i))' % \ | |
160 | (old_pgm_seq, self._status.pgm_seq)) | |
291 | 161 | |
292 | 162 | def _read_response(self, may_block=False): |
293 | 163 | """ Wait for a response to become available, and read it. """ |
296 | 166 | # continue reading while response pending is set |
297 | 167 | while True: |
298 | 168 | this = self._read() |
299 | flags = ord(this[7]) | |
169 | flags = yubico_util.ord_byte(this[7]) | |
300 | 170 | if flags & yubikey_defs.RESP_PENDING_FLAG: |
301 | 171 | seq = flags & 0b00011111 |
302 | 172 | if res and (seq == 0): |
320 | 190 | self._debug("Failed reading %i bytes (got %i) from USB HID YubiKey.\n" |
321 | 191 | % (_FEATURE_RPT_SIZE, recv)) |
322 | 192 | raise YubiKeyUSBHIDError('Failed reading from USB HID YubiKey') |
323 | data = ''.join(chr(c) for c in recv) | |
193 | data = b''.join(yubico_util.chr_byte(c) for c in recv) | |
324 | 194 | self._debug("READ : %s" % (yubico_util.hexdump(data, colorize=True))) |
325 | 195 | return data |
326 | 196 | |
343 | 213 | """ |
344 | 214 | Reset read mode by issuing a dummy write. |
345 | 215 | """ |
346 | data = '\x00\x00\x00\x00\x00\x00\x00\x8f' | |
216 | data = b'\x00\x00\x00\x00\x00\x00\x00\x8f' | |
347 | 217 | self._raw_write(data) |
348 | 218 | self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG) |
349 | 219 | return True |
399 | 269 | wait_num = (timeout * 2) - 1 + 6 |
400 | 270 | resp_timeout = False # YubiKey hasn't indicated RESP_TIMEOUT (yet) |
401 | 271 | while not finished: |
272 | time.sleep(sleep) | |
402 | 273 | this = self._read() |
403 | flags = ord(this[7]) | |
274 | flags = yubico_util.ord_byte(this[7]) | |
404 | 275 | |
405 | 276 | if flags & yubikey_defs.RESP_TIMEOUT_WAIT_FLAG: |
406 | 277 | if not resp_timeout: |
435 | 306 | reason = 'Timed out waiting for YubiKey to clear status 0x%x' % mask |
436 | 307 | else: |
437 | 308 | reason = 'Timed out waiting for YubiKey to set status 0x%x' % mask |
438 | raise yubikey.YubiKeyTimeout(reason) | |
439 | time.sleep(sleep) | |
309 | raise yubikey_base.YubiKeyTimeout(reason) | |
440 | 310 | sleep = min(sleep + sleep, 0.5) |
441 | 311 | else: |
442 | 312 | return this |
454 | 324 | try: |
455 | 325 | self._usb_handle = usb_device.open() |
456 | 326 | self._usb_handle.detachKernelDriver(0) |
457 | except Exception, error: | |
327 | except Exception as error: | |
458 | 328 | if 'could not detach kernel driver from interface' in str(error): |
459 | 329 | self._debug('The in-kernel-HID driver has already been detached\n') |
460 | 330 | else: |
493 | 363 | import usb.core |
494 | 364 | import usb.legacy |
495 | 365 | devices = [usb.legacy.Device(d) for d in usb.core.find( |
496 | find_all=True, idVendor=_YUBICO_VID)] | |
366 | find_all=True, idVendor=YUBICO_VID)] | |
497 | 367 | except ImportError: |
498 | 368 | # Using PyUsb < 1.0. |
499 | 369 | import usb |
500 | 370 | devices = [d for bus in usb.busses() for d in bus.devices] |
501 | 371 | for device in devices: |
502 | if device.idVendor == _YUBICO_VID: | |
503 | if device.idProduct in _YK_PIDS: | |
372 | if device.idVendor == YUBICO_VID: | |
373 | if device.idProduct in PID.all(otp=True): | |
504 | 374 | if skip == 0: |
505 | 375 | return device |
506 | 376 | skip -= 1 |
513 | 383 | pre = self.__class__.__name__ |
514 | 384 | if hasattr(self, 'debug_prefix'): |
515 | 385 | pre = getattr(self, 'debug_prefix') |
516 | sys.stderr.write("%s: " % (self.__class__.__name__)) | |
386 | sys.stderr.write("%s: " % pre) | |
517 | 387 | sys.stderr.write(out) |
518 | 388 | |
519 | class YubiKeyUSBHIDStatus(): | |
389 | ||
390 | class YubiKeyUSBHID(YubiKey): | |
391 | """ | |
392 | Class for accessing a YubiKey over USB HID. | |
393 | ||
394 | This class is for communicating specifically with standard YubiKeys | |
395 | (USB vendor id = 0x1050, product id = 0x10) using USB HID. | |
396 | ||
397 | There is another class for the YubiKey NEO BETA, even though that | |
398 | product also goes by product id 0x10 for the BETA versions. The | |
399 | expectation is that the final YubiKey NEO will have it's own product id. | |
400 | ||
401 | Tested with YubiKey versions 1.3 and 2.2. | |
402 | """ | |
403 | ||
404 | model = 'YubiKey' | |
405 | description = 'YubiKey (or YubiKey NANO)' | |
406 | _capabilities_cls = YubiKeyUSBHIDCapabilities | |
407 | ||
408 | def __init__(self, debug=False, skip=0, hid_device=None): | |
409 | """ | |
410 | Find and connect to a YubiKey (USB HID). | |
411 | ||
412 | Attributes : | |
413 | skip -- number of YubiKeys to skip | |
414 | debug -- True or False | |
415 | """ | |
416 | super(YubiKeyUSBHID, self).__init__(debug) | |
417 | if hid_device is None: | |
418 | self._device = YubiKeyHIDDevice(debug, skip) | |
419 | else: | |
420 | self._device = hid_device | |
421 | self.capabilities = \ | |
422 | self._capabilities_cls(model=self.model, | |
423 | version=self.version_num(), | |
424 | default_answer=False) | |
425 | ||
426 | def __repr__(self): | |
427 | return '<%s instance at %s: YubiKey version %s>' % ( | |
428 | self.__class__.__name__, | |
429 | hex(id(self)), | |
430 | self.version() | |
431 | ) | |
432 | ||
433 | def __str__(self): | |
434 | return '%s (%s)' % (self.model, self.version()) | |
435 | ||
436 | def status(self): | |
437 | """ | |
438 | Poll YubiKey for status. | |
439 | """ | |
440 | return self._device.status() | |
441 | ||
442 | def version_num(self): | |
443 | """ Get the YubiKey version as a tuple (major, minor, build). """ | |
444 | return self._device._status.ykver() | |
445 | ||
446 | def version(self): | |
447 | """ Get the YubiKey version. """ | |
448 | return self._device._status.version() | |
449 | ||
450 | def serial(self, may_block=True): | |
451 | """ Get the YubiKey serial number (requires YubiKey 2.2). """ | |
452 | if not self.capabilities.have_serial_number(): | |
453 | raise yubikey_base.YubiKeyVersionError("Serial number unsupported in YubiKey %s" % self.version() ) | |
454 | return self._read_serial(may_block) | |
455 | ||
456 | def challenge_response(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): | |
457 | """ Issue a challenge to the YubiKey and return the response (requires YubiKey 2.2). """ | |
458 | if not self.capabilities.have_challenge_response(mode): | |
459 | raise yubikey_base.YubiKeyVersionError("%s challenge-response unsupported in YubiKey %s" % (mode, self.version()) ) | |
460 | return self._challenge_response(challenge, mode, slot, variable, may_block) | |
461 | ||
462 | def init_config(self, **kw): | |
463 | """ Get a configuration object for this type of YubiKey. """ | |
464 | return YubiKeyConfigUSBHID(ykver=self.version_num(), \ | |
465 | capabilities = self.capabilities, \ | |
466 | **kw) | |
467 | ||
468 | def write_config(self, cfg, slot=1): | |
469 | """ Write a configuration to the YubiKey. """ | |
470 | cfg_req_ver = cfg.version_required() | |
471 | if cfg_req_ver > self.version_num(): | |
472 | raise yubikey_base.YubiKeyVersionError('Configuration requires YubiKey version %i.%i (this is %s)' % \ | |
473 | (cfg_req_ver[0], cfg_req_ver[1], self.version())) | |
474 | if not self.capabilities.have_configuration_slot(slot): | |
475 | raise YubiKeyUSBHIDError("Can't write configuration to slot %i" % (slot)) | |
476 | return self._device._write_config(cfg, slot) | |
477 | ||
478 | def _read_serial(self, may_block): | |
479 | """ Read the serial number from a YubiKey > 2.2. """ | |
480 | ||
481 | frame = yubikey_frame.YubiKeyFrame(command = SLOT.DEVICE_SERIAL) | |
482 | self._device._write(frame) | |
483 | response = self._device._read_response(may_block=may_block) | |
484 | if not yubico_util.validate_crc16(response[:6]): | |
485 | raise YubiKeyUSBHIDError("Read from device failed CRC check") | |
486 | # the serial number is big-endian, although everything else is little-endian | |
487 | serial = struct.unpack('>lxxx', response) | |
488 | return serial[0] | |
489 | ||
490 | def _challenge_response(self, challenge, mode, slot, variable, may_block): | |
491 | """ Do challenge-response with a YubiKey > 2.0. """ | |
492 | # Check length and pad challenge if appropriate | |
493 | if mode == 'HMAC': | |
494 | if len(challenge) > yubikey_defs.SHA1_MAX_BLOCK_SIZE: | |
495 | raise yubico_exception.InputError('Mode HMAC challenge too big (%i/%i)' \ | |
496 | % (yubikey_defs.SHA1_MAX_BLOCK_SIZE, len(challenge))) | |
497 | if len(challenge) < yubikey_defs.SHA1_MAX_BLOCK_SIZE: | |
498 | pad_with = b'\0' | |
499 | if variable and challenge[-1:] == pad_with: | |
500 | pad_with = b'\xff' | |
501 | challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, pad_with) | |
502 | response_len = yubikey_defs.SHA1_DIGEST_SIZE | |
503 | elif mode == 'OTP': | |
504 | if len(challenge) != yubikey_defs.UID_SIZE: | |
505 | raise yubico_exception.InputError('Mode OTP challenge must be %i bytes (got %i)' \ | |
506 | % (yubikey_defs.UID_SIZE, len(challenge))) | |
507 | challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, b'\0') | |
508 | response_len = 16 | |
509 | else: | |
510 | raise yubico_exception.InputError('Invalid mode supplied (%s, valid values are HMAC and OTP)' \ | |
511 | % (mode)) | |
512 | ||
513 | try: | |
514 | command = _CMD_CHALLENGE[mode][slot] | |
515 | except: | |
516 | raise yubico_exception.InputError('Invalid slot specified (%s)' % (slot)) | |
517 | ||
518 | frame = yubikey_frame.YubiKeyFrame(command=command, payload=challenge) | |
519 | self._device._write(frame) | |
520 | response = self._device._read_response(may_block=may_block) | |
521 | if not yubico_util.validate_crc16(response[:response_len + 2]): | |
522 | raise YubiKeyUSBHIDError("Read from device failed CRC check") | |
523 | return response[:response_len] | |
524 | ||
525 | ||
526 | class YubiKeyUSBHIDStatus(object): | |
520 | 527 | """ Class to represent the status information we get from the YubiKey. """ |
521 | 528 | |
522 | 529 | CONFIG1_VALID = 0x01 # Bit in touchLevel indicating that configuration 1 is valid (from firmware 2.1) |
578 | 585 | res.append(2) |
579 | 586 | return res |
580 | 587 | |
588 | ||
581 | 589 | class YubiKeyConfigUSBHID(yubikey_config.YubiKeyConfig): |
582 | 590 | """ |
583 | 591 | Configuration class for USB HID YubiKeys. |
584 | 592 | """ |
585 | 593 | def __init__(self, ykver, capabilities = None, **kw): |
586 | yubikey_config.YubiKeyConfig.__init__(self, ykver = ykver, capabilities = capabilities, **kw) | |
587 | return None | |
594 | super(YubiKeyConfigUSBHID, self).__init__(ykver=ykver, capabilities=capabilities, **kw) |