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)