From 9039356ba6a08c21ac6b25200dc58e3d14ab1367 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 3 Jul 2015 14:10:55 +0200 Subject: [PATCH] Add Python3 compatibility to util and examples - Use print as a function - Use 'as' syntax when catching exceptions - Use six.moves.input instead of raw_input - Split text & bytes - Use binascii.hexlify instead of encode('hex') Also, add iv argument to AES.new. It is required in newer versions of PyCrypto. --- examples/configure_neo_ndef | 16 +++--- examples/configure_nist_test_key | 13 ++--- examples/nist_challenge_response | 25 +++++---- examples/rolling_challenge_response | 105 +++++++++++++++++++----------------- examples/update_cfg_remove_cr | 22 ++++---- examples/yubikey-inventory | 4 +- util/yubikey-totp | 18 +++---- yubico/yubikey_frame.py | 18 +++---- yubico/yubikey_neo_usb_hid.py | 2 +- 9 files changed, 119 insertions(+), 104 deletions(-) diff --git a/examples/configure_neo_ndef b/examples/configure_neo_ndef index 81bd927..44fc87d 100755 --- a/examples/configure_neo_ndef +++ b/examples/configure_neo_ndef @@ -5,7 +5,8 @@ Set up a YubiKey NEO NDEF import sys import struct -import urllib + +import six import yubico import yubico.yubikey_neo_usb_hid @@ -16,18 +17,21 @@ if len(sys.argv) != 2: url = sys.argv[1] +if sys.version_info >= (3, 0): + url = url.encode('utf-8') + try: YK = yubico.yubikey_neo_usb_hid.YubiKeyNEO_USBHID(debug=True) - print "Version : %s " % YK.version() + print("Version : %s " % YK.version()) ndef = yubico.yubikey_neo_usb_hid.YubiKeyNEO_NDEF(data = url) - user_input = raw_input('Write configuration to YubiKey? [y/N] : ') + user_input = six.moves.input('Write configuration to YubiKey? [y/N] : ') if user_input in ('y', 'ye', 'yes'): YK.write_ndef(ndef) - print "\nSuccess!" + print("\nSuccess!") else: - print "\nAborted" + print("\nAborted") except yubico.yubico_exception.YubicoError as inst: - print "ERROR: %s" % inst.reason + print("ERROR: %s" % inst.reason) sys.exit(1) diff --git a/examples/configure_nist_test_key b/examples/configure_nist_test_key index 8bb80b6..ae0dd22 100755 --- a/examples/configure_nist_test_key +++ b/examples/configure_nist_test_key @@ -7,24 +7,25 @@ Set up a YubiKey with a NIST PUB 198 A.2 import sys import struct import yubico +import six slot=2 try: YK = yubico.find_yubikey(debug=True) - print "Version : %s " % YK.version() + print("Version : %s " % YK.version()) Cfg = YK.init_config() - key='h:303132333435363738393a3b3c3d3e3f40414243' + key = b'h:303132333435363738393a3b3c3d3e3f40414243' Cfg.mode_challenge_response(key, type='HMAC', variable=True) Cfg.extended_flag('SERIAL_API_VISIBLE', True) - user_input = raw_input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) + user_input = six.moves.input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) if user_input in ('y', 'ye', 'yes'): YK.write_config(Cfg, slot=slot) - print "\nSuccess!" + print("\nSuccess!") else: - print "\nAborted" + print("\nAborted") except yubico.yubico_exception.YubicoError as inst: - print "ERROR: %s" % inst.reason + print("ERROR: %s" % inst.reason) sys.exit(1) diff --git a/examples/nist_challenge_response b/examples/nist_challenge_response index 0c42ea2..f5ea188 100755 --- a/examples/nist_challenge_response +++ b/examples/nist_challenge_response @@ -8,8 +8,8 @@ import sys import yubico expected = \ - '\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82' + \ - '\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24' + b'\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82' + \ + b'\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24' # turn on YubiKey debug if -v is given as an argument debug = False @@ -19,25 +19,28 @@ if len(sys.argv) > 1: # Look for and initialize the YubiKey try: YK = yubico.find_yubikey(debug=debug) - print "Version : %s " % YK.version() - print "Serial : %i" % YK.serial() - print "" + print("Version : %s " % YK.version()) + print("Serial : %i" % YK.serial()) + print("") # Do challenge-response - secret = 'Sample #2'.ljust(64, chr(0x0)) - print "Sending challenge : %s\n" % repr(secret) + secret = b'Sample #2'.ljust(64, b'\0') + print("Sending challenge : %s\n" % repr(secret)) response = YK.challenge_response(secret, slot=2) except yubico.yubico_exception.YubicoError as inst: - print "ERROR: %s" % inst.reason + print("ERROR: %s" % inst.reason) sys.exit(1) -print "Response :\n%s\n" % yubico.yubico_util.hexdump(response) +print("Response :\n%s\n" % yubico.yubico_util.hexdump(response)) + +# Workaround for http://bugs.python.org/issue24596 +del YK # Check if the response matched the expected one if response == expected: - print "OK! Response matches the NIST PUB 198 A.2 expected response." + print("OK! Response matches the NIST PUB 198 A.2 expected response.") sys.exit(0) else: - print "ERROR! Response does NOT match the NIST PUB 198 A.2 expected response." + print("ERROR! Response does NOT match the NIST PUB 198 A.2 expected response.") sys.exit(1) diff --git a/examples/rolling_challenge_response b/examples/rolling_challenge_response index c383f00..c371392 100755 --- a/examples/rolling_challenge_response +++ b/examples/rolling_challenge_response @@ -22,8 +22,10 @@ import json import hmac import argparse import hashlib +import binascii import yubico +import six from Crypto.Cipher import AES @@ -70,10 +72,10 @@ def parse_args(): def init_demo(args): """ Initializes the demo by asking a few questions and creating a new stat file. """ - hmac_key = raw_input("Enter HMAC-SHA1 key as 40 chars of hex (or press enter for random key) : ") + hmac_key = six.moves.input("Enter HMAC-SHA1 key as 40 chars of hex (or press enter for random key) : ") if hmac_key: try: - hmac_key = hmac_key.decode('hex') + hmac_key = binascii.unhexlify(hmac_key) except: sys.stderr.write("Could not decode HMAC-SHA1 key. Please enter 40 hex-chars.\n") sys.exit(1) @@ -83,12 +85,12 @@ def init_demo(args): sys.stderr.write("Decoded HMAC-SHA1 key is %i bytes, expected 20.\n" %( len(hmac_key))) sys.exit(1) - print "To program a YubiKey >= 2.2 for challenge-response with this key, use :" - print "" - print " $ ykpersonalize -%i -ochal-resp -ochal-hmac -ohmac-lt64 -a %s" % (args.slot, hmac_key.encode('hex')) - print "" + print("To program a YubiKey >= 2.2 for challenge-response with this key, use :") + print("") + print(" $ ykpersonalize -%i -ochal-resp -ochal-hmac -ohmac-lt64 -a %s" % (args.slot, binascii.hexlify(hmac_key).decode('ascii'))) + print("") - passphrase = raw_input("Enter the secret passphrase to protect with the rolling challenges : ") + passphrase = six.moves.input("Enter the secret passphrase to protect with the rolling challenges : ") secret_dict = {"count": 0, "passphrase": passphrase, @@ -99,81 +101,84 @@ def do_challenge(args): """ Send a challenge to the YubiKey and use the result to decrypt the state file. """ outer_j = load_state_file(args) challenge = outer_j["challenge"] - print "Challenge : %s" % (challenge) - response = get_yubikey_response(args, outer_j["challenge"].decode('hex')) + print("Challenge : %s" % (challenge)) + response = get_yubikey_response(args, binascii.unhexlify(outer_j["challenge"])) if args.debug or args.verbose: - print "\nGot %i bytes response %s\n" % (len(response), response.encode('hex')) + print("\nGot %i bytes response %s\n" % (len(response), binascii.hexlify(response))) else: - print "Response : %s" % (response.encode('hex')) + print("Response : %s" % binascii.hexlify(response)) inner_j = decrypt_with_response(args, outer_j["inner"], response) if args.verbose or args.debug: - print "\nDecrypted 'inner' :\n%s\n" % (inner_j) + print("\nDecrypted 'inner' :\n%s\n" % (inner_j)) secret_dict = {} try: - secret_dict = json.loads(inner_j) + secret_dict = json.loads(inner_j.decode('ascii')) except ValueError: sys.stderr.write("\nCould not parse decoded data as JSON, you probably did not produce the right response.\n") sys.exit(1) secret_dict["count"] += 1 - print "\nThe passphrase protected using rolling challenges is :\n" - print "\t%s\n\nAccessed %i times.\n" % (secret_dict["passphrase"], secret_dict["count"]) - roll_next_challenge(args, secret_dict["hmac_key"].decode('hex'), secret_dict) + print("\nThe passphrase protected using rolling challenges is :\n") + print("\t%s\n\nAccessed %i times.\n" % (secret_dict["passphrase"], secret_dict["count"])) + roll_next_challenge(args, binascii.unhexlify(secret_dict["hmac_key"]), secret_dict) def get_yubikey_response(args, challenge): """ Do challenge-response with the YubiKey if one is found. Otherwise prompt user to fake a response. """ try: YK = yubico.find_yubikey(debug = args.debug) - response = YK.challenge_response(challenge.ljust(64, chr(0x0)), slot = args.slot) + response = YK.challenge_response(challenge.ljust(64, b'\0'), slot = args.slot) return response except yubico.yubico_exception.YubicoError as e: - print "YubiKey challenge-response failed (%s)" % e.reason - print "" - response = raw_input("Assuming you do not have a YubiKey. Enter repsonse manually (hex encoded) : ") - return response + print("YubiKey challenge-response failed (%s)" % e.reason) + print("") + response = six.moves.input("Assuming you do not have a YubiKey. Enter repsonse manually (hex encoded) : ") + return binascii.unhexlify(response) def roll_next_challenge(args, hmac_key, inner_dict): """ When we have the HMAC-SHA1 key in clear, generate a random challenge and compute the expected response for that challenge. + + hmac_key is a 20-byte bytestring """ - if len(hmac_key) != 20: - hmac_key = hmac_key.decode('hex') + if len(hmac_key) != 20 or not isinstance(hmac_key, bytes): + hmac_key = binascii.unhexlify(hmac_key) challenge = os.urandom(args.challenge_length) response = get_response(hmac_key, challenge) - print "Generated challenge : %s" % (challenge.encode('hex')) - print "Expected response : %s (sssh, don't tell anyone)" % (response) - print "" + print("Generated challenge : %s" % binascii.hexlify(challenge).decode('ascii')) + print("Expected response : %s (sssh, don't tell anyone)" % binascii.hexlify(response).decode('ascii')) + print("") if args.debug or args.verbose or args.init: - print "To manually verify that your YubiKey produces this response, use :" - print "" - print " $ ykchalresp -%i -x %s" % (args.slot, challenge.encode('hex')) - print "" + print("To manually verify that your YubiKey produces this response, use :") + print("") + print(" $ ykchalresp -%i -x %s" % (args.slot, binascii.hexlify(challenge).decode('ascii'))) + print("") - inner_dict["hmac_key"] = hmac_key.encode('hex') + inner_dict["hmac_key"] = binascii.hexlify(hmac_key).decode('ascii') inner_j = json.dumps(inner_dict, indent = 4) if args.verbose or args.debug: - print "Inner JSON :\n%s\n" % (inner_j) + print("Inner JSON :\n%s\n" % (inner_j)) inner_ciphertext = encrypt_with_response(args, inner_j, response) - outer_dict = {"challenge": challenge.encode('hex'), - "inner": inner_ciphertext, + outer_dict = {"challenge": binascii.hexlify(challenge).decode('ascii'), + "inner": inner_ciphertext.decode('ascii'), } outer_j = json.dumps(outer_dict, indent = 4) if args.verbose or args.debug: - print "\nOuter JSON :\n%s\n" % (outer_j) + print("\nOuter JSON :\n%s\n" % (outer_j)) - print "Saving 'outer' JSON to file '%s'" % (args.filename) + print("Saving 'outer' JSON to file '%s'" % (args.filename)) write_state_file(args, outer_j) def get_response(hmac_key, challenge): - """ Compute the expected response for `challenge'. """ + """ Compute the expected response for `challenge', as hexadecimal string """ + print(binascii.hexlify(hmac_key), binascii.hexlify(challenge), hashlib.sha1) h = hmac.new(hmac_key, challenge, hashlib.sha1) - return h.hexdigest() + return h.digest() def encrypt_with_response(args, data, key): """ @@ -191,14 +196,14 @@ def encrypt_with_response(args, data, key): data += ' ' * (16 - pad) # need to pad key as well - aes_key = key.decode('hex') - aes_key += chr(0x0) * (32 - len(aes_key)) + aes_key = key + aes_key += b'\0' * (32 - len(aes_key)) if args.debug: - print ("AES-CBC encrypting 'inner' with key (%i bytes) : %s" % (len(aes_key), aes_key.encode('hex'))) + print(("AES-CBC encrypting 'inner' with key (%i bytes) : %s" % (len(aes_key), binascii.hexlify(aes_key)))) - obj = AES.new(aes_key, AES.MODE_CBC) + obj = AES.new(aes_key, AES.MODE_CBC, b'\0' * 16) ciphertext = obj.encrypt(data) - return ciphertext.encode('hex') + return binascii.hexlify(ciphertext) def decrypt_with_response(args, data, key): """ @@ -206,17 +211,17 @@ def decrypt_with_response(args, data, key): """ aes_key = key try: - aes_key = key.decode('hex') - except TypeError: + aes_key = binascii.unhexlify(key) + except (TypeError, binascii.Error): # was not hex encoded pass # need to pad key - aes_key += chr(0x0) * (32 - len(aes_key)) + aes_key += b'\0' * (32 - len(aes_key)) if args.debug: - print ("AES-CBC decrypting 'inner' using key (%i bytes) : %s" % (len(aes_key), aes_key.encode('hex'))) + print(("AES-CBC decrypting 'inner' using key (%i bytes) : %s" % (len(aes_key), binascii.hexlify(aes_key)))) - obj = AES.new(aes_key, AES.MODE_CBC) - plaintext = obj.decrypt(data.decode('hex')) + obj = AES.new(aes_key, AES.MODE_CBC, b'\0' * 16) + plaintext = obj.decrypt(binascii.unhexlify(data)) return plaintext def write_state_file(args, data): @@ -236,7 +241,7 @@ def main(): else: do_challenge(args) - print "\nDone\n" + print("\nDone\n") if __name__ == '__main__': main() diff --git a/examples/update_cfg_remove_cr b/examples/update_cfg_remove_cr index bc27849..78012b3 100755 --- a/examples/update_cfg_remove_cr +++ b/examples/update_cfg_remove_cr @@ -6,39 +6,41 @@ Set up a YubiKey for standard OTP with CR, then remove it. import sys import struct import yubico +import six +import binascii slot=2 try: YK = yubico.find_yubikey(debug=True) - print "Version : %s " % YK.version() - print "Status : %s " % YK.status() + print("Version : %s " % YK.version()) + print("Status : %s " % YK.status()) Cfg = YK.init_config() Cfg.extended_flag('ALLOW_UPDATE', True) Cfg.ticket_flag('APPEND_CR', True) Cfg.extended_flag('SERIAL_API_VISIBLE', True) - Cfg.uid = '010203040506'.decode('hex') + Cfg.uid = binascii.unhexlify('010203040506') Cfg.fixed_string("m:ftccftbbftdd") Cfg.aes_key('h:' + 32 * 'a') - user_input = raw_input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) + user_input = six.moves.input('Write configuration to slot %i of YubiKey? [y/N] : ' % slot ) if user_input in ('y', 'ye', 'yes'): YK.write_config(Cfg, slot=slot) - print "\nSuccess!" - print "Status : %s " % YK.status() + print("\nSuccess!") + print("Status : %s " % YK.status()) else: - print "\nAborted" + print("\nAborted") sys.exit(0) - raw_input("Press enter to update...") + six.moves.input("Press enter to update...") Cfg = YK.init_config(update=True) Cfg.ticket_flag('APPEND_CR', False) print ("Updating..."); YK.write_config(Cfg, slot=slot) - print "\nSuccess!" + print("\nSuccess!") except yubico.yubico_exception.YubicoError as inst: - print "ERROR: %s" % inst.reason + print("ERROR: %s" % inst.reason) sys.exit(1) diff --git a/examples/yubikey-inventory b/examples/yubikey-inventory index 0b34a22..e36414d 100755 --- a/examples/yubikey-inventory +++ b/examples/yubikey-inventory @@ -29,9 +29,9 @@ if len(sys.argv) > 1: keys = get_all_yubikeys(debug) if not keys: - print "No YubiKey found." + print("No YubiKey found.") else: n = 1 for this in keys: - print "YubiKey #%02i : %s %s" % (n, this.description, this.status()) + print("YubiKey #%02i : %s %s" % (n, this.description, this.status())) n += 1 diff --git a/util/yubikey-totp b/util/yubikey-totp index f02ad07..9ace901 100755 --- a/util/yubikey-totp +++ b/util/yubikey-totp @@ -38,6 +38,7 @@ import time import struct import yubico import argparse +import binascii default_slot=2 default_time=int(time.time()) @@ -97,18 +98,17 @@ def make_totp(args): """ YK = yubico.find_yubikey(debug=args.debug) if args.debug or args.verbose: - print "Version : %s " % YK.version() + print("Version : %s " % YK.version()) if args.debug: - print "Serial : %i" % YK.serial() - print "" + print("Serial : %i" % YK.serial()) + print("") # Do challenge-response secret = struct.pack("> Q", args.time / args.step).ljust(64, chr(0x0)) if args.debug: - print "Sending challenge : %s\n" % (secret.encode('hex')) + print("Sending challenge : %s\n" % (binascii.hexlify(secret))) response = YK.challenge_response(secret, slot=args.slot) # format with appropriate number of leading zeros - fmt = "%." + str(args.digits) + "i" - totp_str = fmt % (yubico.yubico_util.hotp_truncate(response, length=args.digits)) + totp_str = '%.*i' % (args.digits, yubico.yubico_util.hotp_truncate(response, length=args.digits)) return totp_str def main(): @@ -118,14 +118,14 @@ def main(): otp = None try: otp = make_totp(args) - except yubico.yubico_exception.YubicoError, e: - print "ERROR: %s" % (e.reason) + except yubico.yubico_exception.YubicoError as e: + print("ERROR: %s" % (e.reason)) return 1 if not otp: return 1 - print otp + print(otp) return 0 if __name__ == '__main__': diff --git a/yubico/yubikey_frame.py b/yubico/yubikey_frame.py index c43c6a1..f095d20 100644 --- a/yubico/yubikey_frame.py +++ b/yubico/yubikey_frame.py @@ -101,24 +101,24 @@ def _debug_string(self, debug, data): yubikey_defs.SLOT_SWAP, ]: # annotate according to config_st (see yubikey_defs.to_string()) - if ord(data[-1]) == 0x80: + if yubico_util.ord_byte(data[-1]) == 0x80: return (data, "FFFFFFF") - if ord(data[-1]) == 0x81: + if yubico_util.ord_byte(data[-1]) == 0x81: return (data, "FFFFFFF") - if ord(data[-1]) == 0x82: + if yubico_util.ord_byte(data[-1]) == 0x82: return (data, "FFUUUUU") - if ord(data[-1]) == 0x83: + if yubico_util.ord_byte(data[-1]) == 0x83: return (data, "UKKKKKK") - if ord(data[-1]) == 0x84: + if yubico_util.ord_byte(data[-1]) == 0x84: return (data, "KKKKKKK") - if ord(data[-1]) == 0x85: + if yubico_util.ord_byte(data[-1]) == 0x85: return (data, "KKKAAAA") - if ord(data[-1]) == 0x86: + if yubico_util.ord_byte(data[-1]) == 0x86: return (data, "AAlETCr") - if ord(data[-1]) == 0x87: + if yubico_util.ord_byte(data[-1]) == 0x87: return (data, "rCR") # after payload - if ord(data[-1]) == 0x89: + if yubico_util.ord_byte(data[-1]) == 0x89: return (data, " Scr") else: return (data, '') diff --git a/yubico/yubikey_neo_usb_hid.py b/yubico/yubikey_neo_usb_hid.py index 88fceba..7030c59 100644 --- a/yubico/yubikey_neo_usb_hid.py +++ b/yubico/yubikey_neo_usb_hid.py @@ -188,7 +188,7 @@ def to_string(self): first = struct.pack(fmt, len(data), self.ndef_type, - data.ljust(_NDEF_DATA_SIZE, chr(0x0)), + data.ljust(_NDEF_DATA_SIZE, b'\0'), self.access_code, ) #crc = 0xffff - yubico_util.crc16(first)