Codebase list python-yubico / 10d0998
Imported Upstream version 1.3.0 Dain Nilsson 8 years ago
39 changed file(s) with 1968 addition(s) and 1310 deletion(s). Raw diff Collapse all Expand all
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
0175 2015-03-23 Dain Nilsson <dain@yubico.com>
1176
2177 * NEWS, yubico/__init__.py: Updated version.
0 include release.py
10 include COPYING
21 include NEWS
32 include ChangeLog
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
06 * Version 1.2.3 (released 2015-03-23)
17 ** Added PIDs for newer devices.
28 ** Failure to call setConfiguration is now ignored.
00 Metadata-Version: 1.1
11 Name: python-yubico
2 Version: 1.2.3
2 Version: 1.3.0
33 Summary: Python code for talking to Yubico's YubiKeys
44 Home-page: https://github.com/Yubico/python-yubico
55 Author: Yubico Open Source Maintainers
11 Python package for talking to YubiKeys.
22
33 === 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:
88
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).
1312 See `examples/configure_nist_test_key` for an example.
1413
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
1615 challenge-response operations.
1716 See `examples/nist_challenge_response` for an example.
1817
4039 === Installation
4140
4241 ==== 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 :
4544
4645 $ sudo add-apt-repository ppa:yubico/stable
4746 $ sudo apt-get update
5958 $ cd python-yubico-$ver
6059 $ python setup.py install
6160
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
7377
7478 ==== 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.
7882
7983 === License
8084 Copyright (c) Yubico AB.
44
55 import sys
66 import struct
7 import urllib
7
8 import six
89
910 import yubico
1011 import yubico.yubikey_neo_usb_hid
1516
1617 url = sys.argv[1]
1718
19 if sys.version_info >= (3, 0):
20 url = url.encode('utf-8')
21
1822 try:
1923 YK = yubico.yubikey_neo_usb_hid.YubiKeyNEO_USBHID(debug=True)
20 print "Version : %s " % YK.version()
24 print("Version : %s " % YK.version())
2125
2226 ndef = yubico.yubikey_neo_usb_hid.YubiKeyNEO_NDEF(data = url)
2327
24 user_input = raw_input('Write configuration to YubiKey? [y/N] : ')
28 user_input = six.moves.input('Write configuration to YubiKey? [y/N] : ')
2529 if user_input in ('y', 'ye', 'yes'):
2630 YK.write_ndef(ndef)
27 print "\nSuccess!"
31 print("\nSuccess!")
2832 else:
29 print "\nAborted"
33 print("\nAborted")
3034 except yubico.yubico_exception.YubicoError as inst:
31 print "ERROR: %s" % inst.reason
35 print("ERROR: %s" % inst.reason)
3236 sys.exit(1)
66 import sys
77 import struct
88 import yubico
9 import six
910
1011 slot=2
1112
1213 try:
1314 YK = yubico.find_yubikey(debug=True)
14 print "Version : %s " % YK.version()
15 print("Version : %s " % YK.version())
1516
1617 Cfg = YK.init_config()
17 key='h:303132333435363738393a3b3c3d3e3f40414243'
18 key = b'h:303132333435363738393a3b3c3d3e3f40414243'
1819 Cfg.mode_challenge_response(key, type='HMAC', variable=True)
1920 Cfg.extended_flag('SERIAL_API_VISIBLE', True)
2021
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 )
2223 if user_input in ('y', 'ye', 'yes'):
2324 YK.write_config(Cfg, slot=slot)
24 print "\nSuccess!"
25 print("\nSuccess!")
2526 else:
26 print "\nAborted"
27 print("\nAborted")
2728 except yubico.yubico_exception.YubicoError as inst:
28 print "ERROR: %s" % inst.reason
29 print("ERROR: %s" % inst.reason)
2930 sys.exit(1)
77 import yubico
88
99 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'
1212
1313 # turn on YubiKey debug if -v is given as an argument
1414 debug = False
1818 # Look for and initialize the YubiKey
1919 try:
2020 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("")
2424
2525 # 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))
2828
2929 response = YK.challenge_response(secret, slot=2)
3030 except yubico.yubico_exception.YubicoError as inst:
31 print "ERROR: %s" % inst.reason
31 print("ERROR: %s" % inst.reason)
3232 sys.exit(1)
3333
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
3538
3639 # Check if the response matched the expected one
3740 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.")
3942 sys.exit(0)
4043 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.")
4245 sys.exit(1)
2121 import hmac
2222 import argparse
2323 import hashlib
24 import binascii
2425
2526 import yubico
27 import six
2628
2729 from Crypto.Cipher import AES
2830
6971
7072 def init_demo(args):
7173 """ 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) : ")
7375 if hmac_key:
7476 try:
75 hmac_key = hmac_key.decode('hex')
77 hmac_key = binascii.unhexlify(hmac_key)
7678 except:
7779 sys.stderr.write("Could not decode HMAC-SHA1 key. Please enter 40 hex-chars.\n")
7880 sys.exit(1)
8284 sys.stderr.write("Decoded HMAC-SHA1 key is %i bytes, expected 20.\n" %( len(hmac_key)))
8385 sys.exit(1)
8486
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 : ")
9193
9294 secret_dict = {"count": 0,
9395 "passphrase": passphrase,
98100 """ Send a challenge to the YubiKey and use the result to decrypt the state file. """
99101 outer_j = load_state_file(args)
100102 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"]))
103105 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)))
105107 else:
106 print "Response : %s" % (response.encode('hex'))
108 print("Response : %s" % binascii.hexlify(response))
107109 inner_j = decrypt_with_response(args, outer_j["inner"], response)
108110 if args.verbose or args.debug:
109 print "\nDecrypted 'inner' :\n%s\n" % (inner_j)
111 print("\nDecrypted 'inner' :\n%s\n" % (inner_j))
110112
111113 secret_dict = {}
112114 try:
113 secret_dict = json.loads(inner_j)
115 secret_dict = json.loads(inner_j.decode('ascii'))
114116 except ValueError:
115117 sys.stderr.write("\nCould not parse decoded data as JSON, you probably did not produce the right response.\n")
116118 sys.exit(1)
117119
118120 secret_dict["count"] += 1
119121
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)
123125
124126 def get_yubikey_response(args, challenge):
125127 """
126128 Do challenge-response with the YubiKey if one is found. Otherwise prompt user to fake a response. """
127129 try:
128130 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)
130132 return response
131133 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)
136138
137139 def roll_next_challenge(args, hmac_key, inner_dict):
138140 """
139141 When we have the HMAC-SHA1 key in clear, generate a random challenge and compute the
140142 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)
144148
145149 challenge = os.urandom(args.challenge_length)
146150 response = get_response(hmac_key, challenge)
147151
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("")
151155 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')
158162 inner_j = json.dumps(inner_dict, indent = 4)
159163 if args.verbose or args.debug:
160 print "Inner JSON :\n%s\n" % (inner_j)
164 print("Inner JSON :\n%s\n" % (inner_j))
161165 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'),
164168 }
165169 outer_j = json.dumps(outer_dict, indent = 4)
166170 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))
170174 write_state_file(args, outer_j)
171175
172176 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)
174179 h = hmac.new(hmac_key, challenge, hashlib.sha1)
175 return h.hexdigest()
180 return h.digest()
176181
177182 def encrypt_with_response(args, data, key):
178183 """
190195 data += ' ' * (16 - pad)
191196
192197 # 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))
195200 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)
199204 ciphertext = obj.encrypt(data)
200 return ciphertext.encode('hex')
205 return binascii.hexlify(ciphertext)
201206
202207 def decrypt_with_response(args, data, key):
203208 """
205210 """
206211 aes_key = key
207212 try:
208 aes_key = key.decode('hex')
209 except TypeError:
213 aes_key = binascii.unhexlify(key)
214 except (TypeError, binascii.Error):
210215 # was not hex encoded
211216 pass
212217 # need to pad key
213 aes_key += chr(0x0) * (32 - len(aes_key))
218 aes_key += b'\0' * (32 - len(aes_key))
214219 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))
219224 return plaintext
220225
221226 def write_state_file(args, data):
235240 else:
236241 do_challenge(args)
237242
238 print "\nDone\n"
243 print("\nDone\n")
239244
240245 if __name__ == '__main__':
241246 main()
55 import sys
66 import struct
77 import yubico
8 import six
9 import binascii
810
911 slot=2
1012
1113 try:
1214 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())
1517
1618 Cfg = YK.init_config()
1719 Cfg.extended_flag('ALLOW_UPDATE', True)
1820 Cfg.ticket_flag('APPEND_CR', True)
1921 Cfg.extended_flag('SERIAL_API_VISIBLE', True)
20 Cfg.uid = '010203040506'.decode('hex')
22 Cfg.uid = binascii.unhexlify('010203040506')
2123 Cfg.fixed_string("m:ftccftbbftdd")
2224 Cfg.aes_key('h:' + 32 * 'a')
2325
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 )
2527 if user_input in ('y', 'ye', 'yes'):
2628 YK.write_config(Cfg, slot=slot)
27 print "\nSuccess!"
28 print "Status : %s " % YK.status()
29 print("\nSuccess!")
30 print("Status : %s " % YK.status())
2931 else:
30 print "\nAborted"
32 print("\nAborted")
3133 sys.exit(0)
3234
33 raw_input("Press enter to update...")
35 six.moves.input("Press enter to update...")
3436
3537 Cfg = YK.init_config(update=True)
3638 Cfg.ticket_flag('APPEND_CR', False)
3739
3840 print ("Updating...");
3941 YK.write_config(Cfg, slot=slot)
40 print "\nSuccess!"
42 print("\nSuccess!")
4143 except yubico.yubico_exception.YubicoError as inst:
42 print "ERROR: %s" % inst.reason
44 print("ERROR: %s" % inst.reason)
4345 sys.exit(1)
2828 keys = get_all_yubikeys(debug)
2929
3030 if not keys:
31 print "No YubiKey found."
31 print("No YubiKey found.")
3232 else:
3333 n = 1
3434 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()))
3636 n += 1
00 Metadata-Version: 1.1
11 Name: python-yubico
2 Version: 1.2.3
2 Version: 1.3.0
33 Summary: Python code for talking to Yubico's YubiKeys
44 Home-page: https://github.com/Yubico/python-yubico
55 Author: Yubico Open Source Maintainers
22 MANIFEST.in
33 NEWS
44 README
5 release.py
65 setup.cfg
76 setup.py
87 doc/ykdef.h
1716 python_yubico.egg-info/dependency_links.txt
1817 python_yubico.egg-info/requires.txt
1918 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
2421 util/yubikey-totp
2522 util/yubikey-totp.1
2623 yubico/__init__.py
2724 yubico/yubico_exception.py
2825 yubico/yubico_util.py
26 yubico/yubico_version.py
2927 yubico/yubikey.py
28 yubico/yubikey_4_usb_hid.py
29 yubico/yubikey_base.py
3030 yubico/yubikey_config.py
3131 yubico/yubikey_config_util.py
3232 yubico/yubikey_defs.py
3333 yubico/yubikey_frame.py
3434 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
-150
release.py less more
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.
12
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
154
165
176 setup(
187 name='python-yubico',
19 version=get_version(),
208 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',
2312 maintainer='Yubico Open Source Maintainers',
2413 maintainer_email='ossmaint@yubico.com',
2514 url='https://github.com/Yubico/python-yubico',
2615 license='BSD 2 clause',
2716 packages=['yubico'],
2817 install_requires=['pyusb'],
29 tests_require=['nose>=1.0'],
30 test_suite='nose.collector',
31 cmdclass={'release': release},
18 test_suite='test',
3219 classifiers=[
3320 'License :: OSI Approved :: BSD License',
3421 'Operating System :: OS Independent',
0 # Copyright (c) 2010, 2011, 2012 Yubico AB
1 # See the file COPYING for licence statement.
Binary diff not shown
+0
-49
test/test_yubico.py less more
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
-307
test/test_yubikey_config.py less more
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
-58
test/test_yubikey_frame.py less more
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
-56
test/test_yubikey_usb_hid.py less more
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()
3737 import struct
3838 import yubico
3939 import argparse
40 import binascii
4041
4142 default_slot=2
4243 default_time=int(time.time())
9697 """
9798 YK = yubico.find_yubikey(debug=args.debug)
9899 if args.debug or args.verbose:
99 print "Version : %s " % YK.version()
100 print("Version : %s " % YK.version())
100101 if args.debug:
101 print "Serial : %i" % YK.serial()
102 print ""
102 print("Serial : %i" % YK.serial())
103 print("")
103104 # Do challenge-response
104105 secret = struct.pack("> Q", args.time / args.step).ljust(64, chr(0x0))
105106 if args.debug:
106 print "Sending challenge : %s\n" % (secret.encode('hex'))
107 print("Sending challenge : %s\n" % (binascii.hexlify(secret)))
107108 response = YK.challenge_response(secret, slot=args.slot)
108109 # 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))
111111 return totp_str
112112
113113 def main():
117117 otp = None
118118 try:
119119 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))
122122 return 1
123123
124124 if not otp:
125125 return 1
126126
127 print otp
127 print(otp)
128128 return 0
129129
130130 if __name__ == '__main__':
1919 # Copyright (c) 2010, 2011, 2012 Yubico AB
2020 # See the file COPYING for licence statement.
2121
22 __version__ = "1.2.3"
22 from .yubico_version import __version__
2323
2424 __all__ = [
2525 # classes
3939 ]
4040
4141 # 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
2121 'YubiKeyTimeout',
2222 ]
2323
24 from yubico import __version__
24 from .yubico_version import __version__
25
2526
2627 class YubicoError(Exception):
2728 """
4344
4445 pass
4546
47
4648 class InputError(YubicoError):
4749 """
4850 Exception raised for errors in an input to some function.
4951 """
5052 def __init__(self, reason='input validation error'):
51 YubicoError.__init__(self, reason)
53 super(InputError, self).__init__(reason)
1414 # classes
1515 ]
1616
17 from yubico import __version__
18 import yubikey_defs
19 import yubico_exception
17 import sys
2018 import string
19
20 from .yubico_version import __version__
21 from . import yubikey_defs
22 from . import yubico_exception
2123
2224 _CRC_OK_RESIDUAL = 0xf0b8
2325
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
2441 def crc16(data):
2542 """
26 Calculate an ISO13239 CRC checksum of the input buffer.
43 Calculate an ISO13239 CRC checksum of the input buffer (bytestring).
2744 """
2845 m_crc = 0xffff
2946 for this in data:
30 m_crc ^= ord(this)
47 m_crc ^= ord_byte(this)
3148 for _ in range(8):
3249 j = m_crc & 1
3350 m_crc >>= 1
3855 def validate_crc16(data):
3956 """
4057 Validate that the CRC of the contents of buffer is the residual OK value.
58
59 The input is a bytestring.
4160 """
4261 return crc16(data) == _CRC_OK_RESIDUAL
4362
7392 self.enabled = False
7493
7594 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 """
7799 if not src:
78100 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))
81103 offset = 0
82104 result = ''
83105 for this in group(src, length):
84106 if colorize:
85 last, this = this[-1:], this[:-1]
107 last, this = this[-1], this[:-1]
86108 colors = DumpColors()
87109 color = colors.get('RESET')
88 if ord(last) & yubikey_defs.RESP_PENDING_FLAG:
110 if ord_byte(last) & yubikey_defs.RESP_PENDING_FLAG:
89111 # write to key
90112 color = colors.get('BLUE')
91 elif ord(last) & yubikey_defs.SLOT_WRITE_FLAG:
113 elif ord_byte(last) & yubikey_defs.SLOT_WRITE_FLAG:
92114 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)
95117 else:
96 hex_s = ' '.join(["%02x" % ord(x) for x in this])
118 hex_s = ' '.join(["%02x" % ord_byte(x) for x in this])
97119 result += "%04X %s\n" % (offset, hex_s)
98120 offset += length
99121 return result
100122
101123 def group(data, num):
102124 """ 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)]
104126
105127 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")
108135 return data.translate(t_map)
109136
110137 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 """
112142 if len(hmac_result) != 20:
113143 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)
119149 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 })
3030 'YubiKeyTimeout',
3131 ]
3232
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
3538
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
21539
21640 def find_key(debug=False, skip=0):
21741 """
22549 debug -- True or False
22650 """
22751 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)
23861 except YubiKeyUSBHIDError as inst:
23962 if 'No USB YubiKey found' in str(inst):
24063 # 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
1414 'YubiKeyConfig',
1515 ]
1616
17 from yubico import __version__
18
17 from .yubico_version import __version__
18
19 import sys
1920 import struct
2021 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
2849
2950 TicketFlags = [
3051 YubiKeyTicketFlag('TAB_FIRST', 0x01, min_ykver=(1, 0), doc='Send TAB before first part'),
7899 YubiKeyExtendedFlag('DORMANT', 0x40, min_ykver=(2, 3), doc='Dormant configuration (can be woken up and flag removed = requires update flag)'),
79100 ]
80101
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)
98102
99103 class YubiKeyConfigError(yubico_exception.YubicoError):
100104 """
101105 Exception raised for YubiKey configuration errors.
102106 """
103107
104 class YubiKeyConfig():
108
109 class YubiKeyConfig(object):
105110 """
106111 Base class for configuration of all current types of YubiKeys.
107112 """
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):
109115 """
110116 `ykver' is a tuple (major, minor) with the version number of the key
111117 you are planning to apply this configuration to. Not mandated, but
121127
122128 YubiKey >= 2.3 also supports swapping the configurations, making
123129 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.
124133 """
125134 if capabilities is None:
126 self.capabilities = yubikey.YubiKeyCapabilities(default_answer = True)
135 self.capabilities = yubikey_base.YubiKeyCapabilities(default_answer = True)
127136 else:
128137 self.capabilities = capabilities
129138
131140 self.yk_req_version = (0, 0)
132141 self.ykver = ykver
133142
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''
138147
139148 self.ticket_flags = YubiKeyConfigBits(0x0)
140149 self.config_flags = YubiKeyConfigBits(0x0)
141150 self.extended_flags = YubiKeyConfigBits(0x0)
142151
143 self.unlock_code = ''
152 self.unlock_code = b''
144153 self._mode = ''
145154 if update or swap:
146155 self._require_version(major=2, minor=3)
147156 self._update_config = update
148157 self._swap_slots = swap
158 self._zap = zap
149159
150160 return None
151161
152162 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>' % (
154164 self.__class__.__name__,
155165 hex(id(self)),
156166 self._mode,
165175 len(self.unlock_code),
166176 self._update_config,
167177 self._swap_slots,
178 self._zap
168179 )
169180
170181 def version_required(self):
246257 """
247258 Access code to allow re-programming of your YubiKey.
248259
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:'.
250261 The result, after any hex decoding, must be 6 bytes.
251262 """
252 if data.startswith('h:'):
263 if data.startswith(b'h:'):
253264 new = binascii.unhexlify(data[2:])
254265 else:
255266 new = data
269280 Supply data as either a raw string, or a hexlified string prefixed by 'h:'.
270281 The result, after any hex decoding, must be 6 bytes.
271282 """
272 if data.startswith('h:'):
283 if data.startswith(b'h:'):
273284 new = binascii.unhexlify(data[2:])
274285 else:
275286 new = data
283294 Set the YubiKey up for standard OTP validation.
284295 """
285296 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:'):
289300 private_uid = binascii.unhexlify(private_uid[2:])
290301 if len(private_uid) != yubikey_defs.UID_SIZE:
291302 raise yubico_exception.InputError('Private UID must be %i bytes' % (yubikey_defs.UID_SIZE))
301312 Requires YubiKey 2.1.
302313 """
303314 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]))
306317 if digits != 6 and digits != 8:
307318 raise yubico_exception.InputError('OATH-HOTP digits must be 6 or 8')
308319
312323 self.config_flag('OATH_HOTP8', True)
313324 if omp or tt or mui:
314325 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
316327 self.fixed_string(fixed)
317328 if factor_seed:
318329 self.uid = self.uid + struct.pack('<H', factor_seed)
333344 if not type.upper() in ['HMAC', 'OTP']:
334345 raise yubico_exception.InputError('Invalid \'type\' (%s)' % type)
335346 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]))
339350 self._change_mode('CHAL_RESP', major=2, minor=2)
340351 if type.upper() == 'HMAC':
341352 self.config_flag('CHAL_HMAC', True)
357368 flag = _get_flag(which, TicketFlags)
358369 if flag:
359370 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), \
362373 self.capabilities.model, self.ykver[0], self.ykver[1]))
363374 req_major, req_minor = flag.req_version()
364375 self._require_version(major=req_major, minor=req_minor)
380391 flag = _get_flag(which, ConfigFlags)
381392 if flag:
382393 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), \
385396 self.capabilities.model, self.ykver[0], self.ykver[1]))
386397 req_major, req_minor = flag.req_version()
387398 self._require_version(major=req_major, minor=req_minor)
403414 flag = _get_flag(which, ExtendedFlags)
404415 if flag:
405416 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), \
408419 self.capabilities.model, self.ykver[0], self.ykver[1]))
409420 req_major, req_minor = flag.req_version()
410421 self._require_version(major=req_major, minor=req_minor)
418429
419430 def to_string(self):
420431 """
421 Return the current configuration as a string (always 64 bytes).
432 Return the current configuration as a bytestring (always 64 bytes).
422433 """
423434 #define UID_SIZE 6 /* Size of secret ID field */
424435 #define FIXED_SIZE 16 /* Max size of fixed field */
462473 Return the current configuration as a YubiKeyFrame object.
463474 """
464475 data = self.to_string()
465 payload = data.ljust(64, chr(0x0))
476 payload = data.ljust(64, yubico_util.chr_byte(0x0))
466477 if slot is 1:
467478 if self._update_config:
468 command = SLOT_UPDATE1
479 command = SLOT.UPDATE1
469480 else:
470 command = SLOT_CONFIG
481 command = SLOT.CONFIG
471482 elif slot is 2:
472483 if self._update_config:
473 command = SLOT_UPDATE2
484 command = SLOT.UPDATE2
474485 else:
475 command = SLOT_CONFIG2
486 command = SLOT.CONFIG2
476487 else:
477488 assert()
478489
479490 if self._swap_slots:
480 command = SLOT_SWAP
491 command = SLOT.SWAP
492
493 if self._zap:
494 payload = b''
481495
482496 return yubikey_frame.YubiKeyFrame(command=command, payload=payload)
483497
485499 """ Update the minimum version of YubiKey this configuration can be applied to. """
486500 new_ver = (major, minor)
487501 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'
489503 % (major, minor, self.ykver[0], self.ykver[1]))
490504 if new_ver > self.yk_req_version:
491505 self.yk_req_version = new_ver
492506
493507 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:'):
497513 return(binascii.unhexlify(data[2:]))
498514 else:
499515 return(data)
516532 """
517533 Set a 20 bytes key. This is used in CHAL_HMAC and OATH_HOTP mode.
518534
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:'.
520536 The result, after any hex decoding, must be 20 bytes.
521537 """
522 if data.startswith('h:'):
538 if data.startswith(b'h:'):
523539 new = binascii.unhexlify(data[2:])
524540 else:
525541 new = data
528544 self.uid = new[16:]
529545 else:
530546 raise yubico_exception.InputError('HMAC key must be exactly 20 bytes')
547
531548
532549 def _get_flag(which, flags):
533550 """ Find 'which' entry in 'flags'. """
1313 'YubiKeyTicketFlag',
1414 ]
1515
16 class YubiKeyFlag():
16
17 class YubiKeyFlag(object):
1718 """
1819 A flag value, and associated metadata.
1920 """
2021
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']):
2223 """
2324 Metadata about a ticket/config/extended flag bit.
2425
9192 else:
9293 return version >= self.min_ykver
9394
95
9496 class YubiKeyTicketFlag(YubiKeyFlag):
9597 """
9698 A ticket flag value, and associated metadata.
9799 """
100
98101
99102 class YubiKeyConfigFlag(YubiKeyFlag):
100103 """
106109 assert()
107110 self.mode = mode
108111
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
110114
111115 class YubiKeyExtendedFlag(YubiKeyFlag):
112116 """
118122 assert()
119123 self.mode = mode
120124
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)
122126
123 class YubiKeyConfigBits():
127
128 class YubiKeyConfigBits(object):
124129 """
125130 Class to hold bit values for configuration.
126131 """
1313 'SHA1_DIGEST_SIZE',
1414 'OTP_CHALRESP_SIZE',
1515 'UID_SIZE',
16 'YUBICO_VID',
1617 # functions
1718 # classes
19 'SLOT',
20 'MODE',
21 'PID',
22 'YK4_CAPA'
1823 ]
1924
20 from yubico import __version__
25 from .yubico_version import __version__
2126
2227 # Yubikey Low level interface #2.3
2328 RESP_TIMEOUT_WAIT_MASK = 0x1f # Mask to get timeout value
3035 OTP_CHALRESP_SIZE = 16 # Number of bytes returned for an Yubico-OTP challenge (not from ykdef.h)
3136
3237 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
1212
1313 import struct
1414
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
2021
2122 class YubiKeyFrame:
2223 """
2829 flags.
2930 """
3031
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
3435 if len(payload) != 64:
3536 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')
3639 self.payload = payload
3740 self.command = command
3841 self.crc = yubico_util.crc16(payload)
5962 # unsigned short crc;
6063 # unsigned char filler[3];
6164 # } YKFRAME;
62 filler = ''
65 filler = b''
6366 return struct.pack('<64sBH3s',
6467 self.payload, self.command, self.crc, filler)
6568
7780 this, rest = rest[:7], rest[7:]
7881 if seq > 0 and rest:
7982 # 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)
8285 out.append(self._debug_string(debug, this))
8386 else:
84 this += chr(yubikey_defs.SLOT_WRITE_FLAG + seq)
87 this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq)
8588 out.append(self._debug_string(debug, this))
8689 seq += 1
8790 return out
9295 """
9396 if not debug:
9497 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:
103107 return (data, "FFFFFFF")
104 if ord(data[-1]) == 0x81:
108 if yubico_util.ord_byte(data[-1]) == 0x81:
105109 return (data, "FFFFFFF")
106 if ord(data[-1]) == 0x82:
110 if yubico_util.ord_byte(data[-1]) == 0x82:
107111 return (data, "FFUUUUU")
108 if ord(data[-1]) == 0x83:
112 if yubico_util.ord_byte(data[-1]) == 0x83:
109113 return (data, "UKKKKKK")
110 if ord(data[-1]) == 0x84:
114 if yubico_util.ord_byte(data[-1]) == 0x84:
111115 return (data, "KKKKKKK")
112 if ord(data[-1]) == 0x85:
116 if yubico_util.ord_byte(data[-1]) == 0x85:
113117 return (data, "KKKAAAA")
114 if ord(data[-1]) == 0x86:
118 if yubico_util.ord_byte(data[-1]) == 0x86:
115119 return (data, "AAlETCr")
116 if ord(data[-1]) == 0x87:
120 if yubico_util.ord_byte(data[-1]) == 0x87:
117121 return (data, "rCR")
118122 # after payload
119 if ord(data[-1]) == 0x89:
123 if yubico_util.ord_byte(data[-1]) == 0x89:
120124 return (data, " Scr")
121125 else:
122126 return (data, '')
1414 ]
1515
1616 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
2226
2327 # 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
2629 _NDEF_DATA_SIZE = 54
2730
2831 # from nfcdef.h
6871 (0x23, "urn:nfc:",),
6972 ]
7073
74 _NDEF_SLOTS = {
75 1: SLOT.NDEF,
76 2: SLOT.NDEF2
77 }
78
79
7180 class YubiKeyNEO_USBHIDError(yubico_exception.YubicoError):
7281 """ Exception raised for errors with the NEO USB HID communication. """
7382
83
7484 class YubiKeyNEO_USBHIDCapabilities(yubikey_usb_hid.YubiKeyUSBHIDCapabilities):
7585 """
76 Capabilities of current YubiKey NEO BETA firmwares 2.1.4 and 2.1.5.
86 Capabilities of current YubiKey NEO.
7787 """
7888
7989 def have_challenge_response(self, mode):
80 return False
90 return self.version >= (3, 0, 0)
8191
8292 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
87114
88115 class YubiKeyNEO_USBHID(yubikey_usb_hid.YubiKeyUSBHID):
89116 """
97124
98125 model = 'YubiKey NEO'
99126 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):
102130 """
103131 Find and connect to a YubiKey NEO (USB HID).
104132
106134 skip -- number of YubiKeys to skip
107135 debug -- True or False
108136 """
109 yubikey_usb_hid.YubiKeyUSBHID.__init__(self, debug, skip)
137 super(YubiKeyNEO_USBHID, self).__init__(debug, skip, hid_device)
110138 if self.version_num() >= (2, 1, 4,) and \
111139 self.version_num() <= (2, 1, 9,):
112140 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 """
118146 Write an NDEF tag configuration to the YubiKey NEO.
119147 """
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):
123171 """
124172 Class allowing programming of a YubiKey NEO NDEF.
125173 """
126174
127175 ndef_type = _NDEF_URI_TYPE
128176 ndef_str = None
129 access_code = chr(0x0) * _ACC_CODE_SIZE
177 access_code = yubico_util.chr_byte(0x0) * _ACC_CODE_SIZE
130178 # For _NDEF_URI_TYPE
131179 ndef_uri_rt = 0x0 # No prepending
132180 # For _NDEF_TEXT_TYPE
133 ndef_text_lang = 'en'
181 ndef_text_lang = b'en'
134182 ndef_text_enc = 'UTF-8'
135183
136184 def __init__(self, data, access_code = None):
186234 first = struct.pack(fmt,
187235 len(data),
188236 self.ndef_type,
189 data.ljust(_NDEF_DATA_SIZE, chr(0x0)),
237 data.ljust(_NDEF_DATA_SIZE, b'\0'),
190238 self.access_code,
191239 )
192240 #crc = 0xffff - yubico_util.crc16(first)
193241 #second = first + struct.pack('<H', crc) + self.unlock_code
194242 return first
195243
196 def to_frame(self, slot=_SLOT_NDEF):
244 def to_frame(self, slot=SLOT.NDEF):
197245 """
198246 Return the current configuration as a YubiKeyFrame object.
199247 """
200248 data = self.to_string()
201 payload = data.ljust(64, chr(0x0))
249 payload = data.ljust(64, b'\0')
202250 return yubikey_frame.YubiKeyFrame(command = slot, payload = payload)
203251
204252 def _encode_ndef_uri_type(self, data):
210258 """
211259 t = 0x0
212260 for (code, prefix) in uri_identifiers:
213 if data[:len(prefix)].lower() == prefix:
261 if data[:len(prefix)].decode('latin-1').lower() == prefix:
214262 t = code
215263 data = data[len(prefix):]
216264 break
217 data = chr(t) + data
265 data = yubico_util.chr_byte(t) + data
218266 return data
219267
220268 def _encode_ndef_text_params(self, data):
225273 status = len(self.ndef_text_lang)
226274 if self.ndef_text_enc == 'UTF16':
227275 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)
1313 'YubiKeyUSBHIDStatus',
1414 ]
1515
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
2526 import struct
2627 import time
2728 import sys
4142 # from ykcore_backend.h
4243 _FEATURE_RPT_SIZE = 8
4344 _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
7845
7946 # 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},
8249 }
8350
8451 class YubiKeyUSBHIDError(yubico_exception.YubicoError):
8552 """ Exception raised for errors with the USB HID communication. """
8653
87 class YubiKeyUSBHIDCapabilities(yubikey.YubiKeyCapabilities):
54
55 class YubiKeyUSBHIDCapabilities(yubikey_base.YubiKeyCapabilities):
8856 """
8957 Capture the capabilities of the various versions of YubiKeys.
9058
9260 in one or more versions, leaving the other ones at False through default_answer.
9361 """
9462 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)
9867
9968 def have_yubico_OTP(self):
10069 """ Yubico OTP support has always been available in the standard YubiKey. """
134103 def have_configuration_slot(self, slot):
135104 return (slot in [1, 2])
136105
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 """
153111
154112 def __init__(self, debug=False, skip=0):
155113 """
159117 skip -- number of YubiKeys to skip
160118 debug -- True or False
161119 """
162 YubiKey.__init__(self, debug)
120 self.debug = debug
163121 self._usb_handle = None
164122 if not self._open(skip):
165123 raise YubiKeyUSBHIDError('YubiKey USB HID initialization failed')
166124 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
171133
172134 def __del__(self):
173135 try:
175137 self._close()
176138 except IOError:
177139 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]
276140
277141 def _write_config(self, cfg, slot):
278142 """ Write configuration to YubiKey. """
285149 # make sure we have a fresh pgm_seq value
286150 self.status()
287151 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))
291161
292162 def _read_response(self, may_block=False):
293163 """ Wait for a response to become available, and read it. """
296166 # continue reading while response pending is set
297167 while True:
298168 this = self._read()
299 flags = ord(this[7])
169 flags = yubico_util.ord_byte(this[7])
300170 if flags & yubikey_defs.RESP_PENDING_FLAG:
301171 seq = flags & 0b00011111
302172 if res and (seq == 0):
320190 self._debug("Failed reading %i bytes (got %i) from USB HID YubiKey.\n"
321191 % (_FEATURE_RPT_SIZE, recv))
322192 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)
324194 self._debug("READ : %s" % (yubico_util.hexdump(data, colorize=True)))
325195 return data
326196
343213 """
344214 Reset read mode by issuing a dummy write.
345215 """
346 data = '\x00\x00\x00\x00\x00\x00\x00\x8f'
216 data = b'\x00\x00\x00\x00\x00\x00\x00\x8f'
347217 self._raw_write(data)
348218 self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG)
349219 return True
399269 wait_num = (timeout * 2) - 1 + 6
400270 resp_timeout = False # YubiKey hasn't indicated RESP_TIMEOUT (yet)
401271 while not finished:
272 time.sleep(sleep)
402273 this = self._read()
403 flags = ord(this[7])
274 flags = yubico_util.ord_byte(this[7])
404275
405276 if flags & yubikey_defs.RESP_TIMEOUT_WAIT_FLAG:
406277 if not resp_timeout:
435306 reason = 'Timed out waiting for YubiKey to clear status 0x%x' % mask
436307 else:
437308 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)
440310 sleep = min(sleep + sleep, 0.5)
441311 else:
442312 return this
454324 try:
455325 self._usb_handle = usb_device.open()
456326 self._usb_handle.detachKernelDriver(0)
457 except Exception, error:
327 except Exception as error:
458328 if 'could not detach kernel driver from interface' in str(error):
459329 self._debug('The in-kernel-HID driver has already been detached\n')
460330 else:
493363 import usb.core
494364 import usb.legacy
495365 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)]
497367 except ImportError:
498368 # Using PyUsb < 1.0.
499369 import usb
500370 devices = [d for bus in usb.busses() for d in bus.devices]
501371 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):
504374 if skip == 0:
505375 return device
506376 skip -= 1
513383 pre = self.__class__.__name__
514384 if hasattr(self, 'debug_prefix'):
515385 pre = getattr(self, 'debug_prefix')
516 sys.stderr.write("%s: " % (self.__class__.__name__))
386 sys.stderr.write("%s: " % pre)
517387 sys.stderr.write(out)
518388
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):
520527 """ Class to represent the status information we get from the YubiKey. """
521528
522529 CONFIG1_VALID = 0x01 # Bit in touchLevel indicating that configuration 1 is valid (from firmware 2.1)
578585 res.append(2)
579586 return res
580587
588
581589 class YubiKeyConfigUSBHID(yubikey_config.YubiKeyConfig):
582590 """
583591 Configuration class for USB HID YubiKeys.
584592 """
585593 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)