Blob Blame History Raw
From 9039356ba6a08c21ac6b25200dc58e3d14ab1367 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori@redhat.com>
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)