From 90e1c46dfa574ebaebeb1aca789fe5918789bdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Mon, 20 Jul 2015 11:01:44 +0200 Subject: [PATCH] Add Python 3 subpackage (#1244237) --- python-yubico-python3.patch | 2115 +++++++++++++++++++++ python-yubico-python3_util_examples.patch | 547 ++++++ python-yubico.spec | 85 +- 3 files changed, 2742 insertions(+), 5 deletions(-) create mode 100644 python-yubico-python3.patch create mode 100644 python-yubico-python3_util_examples.patch diff --git a/python-yubico-python3.patch b/python-yubico-python3.patch new file mode 100644 index 0000000..88adead --- /dev/null +++ b/python-yubico-python3.patch @@ -0,0 +1,2115 @@ +From 1d38bede51448043ccda379adc6a254ddd313893 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 1 Jul 2015 15:04:18 +0200 +Subject: [PATCH 1/6] Breaking circular imports: Move __version__ to a + dedicated module + +--- + setup.py | 2 +- + yubico/__init__.py | 2 +- + yubico/yubico_exception.py | 2 +- + yubico/yubico_util.py | 2 +- + yubico/yubico_version.py | 1 + + yubico/yubikey.py | 2 +- + yubico/yubikey_config.py | 2 +- + yubico/yubikey_defs.py | 2 +- + yubico/yubikey_frame.py | 2 +- + yubico/yubikey_neo_usb_hid.py | 2 +- + yubico/yubikey_usb_hid.py | 2 +- + 11 files changed, 11 insertions(+), 10 deletions(-) + create mode 100644 yubico/yubico_version.py + +diff --git a/setup.py b/setup.py +index cc3e92e..0b08fec 100644 +--- a/setup.py ++++ b/setup.py +@@ -10,7 +10,7 @@ + def get_version(): + """Return the current version as defined by yubico/__init__.py.""" + +- with open('yubico/__init__.py', 'r') as f: ++ with open('yubico/yubico_version.py', 'r') as f: + match = VERSION_PATTERN.search(f.read()) + return match.group(1) + +diff --git a/yubico/__init__.py b/yubico/__init__.py +index 0f868b2..738897e 100644 +--- a/yubico/__init__.py ++++ b/yubico/__init__.py +@@ -20,7 +20,7 @@ + # Copyright (c) 2010, 2011, 2012 Yubico AB + # See the file COPYING for licence statement. + +-__version__ = "1.2.3" ++from yubico_version import __version__ + + __all__ = [ + # classes +diff --git a/yubico/yubico_exception.py b/yubico/yubico_exception.py +index bb36f0d..3adba6c 100644 +--- a/yubico/yubico_exception.py ++++ b/yubico/yubico_exception.py +@@ -22,7 +22,7 @@ class for exceptions used in the other Yubico modules + 'YubiKeyTimeout', + ] + +-from yubico import __version__ ++from yubico_version import __version__ + + class YubicoError(Exception): + """ +diff --git a/yubico/yubico_util.py b/yubico/yubico_util.py +index 2f3f1bc..8e13f32 100644 +--- a/yubico/yubico_util.py ++++ b/yubico/yubico_util.py +@@ -15,7 +15,7 @@ + # classes + ] + +-from yubico import __version__ ++from yubico_version import __version__ + import yubikey_defs + import yubico_exception + import string +diff --git a/yubico/yubico_version.py b/yubico/yubico_version.py +new file mode 100644 +index 0000000..10aa336 +--- /dev/null ++++ b/yubico/yubico_version.py +@@ -0,0 +1 @@ ++__version__ = "1.2.3" +diff --git a/yubico/yubikey.py b/yubico/yubikey.py +index a44dabc..c576e80 100644 +--- a/yubico/yubikey.py ++++ b/yubico/yubikey.py +@@ -31,7 +31,7 @@ + 'YubiKeyTimeout', + ] + +-from yubico import __version__ ++from yubico_version import __version__ + import yubico_exception + + class YubiKeyError(yubico_exception.YubicoError): +diff --git a/yubico/yubikey_config.py b/yubico/yubikey_config.py +index 0f08e3f..4f9ae29 100644 +--- a/yubico/yubikey_config.py ++++ b/yubico/yubikey_config.py +@@ -15,7 +15,7 @@ + 'YubiKeyConfig', + ] + +-from yubico import __version__ ++from yubico_version import __version__ + + import struct + import binascii +diff --git a/yubico/yubikey_defs.py b/yubico/yubikey_defs.py +index 74f5bb8..843b8d9 100644 +--- a/yubico/yubikey_defs.py ++++ b/yubico/yubikey_defs.py +@@ -18,7 +18,7 @@ + # classes + ] + +-from yubico import __version__ ++from yubico_version import __version__ + + # Yubikey Low level interface #2.3 + RESP_TIMEOUT_WAIT_MASK = 0x1f # Mask to get timeout value +diff --git a/yubico/yubikey_frame.py b/yubico/yubikey_frame.py +index 018f9c7..c34b4ec 100644 +--- a/yubico/yubikey_frame.py ++++ b/yubico/yubikey_frame.py +@@ -17,7 +17,7 @@ + import yubikey_defs + import yubico_exception + import yubikey_config +-from yubico import __version__ ++from yubico_version import __version__ + + class YubiKeyFrame: + """ +diff --git a/yubico/yubikey_neo_usb_hid.py b/yubico/yubikey_neo_usb_hid.py +index f7913c7..7711779 100644 +--- a/yubico/yubikey_neo_usb_hid.py ++++ b/yubico/yubikey_neo_usb_hid.py +@@ -16,7 +16,7 @@ + + import struct + +-from yubico import __version__ ++from yubico_version import __version__ + import yubikey_usb_hid + import yubikey_frame + import yubico_exception +diff --git a/yubico/yubikey_usb_hid.py b/yubico/yubikey_usb_hid.py +index e808804..716ddaf 100644 +--- a/yubico/yubikey_usb_hid.py ++++ b/yubico/yubikey_usb_hid.py +@@ -14,7 +14,7 @@ + 'YubiKeyUSBHIDStatus', + ] + +-from yubico import __version__ ++from yubico_version import __version__ + + import yubico_util + import yubico_exception + +From 8061cb91bb582f7000e7432bb2319b9f70add306 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 1 Jul 2015 15:12:45 +0200 +Subject: [PATCH 2/6] Breaking circular imports: Move command definitions to + yubikey_defs + +This breaks the yubikey_config <-> yubikey_frame circular import. +--- + yubico/yubikey_config.py | 22 ++++------------------ + yubico/yubikey_defs.py | 18 ++++++++++++++++++ + yubico/yubikey_frame.py | 13 ++++++------- + yubico/yubikey_usb_hid.py | 2 +- + 4 files changed, 29 insertions(+), 26 deletions(-) + +diff --git a/yubico/yubikey_config.py b/yubico/yubikey_config.py +index 4f9ae29..24f0ba0 100644 +--- a/yubico/yubikey_config.py ++++ b/yubico/yubikey_config.py +@@ -27,6 +27,10 @@ + from yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag + import yubikey + ++# these used to be defined here; import them for backwards compatibility ++from yubikey_defs import SLOT_CONFIG, SLOT_CONFIG2, SLOT_UPDATE1, SLOT_UPDATE2, SLOT_SWAP, command2str ++ ++ + TicketFlags = [ + YubiKeyTicketFlag('TAB_FIRST', 0x01, min_ykver=(1, 0), doc='Send TAB before first part'), + YubiKeyTicketFlag('APPEND_TAB1', 0x02, min_ykver=(1, 0), doc='Send TAB after first part'), +@@ -79,24 +83,6 @@ + YubiKeyExtendedFlag('DORMANT', 0x40, min_ykver=(2, 3), doc='Dormant configuration (can be woken up and flag removed = requires update flag)'), + ] + +-SLOT_CONFIG = 0x01 # First (default / V1) configuration +-SLOT_CONFIG2 = 0x03 # Second (V2) configuration +-SLOT_UPDATE1 = 0x04 # Update slot 1 +-SLOT_UPDATE2 = 0x05 # Update slot 2 +-SLOT_SWAP = 0x06 # Swap slot 1 and 2 +- +-def command2str(num): +- """ Turn command number into name """ +- known = {0x01: "SLOT_CONFIG", +- 0x03: "SLOT_CONFIG2", +- 0x04: "SLOT_UPDATE1", +- 0x05: "SLOT_UPDATE2", +- 0x06: "SLOT_SWAP", +- } +- if num in known: +- return known[num] +- return "0x%02x" % (num) +- + class YubiKeyConfigError(yubico_exception.YubicoError): + """ + Exception raised for YubiKey configuration errors. +diff --git a/yubico/yubikey_defs.py b/yubico/yubikey_defs.py +index 843b8d9..bd4f7c4 100644 +--- a/yubico/yubikey_defs.py ++++ b/yubico/yubikey_defs.py +@@ -31,3 +31,21 @@ + OTP_CHALRESP_SIZE = 16 # Number of bytes returned for an Yubico-OTP challenge (not from ykdef.h) + + UID_SIZE = 6 # Size of secret ID field ++ ++SLOT_CONFIG = 0x01 # First (default / V1) configuration ++SLOT_CONFIG2 = 0x03 # Second (V2) configuration ++SLOT_UPDATE1 = 0x04 # Update slot 1 ++SLOT_UPDATE2 = 0x05 # Update slot 2 ++SLOT_SWAP = 0x06 # Swap slot 1 and 2 ++ ++def command2str(num): ++ """ Turn command number into name """ ++ known = {0x01: "SLOT_CONFIG", ++ 0x03: "SLOT_CONFIG2", ++ 0x04: "SLOT_UPDATE1", ++ 0x05: "SLOT_UPDATE2", ++ 0x06: "SLOT_SWAP", ++ } ++ if num in known: ++ return known[num] ++ return "0x%02x" % (num) +diff --git a/yubico/yubikey_frame.py b/yubico/yubikey_frame.py +index c34b4ec..63df89d 100644 +--- a/yubico/yubikey_frame.py ++++ b/yubico/yubikey_frame.py +@@ -16,7 +16,6 @@ + import yubico_util + import yubikey_defs + import yubico_exception +-import yubikey_config + from yubico_version import __version__ + + class YubiKeyFrame: +@@ -93,13 +92,13 @@ def _debug_string(self, debug, data): + """ + if not debug: + return data +- if self.command in [yubikey_config.SLOT_CONFIG, +- yubikey_config.SLOT_CONFIG2, +- yubikey_config.SLOT_UPDATE1, +- yubikey_config.SLOT_UPDATE2, +- yubikey_config.SLOT_SWAP, ++ if self.command in [yubikey_defs.SLOT_CONFIG, ++ yubikey_defs.SLOT_CONFIG2, ++ yubikey_defs.SLOT_UPDATE1, ++ yubikey_defs.SLOT_UPDATE2, ++ yubikey_defs.SLOT_SWAP, + ]: +- # annotate according to config_st (see yubikey_config.to_string()) ++ # annotate according to config_st (see yubikey_defs.to_string()) + if ord(data[-1]) == 0x80: + return (data, "FFFFFFF") + if ord(data[-1]) == 0x81: +diff --git a/yubico/yubikey_usb_hid.py b/yubico/yubikey_usb_hid.py +index 716ddaf..1fd4baa 100644 +--- a/yubico/yubikey_usb_hid.py ++++ b/yubico/yubikey_usb_hid.py +@@ -280,7 +280,7 @@ def _write_config(self, cfg, slot): + old_pgm_seq = self._status.pgm_seq + frame = cfg.to_frame(slot=slot) + self._debug("Writing %s frame :\n%s\n" % \ +- (yubikey_config.command2str(frame.command), cfg)) ++ (yubikey_defs.command2str(frame.command), cfg)) + self._write(frame) + self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG) + # make sure we have a fresh pgm_seq value + +From 3fe8f804faae595e32e880abe349cc6e6d0c26bf Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 1 Jul 2015 15:19:06 +0200 +Subject: [PATCH 3/6] Breaking circular imports: Move base classes to + yubico_base.py + +This breaks the yubikey <-> yubikey_usb_hid circular import +--- + yubico/yubikey.py | 180 +-------------------------------------------- + yubico/yubikey_base.py | 182 ++++++++++++++++++++++++++++++++++++++++++++++ + yubico/yubikey_config.py | 32 ++++---- + yubico/yubikey_usb_hid.py | 20 ++--- + 4 files changed, 209 insertions(+), 205 deletions(-) + create mode 100644 yubico/yubikey_base.py + +diff --git a/yubico/yubikey.py b/yubico/yubikey.py +index c576e80..d23af88 100644 +--- a/yubico/yubikey.py ++++ b/yubico/yubikey.py +@@ -32,185 +32,7 @@ + ] + + from yubico_version import __version__ +-import yubico_exception +- +-class YubiKeyError(yubico_exception.YubicoError): +- """ +- Exception raised concerning YubiKey operations. +- +- Attributes: +- reason -- explanation of the error +- """ +- def __init__(self, reason='no details'): +- yubico_exception.YubicoError.__init__(self, reason) +- +-class YubiKeyTimeout(YubiKeyError): +- """ +- Exception raised when a YubiKey operation timed out. +- +- Attributes: +- reason -- explanation of the error +- """ +- def __init__(self, reason='no details'): +- YubiKeyError.__init__(self, reason) +- +-class YubiKeyVersionError(YubiKeyError): +- """ +- Exception raised when the YubiKey is not capable of something requested. +- +- Attributes: +- reason -- explanation of the error +- """ +- def __init__(self, reason='no details'): +- YubiKeyError.__init__(self, reason) +- +- +-class YubiKeyCapabilities(): +- """ +- Class expressing the functionality of a YubiKey. +- +- This base class should be the superset of all sub-classes. +- +- In this base class, we lie and say 'yes' to all capabilities. +- +- If the base class is used (such as when creating a YubiKeyConfig() +- before getting a YubiKey()), errors must be handled at runtime +- (or later, when the user is unable to use the YubiKey). +- """ +- +- model = 'Unknown' +- version = (0, 0, 0,) +- version_num = 0x0 +- default_answer = True +- +- def __init__(self, model = None, version = None, default_answer = None): +- self.model = model +- if default_answer is not None: +- self.default_answer = default_answer +- if version is not None: +- self.version = version +- (major, minor, build,) = version +- # convert 2.1.3 to 0x00020103 +- self.version_num = (major << 24) | (minor << 16) | build +- return None +- +- def __repr__(self): +- return '<%s instance at %s: Device %s %s (default: %s)>' % ( +- self.__class__.__name__, +- hex(id(self)), +- self.model, +- self.version, +- self.default_answer, +- ) +- +- def have_yubico_OTP(self): +- return self.default_answer +- +- def have_OATH(self, mode): +- return self.default_answer +- +- def have_challenge_response(self, mode): +- return self.default_answer +- +- def have_serial_number(self): +- return self.default_answer +- +- def have_ticket_flag(self, flag): +- return self.default_answer +- +- def have_config_flag(self, flag): +- return self.default_answer +- +- def have_extended_flag(self, flag): +- return self.default_answer +- +- def have_extended_scan_code_mode(self): +- return self.default_answer +- +- def have_shifted_1_mode(self): +- return self.default_answer +- +- def have_nfc_ndef(self): +- return self.default_answer +- +- def have_configuration_slot(self): +- return self.default_answer +- +-class YubiKey(): +- """ +- Base class for accessing YubiKeys +- """ +- +- debug = None +- capabilities = None +- +- def __init__(self, debug, capabilities = None): +- self.debug = debug +- if capabilities is None: +- self.capabilities = YubiKeyCapabilities(default_answer = False) +- else: +- self.capabilities = capabilities +- return None +- +- def version(self): +- """ Get the connected YubiKey's version as a string. """ +- pass +- +- def serial(self, may_block=True): +- """ +- Get the connected YubiKey's serial number. +- +- Note that since version 2.?.? this requires the YubiKey to be +- configured with the extended flag SERIAL_API_VISIBLE. +- +- If the YubiKey is configured with SERIAL_BTN_VISIBLE set to True, +- it will start blinking and require a button press before revealing +- the serial number, with a 15 seconds timeout. Set `may_block' +- to False to abort if this is the case. +- """ +- pass +- +- def challenge(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): +- """ +- Get the response to a challenge from a connected YubiKey. +- +- `mode' is either 'HMAC' or 'OTP'. +- `slot' is 1 or 2. +- `variable' is only relevant for mode == HMAC. +- +- If variable is True, challenge will be padded such that the +- YubiKey will compute the HMAC as if there were no padding. +- If variable is False, challenge will always be NULL-padded +- to 64 bytes. +- +- The special case of no input will be HMACed by the YubiKey +- (in variable HMAC mode) as data = 0x00, length = 1. +- +- In mode 'OTP', the challenge should be exactly 6 bytes. The +- response will be a YubiKey "ticket" with the 6-byte challenge +- in the ticket.uid field. The rest of the "ticket" will contain +- timestamp and counter information, so two identical challenges +- will NOT result in the same responses. The response is +- decryptable using AES ECB if you have access to the AES key +- programmed into the YubiKey. +- """ +- pass +- +- def init_config(self): +- """ +- Return a YubiKey configuration object for this type of YubiKey. +- """ +- pass +- +- def write_config(self, cfg, slot): +- """ +- Configure a YubiKey using a configuration object. +- """ +- pass +- +-# Since YubiKeyUSBHID is a subclass of YubiKey (defined here above), +-# the import must be after the declaration of YubiKey. We also carefully +-# import only what we need to not get a circular import of modules. ++from yubikey_base import YubiKeyError, YubiKeyTimeout, YubiKeyVersionError, YubiKeyCapabilities, YubiKey + from yubikey_usb_hid import YubiKeyUSBHID, YubiKeyUSBHIDError + from yubikey_neo_usb_hid import YubiKeyNEO_USBHID, YubiKeyNEO_USBHIDError + +diff --git a/yubico/yubikey_base.py b/yubico/yubikey_base.py +new file mode 100644 +index 0000000..ef53e2e +--- /dev/null ++++ b/yubico/yubikey_base.py +@@ -0,0 +1,182 @@ ++""" ++module for Yubikey base classes ++""" ++# Copyright (c) 2010, 2011, 2012 Yubico AB ++# See the file COPYING for licence statement. ++ ++from yubico_version import __version__ ++import yubico_exception ++ ++class YubiKeyError(yubico_exception.YubicoError): ++ """ ++ Exception raised concerning YubiKey operations. ++ ++ Attributes: ++ reason -- explanation of the error ++ """ ++ def __init__(self, reason='no details'): ++ yubico_exception.YubicoError.__init__(self, reason) ++ ++class YubiKeyTimeout(YubiKeyError): ++ """ ++ Exception raised when a YubiKey operation timed out. ++ ++ Attributes: ++ reason -- explanation of the error ++ """ ++ def __init__(self, reason='no details'): ++ YubiKeyError.__init__(self, reason) ++ ++class YubiKeyVersionError(YubiKeyError): ++ """ ++ Exception raised when the YubiKey is not capable of something requested. ++ ++ Attributes: ++ reason -- explanation of the error ++ """ ++ def __init__(self, reason='no details'): ++ YubiKeyError.__init__(self, reason) ++ ++ ++class YubiKeyCapabilities(): ++ """ ++ Class expressing the functionality of a YubiKey. ++ ++ This base class should be the superset of all sub-classes. ++ ++ In this base class, we lie and say 'yes' to all capabilities. ++ ++ If the base class is used (such as when creating a YubiKeyConfig() ++ before getting a YubiKey()), errors must be handled at runtime ++ (or later, when the user is unable to use the YubiKey). ++ """ ++ ++ model = 'Unknown' ++ version = (0, 0, 0,) ++ version_num = 0x0 ++ default_answer = True ++ ++ def __init__(self, model = None, version = None, default_answer = None): ++ self.model = model ++ if default_answer is not None: ++ self.default_answer = default_answer ++ if version is not None: ++ self.version = version ++ (major, minor, build,) = version ++ # convert 2.1.3 to 0x00020103 ++ self.version_num = (major << 24) | (minor << 16) | build ++ return None ++ ++ def __repr__(self): ++ return '<%s instance at %s: Device %s %s (default: %s)>' % ( ++ self.__class__.__name__, ++ hex(id(self)), ++ self.model, ++ self.version, ++ self.default_answer, ++ ) ++ ++ def have_yubico_OTP(self): ++ return self.default_answer ++ ++ def have_OATH(self, mode): ++ return self.default_answer ++ ++ def have_challenge_response(self, mode): ++ return self.default_answer ++ ++ def have_serial_number(self): ++ return self.default_answer ++ ++ def have_ticket_flag(self, flag): ++ return self.default_answer ++ ++ def have_config_flag(self, flag): ++ return self.default_answer ++ ++ def have_extended_flag(self, flag): ++ return self.default_answer ++ ++ def have_extended_scan_code_mode(self): ++ return self.default_answer ++ ++ def have_shifted_1_mode(self): ++ return self.default_answer ++ ++ def have_nfc_ndef(self): ++ return self.default_answer ++ ++ def have_configuration_slot(self): ++ return self.default_answer ++ ++class YubiKey(): ++ """ ++ Base class for accessing YubiKeys ++ """ ++ ++ debug = None ++ capabilities = None ++ ++ def __init__(self, debug, capabilities = None): ++ self.debug = debug ++ if capabilities is None: ++ self.capabilities = YubiKeyCapabilities(default_answer = False) ++ else: ++ self.capabilities = capabilities ++ return None ++ ++ def version(self): ++ """ Get the connected YubiKey's version as a string. """ ++ pass ++ ++ def serial(self, may_block=True): ++ """ ++ Get the connected YubiKey's serial number. ++ ++ Note that since version 2.?.? this requires the YubiKey to be ++ configured with the extended flag SERIAL_API_VISIBLE. ++ ++ If the YubiKey is configured with SERIAL_BTN_VISIBLE set to True, ++ it will start blinking and require a button press before revealing ++ the serial number, with a 15 seconds timeout. Set `may_block' ++ to False to abort if this is the case. ++ """ ++ pass ++ ++ def challenge(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): ++ """ ++ Get the response to a challenge from a connected YubiKey. ++ ++ `mode' is either 'HMAC' or 'OTP'. ++ `slot' is 1 or 2. ++ `variable' is only relevant for mode == HMAC. ++ ++ If variable is True, challenge will be padded such that the ++ YubiKey will compute the HMAC as if there were no padding. ++ If variable is False, challenge will always be NULL-padded ++ to 64 bytes. ++ ++ The special case of no input will be HMACed by the YubiKey ++ (in variable HMAC mode) as data = 0x00, length = 1. ++ ++ In mode 'OTP', the challenge should be exactly 6 bytes. The ++ response will be a YubiKey "ticket" with the 6-byte challenge ++ in the ticket.uid field. The rest of the "ticket" will contain ++ timestamp and counter information, so two identical challenges ++ will NOT result in the same responses. The response is ++ decryptable using AES ECB if you have access to the AES key ++ programmed into the YubiKey. ++ """ ++ pass ++ ++ def init_config(self): ++ """ ++ Return a YubiKey configuration object for this type of YubiKey. ++ """ ++ pass ++ ++ def write_config(self, cfg, slot): ++ """ ++ Configure a YubiKey using a configuration object. ++ """ ++ pass +diff --git a/yubico/yubikey_config.py b/yubico/yubikey_config.py +index 24f0ba0..a3e9146 100644 +--- a/yubico/yubikey_config.py ++++ b/yubico/yubikey_config.py +@@ -25,7 +25,7 @@ + import yubico_exception + import yubikey_config_util + from yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag +-import yubikey ++import yubikey_base + + # these used to be defined here; import them for backwards compatibility + from yubikey_defs import SLOT_CONFIG, SLOT_CONFIG2, SLOT_UPDATE1, SLOT_UPDATE2, SLOT_SWAP, command2str +@@ -110,7 +110,7 @@ def __init__(self, ykver=None, capabilities=None, update=False, swap=False): + slot 1 be slot 2 and vice versa. Set swap=True for this. + """ + if capabilities is None: +- self.capabilities = yubikey.YubiKeyCapabilities(default_answer = True) ++ self.capabilities = yubikey_base.YubiKeyCapabilities(default_answer = True) + else: + self.capabilities = capabilities + +@@ -270,8 +270,8 @@ def mode_yubikey_otp(self, private_uid, aes_key): + Set the YubiKey up for standard OTP validation. + """ + if not self.capabilities.have_yubico_OTP(): +- raise yubikey.YubiKeyVersionError('Yubico OTP not available in %s version %d.%d' \ +- % (self.capabilities.model, self.ykver[0], self.ykver[1])) ++ raise yubikey_base.YubiKeyVersionError('Yubico OTP not available in %s version %d.%d' \ ++ % (self.capabilities.model, self.ykver[0], self.ykver[1])) + if private_uid.startswith('h:'): + private_uid = binascii.unhexlify(private_uid[2:]) + if len(private_uid) != yubikey_defs.UID_SIZE: +@@ -288,8 +288,8 @@ def mode_oath_hotp(self, secret, digits=6, factor_seed=None, omp=0x0, tt=0x0, mu + Requires YubiKey 2.1. + """ + if not self.capabilities.have_OATH('HOTP'): +- raise yubikey.YubiKeyVersionError('OATH HOTP not available in %s version %d.%d' \ +- % (self.capabilities.model, self.ykver[0], self.ykver[1])) ++ raise yubikey_base.YubiKeyVersionError('OATH HOTP not available in %s version %d.%d' \ ++ % (self.capabilities.model, self.ykver[0], self.ykver[1])) + if digits != 6 and digits != 8: + raise yubico_exception.InputError('OATH-HOTP digits must be 6 or 8') + +@@ -320,9 +320,9 @@ def mode_challenge_response(self, secret, type='HMAC', variable=True, require_bu + if not type.upper() in ['HMAC', 'OTP']: + raise yubico_exception.InputError('Invalid \'type\' (%s)' % type) + if not self.capabilities.have_challenge_response(type.upper()): +- raise yubikey.YubiKeyVersionError('%s Challenge-Response not available in %s version %d.%d' \ +- % (type.upper(), self.capabilities.model, \ +- self.ykver[0], self.ykver[1])) ++ raise yubikey_base.YubiKeyVersionError('%s Challenge-Response not available in %s version %d.%d' \ ++ % (type.upper(), self.capabilities.model, \ ++ self.ykver[0], self.ykver[1])) + self._change_mode('CHAL_RESP', major=2, minor=2) + if type.upper() == 'HMAC': + self.config_flag('CHAL_HMAC', True) +@@ -344,8 +344,8 @@ def ticket_flag(self, which, new=None): + flag = _get_flag(which, TicketFlags) + if flag: + if not self.capabilities.have_ticket_flag(flag): +- raise yubikey.YubiKeyVersionError('Ticket flag %s requires %s, and this is %s %d.%d' +- % (which, flag.req_string(self.capabilities.model), \ ++ raise yubikey_base.YubiKeyVersionError('Ticket flag %s requires %s, and this is %s %d.%d' ++ % (which, flag.req_string(self.capabilities.model), \ + self.capabilities.model, self.ykver[0], self.ykver[1])) + req_major, req_minor = flag.req_version() + self._require_version(major=req_major, minor=req_minor) +@@ -367,8 +367,8 @@ def config_flag(self, which, new=None): + flag = _get_flag(which, ConfigFlags) + if flag: + if not self.capabilities.have_config_flag(flag): +- raise yubikey.YubiKeyVersionError('Config flag %s requires %s, and this is %s %d.%d' +- % (which, flag.req_string(self.capabilities.model), \ ++ raise yubikey_base.YubiKeyVersionError('Config flag %s requires %s, and this is %s %d.%d' ++ % (which, flag.req_string(self.capabilities.model), \ + self.capabilities.model, self.ykver[0], self.ykver[1])) + req_major, req_minor = flag.req_version() + self._require_version(major=req_major, minor=req_minor) +@@ -390,8 +390,8 @@ def extended_flag(self, which, new=None): + flag = _get_flag(which, ExtendedFlags) + if flag: + if not self.capabilities.have_extended_flag(flag): +- raise yubikey.YubiKeyVersionError('Extended flag %s requires %s, and this is %s %d.%d' +- % (which, flag.req_string(self.capabilities.model), \ ++ raise yubikey_base.YubiKeyVersionError('Extended flag %s requires %s, and this is %s %d.%d' ++ % (which, flag.req_string(self.capabilities.model), \ + self.capabilities.model, self.ykver[0], self.ykver[1])) + req_major, req_minor = flag.req_version() + self._require_version(major=req_major, minor=req_minor) +@@ -472,7 +472,7 @@ def _require_version(self, major, minor=0): + """ Update the minimum version of YubiKey this configuration can be applied to. """ + new_ver = (major, minor) + if self.ykver and new_ver > self.ykver: +- raise yubikey.YubiKeyVersionError('Configuration requires YubiKey %d.%d, and this is %d.%d' ++ raise yubikey_base.YubiKeyVersionError('Configuration requires YubiKey %d.%d, and this is %d.%d' + % (major, minor, self.ykver[0], self.ykver[1])) + if new_ver > self.yk_req_version: + self.yk_req_version = new_ver +diff --git a/yubico/yubikey_usb_hid.py b/yubico/yubikey_usb_hid.py +index 1fd4baa..6af248b 100644 +--- a/yubico/yubikey_usb_hid.py ++++ b/yubico/yubikey_usb_hid.py +@@ -21,8 +21,8 @@ + import yubikey_frame + import yubikey_config + import yubikey_defs +-import yubikey +-from yubikey import YubiKey ++import yubikey_base ++from yubikey_base import YubiKey + import struct + import time + import sys +@@ -85,7 +85,7 @@ + class YubiKeyUSBHIDError(yubico_exception.YubicoError): + """ Exception raised for errors with the USB HID communication. """ + +-class YubiKeyUSBHIDCapabilities(yubikey.YubiKeyCapabilities): ++class YubiKeyUSBHIDCapabilities(yubikey_base.YubiKeyCapabilities): + """ + Capture the capabilities of the various versions of YubiKeys. + +@@ -93,9 +93,9 @@ class YubiKeyUSBHIDCapabilities(yubikey.YubiKeyCapabilities): + in one or more versions, leaving the other ones at False through default_answer. + """ + def __init__(self, model, version, default_answer): +- yubikey.YubiKeyCapabilities.__init__(self, model = model, \ +- version = version, \ +- default_answer = default_answer) ++ yubikey_base.YubiKeyCapabilities.__init__(self, model = model, \ ++ version = version, \ ++ default_answer = default_answer) + + def have_yubico_OTP(self): + """ Yubico OTP support has always been available in the standard YubiKey. """ +@@ -203,13 +203,13 @@ def version(self): + def serial(self, may_block=True): + """ Get the YubiKey serial number (requires YubiKey 2.2). """ + if not self.capabilities.have_serial_number(): +- raise yubikey.YubiKeyVersionError("Serial number unsupported in YubiKey %s" % self.version() ) ++ raise yubikey_base.YubiKeyVersionError("Serial number unsupported in YubiKey %s" % self.version() ) + return self._read_serial(may_block) + + def challenge_response(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True): + """ Issue a challenge to the YubiKey and return the response (requires YubiKey 2.2). """ + if not self.capabilities.have_challenge_response(mode): +- raise yubikey.YubiKeyVersionError("%s challenge-response unsupported in YubiKey %s" % (mode, self.version()) ) ++ raise yubikey_base.YubiKeyVersionError("%s challenge-response unsupported in YubiKey %s" % (mode, self.version()) ) + return self._challenge_response(challenge, mode, slot, variable, may_block) + + def init_config(self, **kw): +@@ -222,7 +222,7 @@ def write_config(self, cfg, slot=1): + """ Write a configuration to the YubiKey. """ + cfg_req_ver = cfg.version_required() + if cfg_req_ver > self.version_num(): +- raise yubikey.YubiKeyVersionError('Configuration requires YubiKey version %i.%i (this is %s)' % \ ++ raise yubikey_base.YubiKeyVersionError('Configuration requires YubiKey version %i.%i (this is %s)' % \ + (cfg_req_ver[0], cfg_req_ver[1], self.version())) + if not self.capabilities.have_configuration_slot(slot): + raise YubiKeyUSBHIDError("Can't write configuration to slot %i" % (slot)) +@@ -436,7 +436,7 @@ def _waitfor(self, mode, mask, may_block, timeout=2): + reason = 'Timed out waiting for YubiKey to clear status 0x%x' % mask + else: + reason = 'Timed out waiting for YubiKey to set status 0x%x' % mask +- raise yubikey.YubiKeyTimeout(reason) ++ raise yubikey_base.YubiKeyTimeout(reason) + time.sleep(sleep) + sleep = min(sleep + sleep, 0.5) + else: + +From 1f2e611c491a168ff5632b3365c399b0e059d367 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 1 Jul 2015 14:18:34 +0200 +Subject: [PATCH 4/6] 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) +--- + test/test_yubikey_config.py | 32 ++++++++++++++++---------------- + test/test_yubikey_frame.py | 2 +- + test/test_yubikey_usb_hid.py | 8 ++++---- + yubico/__init__.py | 6 +++--- + yubico/yubico_exception.py | 2 +- + yubico/yubico_util.py | 8 ++++---- + yubico/yubikey.py | 8 ++++---- + yubico/yubikey_base.py | 4 ++-- + yubico/yubikey_config.py | 18 +++++++++--------- + yubico/yubikey_defs.py | 2 +- + yubico/yubikey_frame.py | 8 ++++---- + yubico/yubikey_neo_usb_hid.py | 8 ++++---- + yubico/yubikey_usb_hid.py | 20 ++++++++++---------- + 13 files changed, 63 insertions(+), 63 deletions(-) + +diff --git a/test/test_yubikey_config.py b/test/test_yubikey_config.py +index 29b8649..5fc694c 100644 +--- a/test/test_yubikey_config.py ++++ b/test/test_yubikey_config.py +@@ -41,8 +41,8 @@ def test_static_ticket(self): + + data = Config.to_frame(slot=1).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -75,8 +75,8 @@ def test_static_ticket_with_access_code(self): + + data = Config.to_frame(slot=1).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -113,8 +113,8 @@ def test_fixed_and_oath_hotp(self): + + data = Config.to_frame(slot=2).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -138,8 +138,8 @@ def test_challenge_response_hmac_nist(self): + + data = Config.to_frame(slot=2).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -186,8 +186,8 @@ def test_oath_hotp_like_windows(self): + + data = Config.to_frame(slot=2).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -211,8 +211,8 @@ def test_oath_hotp_like_windows2(self): + + data = Config.to_frame(slot=2).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -236,8 +236,8 @@ def test_oath_hotp_like_windows_factory_seed(self): + + data = Config.to_frame(slot=2).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +@@ -260,8 +260,8 @@ def test_fixed_length_hmac_like_windows(self): + + data = Config.to_frame(slot=2).to_feature_reports() + +- print "EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), ++ yubico.yubico_util.hexdump(''.join(data)))) + + self.assertEqual(data, expected) + +diff --git a/test/test_yubikey_frame.py b/test/test_yubikey_frame.py +index f6cbb6e..5437922 100644 +--- a/test/test_yubikey_frame.py ++++ b/test/test_yubikey_frame.py +@@ -50,7 +50,7 @@ def test_repr(self): + """ Test string representation of object """ + # to achieve 100% test coverage ;) + frame = YubiKeyFrame(command=0x4d) +- print "Frame is represented as %s" % frame ++ print("Frame is represented as %s" % frame) + re_match = re.search("YubiKeyFrame instance at .*: 77.$", str(frame)) + self.assertNotEqual(re_match, None) + +diff --git a/test/test_yubikey_usb_hid.py b/test/test_yubikey_usb_hid.py +index 356b461..f801a33 100644 +--- a/test/test_yubikey_usb_hid.py ++++ b/test/test_yubikey_usb_hid.py +@@ -18,10 +18,10 @@ def setUp(self): + """ Test connecting to the YubiKey """ + if self.YK is None: + try: +- print "open key" ++ print("open key") + self.YK = YubiKeyUSBHID() + return +- except YubiKeyUSBHIDError, err: ++ except YubiKeyUSBHIDError as err: + self.fail("No YubiKey connected (?) : %s" % str(err)) + + def tearDown(self): +@@ -33,7 +33,7 @@ def test_status(self): + """ Test the simplest form of communication : a status read request """ + status = self.YK.status() + version = self.YK.version() +- print "Version returned: %s" % version ++ print("Version returned: %s" % version) + re_match = re.match("\d+\.\d+\.\d+$", version) + self.assertNotEqual(re_match, None) + +@@ -49,7 +49,7 @@ def test_challenge_response(self): + def test_serial(self): + """ Test serial number retrieval (requires YubiKey 2) """ + serial = self.YK.serial() +- print "Serial returned : %s" % serial ++ print("Serial returned : %s" % serial) + self.assertEqual(type(serial), type(1)) + + if __name__ == '__main__': +diff --git a/yubico/__init__.py b/yubico/__init__.py +index 738897e..d81c1b1 100644 +--- a/yubico/__init__.py ++++ b/yubico/__init__.py +@@ -20,7 +20,7 @@ + # Copyright (c) 2010, 2011, 2012 Yubico AB + # See the file COPYING for licence statement. + +-from yubico_version import __version__ ++from .yubico_version import __version__ + + __all__ = [ + # classes +@@ -40,5 +40,5 @@ + ] + + # to not have to import yubico.yubikey +-from yubikey import YubiKey +-from yubikey import find_key as find_yubikey ++from .yubikey import YubiKey ++from .yubikey import find_key as find_yubikey +diff --git a/yubico/yubico_exception.py b/yubico/yubico_exception.py +index 3adba6c..e2fd393 100644 +--- a/yubico/yubico_exception.py ++++ b/yubico/yubico_exception.py +@@ -22,7 +22,7 @@ class for exceptions used in the other Yubico modules + 'YubiKeyTimeout', + ] + +-from yubico_version import __version__ ++from .yubico_version import __version__ + + class YubicoError(Exception): + """ +diff --git a/yubico/yubico_util.py b/yubico/yubico_util.py +index 8e13f32..5af3956 100644 +--- a/yubico/yubico_util.py ++++ b/yubico/yubico_util.py +@@ -15,9 +15,9 @@ + # classes + ] + +-from yubico_version import __version__ +-import yubikey_defs +-import yubico_exception ++from .yubico_version import __version__ ++from . import yubikey_defs ++from . import yubico_exception + import string + + _CRC_OK_RESIDUAL = 0xf0b8 +@@ -101,7 +101,7 @@ def hexdump(src, length=8, colorize=False): + + def group(data, num): + """ Split data into chunks of num chars each """ +- return [data[i:i+num] for i in xrange(0, len(data), num)] ++ return [data[i:i+num] for i in range(0, len(data), num)] + + def modhex_decode(data): + """ Convert a modhex string to ordinary hex. """ +diff --git a/yubico/yubikey.py b/yubico/yubikey.py +index d23af88..f71d56f 100644 +--- a/yubico/yubikey.py ++++ b/yubico/yubikey.py +@@ -31,10 +31,10 @@ + 'YubiKeyTimeout', + ] + +-from yubico_version import __version__ +-from yubikey_base import YubiKeyError, YubiKeyTimeout, YubiKeyVersionError, YubiKeyCapabilities, YubiKey +-from yubikey_usb_hid import YubiKeyUSBHID, YubiKeyUSBHIDError +-from yubikey_neo_usb_hid import YubiKeyNEO_USBHID, YubiKeyNEO_USBHIDError ++from .yubico_version import __version__ ++from .yubikey_base import YubiKeyError, YubiKeyTimeout, YubiKeyVersionError, YubiKeyCapabilities, YubiKey ++from .yubikey_usb_hid import YubiKeyUSBHID, YubiKeyUSBHIDError ++from .yubikey_neo_usb_hid import YubiKeyNEO_USBHID, YubiKeyNEO_USBHIDError + + def find_key(debug=False, skip=0): + """ +diff --git a/yubico/yubikey_base.py b/yubico/yubikey_base.py +index ef53e2e..9378082 100644 +--- a/yubico/yubikey_base.py ++++ b/yubico/yubikey_base.py +@@ -4,8 +4,8 @@ + # Copyright (c) 2010, 2011, 2012 Yubico AB + # See the file COPYING for licence statement. + +-from yubico_version import __version__ +-import yubico_exception ++from .yubico_version import __version__ ++from . import yubico_exception + + class YubiKeyError(yubico_exception.YubicoError): + """ +diff --git a/yubico/yubikey_config.py b/yubico/yubikey_config.py +index a3e9146..3a02114 100644 +--- a/yubico/yubikey_config.py ++++ b/yubico/yubikey_config.py +@@ -15,20 +15,20 @@ + 'YubiKeyConfig', + ] + +-from yubico_version import __version__ ++from .yubico_version import __version__ + + import struct + import binascii +-import yubico_util +-import yubikey_defs +-import yubikey_frame +-import yubico_exception +-import yubikey_config_util +-from yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag +-import yubikey_base ++from . import yubico_util ++from . import yubikey_defs ++from . import yubikey_frame ++from . import yubico_exception ++from . import yubikey_config_util ++from .yubikey_config_util import YubiKeyConfigBits, YubiKeyConfigFlag, YubiKeyExtendedFlag, YubiKeyTicketFlag ++from . import yubikey_base + + # these used to be defined here; import them for backwards compatibility +-from yubikey_defs import SLOT_CONFIG, SLOT_CONFIG2, SLOT_UPDATE1, SLOT_UPDATE2, SLOT_SWAP, command2str ++from .yubikey_defs import SLOT_CONFIG, SLOT_CONFIG2, SLOT_UPDATE1, SLOT_UPDATE2, SLOT_SWAP, command2str + + + TicketFlags = [ +diff --git a/yubico/yubikey_defs.py b/yubico/yubikey_defs.py +index bd4f7c4..777df30 100644 +--- a/yubico/yubikey_defs.py ++++ b/yubico/yubikey_defs.py +@@ -18,7 +18,7 @@ + # classes + ] + +-from yubico_version import __version__ ++from .yubico_version import __version__ + + # Yubikey Low level interface #2.3 + RESP_TIMEOUT_WAIT_MASK = 0x1f # Mask to get timeout value +diff --git a/yubico/yubikey_frame.py b/yubico/yubikey_frame.py +index 63df89d..80919c9 100644 +--- a/yubico/yubikey_frame.py ++++ b/yubico/yubikey_frame.py +@@ -13,10 +13,10 @@ + + import struct + +-import yubico_util +-import yubikey_defs +-import yubico_exception +-from yubico_version import __version__ ++from . import yubico_util ++from . import yubikey_defs ++from . import yubico_exception ++from .yubico_version import __version__ + + class YubiKeyFrame: + """ +diff --git a/yubico/yubikey_neo_usb_hid.py b/yubico/yubikey_neo_usb_hid.py +index 7711779..0543b8e 100644 +--- a/yubico/yubikey_neo_usb_hid.py ++++ b/yubico/yubikey_neo_usb_hid.py +@@ -16,10 +16,10 @@ + + import struct + +-from yubico_version import __version__ +-import yubikey_usb_hid +-import yubikey_frame +-import yubico_exception ++from .yubico_version import __version__ ++from . import yubikey_usb_hid ++from . import yubikey_frame ++from . import yubico_exception + + # commands from ykdef.h + _SLOT_NDEF = 0x08 # Write YubiKey NEO NDEF +diff --git a/yubico/yubikey_usb_hid.py b/yubico/yubikey_usb_hid.py +index 6af248b..3cf47a4 100644 +--- a/yubico/yubikey_usb_hid.py ++++ b/yubico/yubikey_usb_hid.py +@@ -14,15 +14,15 @@ + 'YubiKeyUSBHIDStatus', + ] + +-from yubico_version import __version__ +- +-import yubico_util +-import yubico_exception +-import yubikey_frame +-import yubikey_config +-import yubikey_defs +-import yubikey_base +-from yubikey_base import YubiKey ++from .yubico_version import __version__ ++ ++from . import yubico_util ++from . import yubico_exception ++from . import yubikey_frame ++from . import yubikey_config ++from . import yubikey_defs ++from . import yubikey_base ++from .yubikey_base import YubiKey + import struct + import time + import sys +@@ -455,7 +455,7 @@ def _open(self, skip=0): + try: + self._usb_handle = usb_device.open() + self._usb_handle.detachKernelDriver(0) +- except Exception, error: ++ except Exception as error: + if 'could not detach kernel driver from interface' in str(error): + self._debug('The in-kernel-HID driver has already been detached\n') + else: + +From ebb015bf0b32a6d3c3a9e86580c90b13545506b6 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 1 Jul 2015 17:38:05 +0200 +Subject: [PATCH 5/6] Distinguish text strings and bytestrings for Python 3 + compatibility + +All (potentially binary) data is bytestrings; text (including +e.g. hexdumps and exception messages) is text strings. + +Note that in Python 2, there's no difference between text (str, '...') +and bytestrings (bytes, b'...'). +--- + test/test_yubico.py | 8 +- + test/test_yubikey_config.py | 190 +++++++++++++++++++++--------------------- + test/test_yubikey_frame.py | 20 ++--- + test/test_yubikey_usb_hid.py | 4 +- + yubico/yubico_util.py | 70 +++++++++++----- + yubico/yubikey_config.py | 37 ++++---- + yubico/yubikey_frame.py | 16 ++-- + yubico/yubikey_neo_usb_hid.py | 13 +-- + yubico/yubikey_usb_hid.py | 16 ++-- + 9 files changed, 205 insertions(+), 169 deletions(-) + +diff --git a/test/test_yubico.py b/test/test_yubico.py +index f045d31..cd87f42 100644 +--- a/test/test_yubico.py ++++ b/test/test_yubico.py +@@ -14,7 +14,7 @@ class TestCRC(unittest.TestCase): + + def test_first(self): + """ Test CRC16 trivial case """ +- buffer = '\x01\x02\x03\x04' ++ buffer = b'\x01\x02\x03\x04' + crc = crc16(buffer) + self.assertEqual(crc, 0xc66e) + return buffer,crc +@@ -31,19 +31,19 @@ def test_second(self): + + def test_hexdump(self): + """ Test hexdump function, normal use """ +- bytes = '\x01\x02\x03\x04\x05\x06\x07\x08' ++ bytes = b'\x01\x02\x03\x04\x05\x06\x07\x08' + self.assertEqual(yubico_util.hexdump(bytes, length=4), \ + '0000 01 02 03 04\n0004 05 06 07 08\n') + + def test_hexdump(self): + """ Test hexdump function, with colors """ +- bytes = '\x01\x02\x03\x04\x05\x06\x07\x08' ++ bytes = b'\x01\x02\x03\x04\x05\x06\x07\x08' + self.assertEqual(yubico_util.hexdump(bytes, length=4, colorize=True), \ + '0000 \x1b[0m01 02 03\x1b[0m 04\n0004 \x1b[0m05 06 07\x1b[0m 08\n') + + def test_modhex_decode(self): + """ Test modhex decoding """ +- self.assertEqual("0123456789abcdef", yubico_util.modhex_decode("cbdefghijklnrtuv")) ++ self.assertEqual(b"0123456789abcdef", yubico_util.modhex_decode(b"cbdefghijklnrtuv")) + + if __name__ == '__main__': + unittest.main() +diff --git a/test/test_yubikey_config.py b/test/test_yubikey_config.py +index 5fc694c..50067d7 100644 +--- a/test/test_yubikey_config.py ++++ b/test/test_yubikey_config.py +@@ -25,24 +25,24 @@ def test_static_ticket(self): + #ticket_flags: APPEND_CR + #config_flags: STATIC_TICKET + +- expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\xe2\xbe\xe9\xa3\x65\x68\x83', +- '\xa0\x0d\x02\x6a\x02\xf8\x5e\x84', +- '\x61\xe6\xfb\x00\x00\x00\x00\x85', +- '\x00\x00\x00\x00\x20\x20\x00\x86', +- '\x00\x5a\x93\x00\x00\x00\x00\x87', +- '\x00\x01\x95\x56\x00\x00\x00\x89' ++ expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\xe2\xbe\xe9\xa3\x65\x68\x83', ++ b'\xa0\x0d\x02\x6a\x02\xf8\x5e\x84', ++ b'\x61\xe6\xfb\x00\x00\x00\x00\x85', ++ b'\x00\x00\x00\x00\x20\x20\x00\x86', ++ b'\x00\x5a\x93\x00\x00\x00\x00\x87', ++ b'\x00\x01\x95\x56\x00\x00\x00\x89' + ] + + Config = self.Config +- Config.aes_key('h:e2bee9a36568a00d026a02f85e61e6fb') ++ Config.aes_key(b'h:e2bee9a36568a00d026a02f85e61e6fb') + Config.ticket_flag('APPEND_CR', True) + Config.config_flag('STATIC_TICKET', True) + + data = Config.to_frame(slot=1).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + +@@ -57,26 +57,26 @@ def test_static_ticket_with_access_code(self): + #ticket_flags: APPEND_CR + #config_flags: STATIC_TICKET + +- expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\xe2\xbe\xe9\xa3\x65\x68\x83', +- '\xa0\x0d\x02\x6a\x02\xf8\x5e\x84', +- '\x61\xe6\xfb\x01\x02\x03\x04\x85', +- '\x05\x06\x00\x00\x20\x20\x00\x86', +- '\x00\x0d\x39\x01\x02\x03\x04\x87', +- '\x05\x06\x00\x00\x00\x00\x00\x88', +- '\x00\x01\xc2\xfc\x00\x00\x00\x89', ++ expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\xe2\xbe\xe9\xa3\x65\x68\x83', ++ b'\xa0\x0d\x02\x6a\x02\xf8\x5e\x84', ++ b'\x61\xe6\xfb\x01\x02\x03\x04\x85', ++ b'\x05\x06\x00\x00\x20\x20\x00\x86', ++ b'\x00\x0d\x39\x01\x02\x03\x04\x87', ++ b'\x05\x06\x00\x00\x00\x00\x00\x88', ++ b'\x00\x01\xc2\xfc\x00\x00\x00\x89', + ] + + Config = self.Config +- Config.aes_key('h:e2bee9a36568a00d026a02f85e61e6fb') ++ Config.aes_key(b'h:e2bee9a36568a00d026a02f85e61e6fb') + Config.ticket_flag('APPEND_CR', True) + Config.config_flag('STATIC_TICKET', True) +- Config.unlock_key('h:010203040506') ++ Config.unlock_key(b'h:010203040506') + + data = Config.to_frame(slot=1).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + +@@ -90,56 +90,56 @@ def test_fixed_and_oath_hotp(self): + #ticket_flags: APPEND_CR|OATH_HOTP + #config_flags: OATH_FIXED_MODHEX1|OATH_FIXED_MODHEX2|STATIC_TICKET + +- expected = ['\x4d\x4d\x4d\x4d\x00\x00\x00\x80', +- '\x00\x52\x3d\x7c\xe7\xe7\xb6\x83', +- '\xee\x85\x35\x17\xa3\xe3\xcc\x84', +- '\x19\x85\xc7\x00\x00\x00\x00\x85', +- '\x00\x00\x04\x00\x60\x70\x00\x86', +- '\x00\x72\xad\xaa\xbb\xcc\xdd\x87', +- '\xee\xff\x00\x00\x00\x00\x00\x88', +- '\x00\x03\xfe\xc4\x00\x00\x00\x89', ++ expected = [b'\x4d\x4d\x4d\x4d\x00\x00\x00\x80', ++ b'\x00\x52\x3d\x7c\xe7\xe7\xb6\x83', ++ b'\xee\x85\x35\x17\xa3\xe3\xcc\x84', ++ b'\x19\x85\xc7\x00\x00\x00\x00\x85', ++ b'\x00\x00\x04\x00\x60\x70\x00\x86', ++ b'\x00\x72\xad\xaa\xbb\xcc\xdd\x87', ++ b'\xee\xff\x00\x00\x00\x00\x00\x88', ++ b'\x00\x03\xfe\xc4\x00\x00\x00\x89', + ] + + Config = self.Config +- Config.aes_key('h:523d7ce7e7b6ee853517a3e3cc1985c7') +- Config.fixed_string('m:ftftftft') ++ Config.aes_key(b'h:523d7ce7e7b6ee853517a3e3cc1985c7') ++ Config.fixed_string(b'm:ftftftft') + Config.ticket_flag('APPEND_CR', True) + Config.ticket_flag('OATH_HOTP', True) + Config.config_flag('OATH_FIXED_MODHEX1', True) + Config.config_flag('OATH_FIXED_MODHEX2', True) + Config.config_flag('STATIC_TICKET', True) +- Config.unlock_key('h:aabbccddeeff') +- Config.access_key('h:000000000000') ++ Config.unlock_key(b'h:aabbccddeeff') ++ Config.access_key(b'h:000000000000') + + data = Config.to_frame(slot=2).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + + def test_challenge_response_hmac_nist(self): + """ Test HMAC challenge response with NIST test vector """ + +- expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\x00\x40\x41\x42\x43\x00\x82', +- '\x00\x30\x31\x32\x33\x34\x35\x83', +- '\x36\x37\x38\x39\x3a\x3b\x3c\x84', +- '\x3d\x3e\x3f\x00\x00\x00\x00\x85', +- '\x00\x00\x00\x04\x40\x26\x00\x86', +- '\x00\x98\x41\x00\x00\x00\x00\x87', +- '\x00\x03\x95\x56\x00\x00\x00\x89', ++ expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\x00\x40\x41\x42\x43\x00\x82', ++ b'\x00\x30\x31\x32\x33\x34\x35\x83', ++ b'\x36\x37\x38\x39\x3a\x3b\x3c\x84', ++ b'\x3d\x3e\x3f\x00\x00\x00\x00\x85', ++ b'\x00\x00\x00\x04\x40\x26\x00\x86', ++ b'\x00\x98\x41\x00\x00\x00\x00\x87', ++ b'\x00\x03\x95\x56\x00\x00\x00\x89', + ] + + Config = self.Config +- secret = 'h:303132333435363738393a3b3c3d3e3f40414243' ++ secret = b'h:303132333435363738393a3b3c3d3e3f40414243' + Config.mode_challenge_response(secret, type='HMAC', variable=True) + Config.extended_flag('SERIAL_API_VISIBLE', True) + + data = Config.to_frame(slot=2).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + +@@ -170,98 +170,98 @@ def test_default_flags(self): + def test_oath_hotp_like_windows(self): + """ Test plain OATH-HOTP with NIST test vector """ + +- expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\x00\x40\x41\x42\x43\x00\x82', +- '\x00\x30\x31\x32\x33\x34\x35\x83', +- '\x36\x37\x38\x39\x3a\x3b\x3c\x84', +- '\x3d\x3e\x3f\x00\x00\x00\x00\x85', +- '\x00\x00\x00\x00\x40\x00\x00\x86', +- '\x00\x6a\xb9\x00\x00\x00\x00\x87', +- '\x00\x03\x95\x56\x00\x00\x00\x89', ++ expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\x00\x40\x41\x42\x43\x00\x82', ++ b'\x00\x30\x31\x32\x33\x34\x35\x83', ++ b'\x36\x37\x38\x39\x3a\x3b\x3c\x84', ++ b'\x3d\x3e\x3f\x00\x00\x00\x00\x85', ++ b'\x00\x00\x00\x00\x40\x00\x00\x86', ++ b'\x00\x6a\xb9\x00\x00\x00\x00\x87', ++ b'\x00\x03\x95\x56\x00\x00\x00\x89', + ] + + Config = self.Config +- secret = 'h:303132333435363738393a3b3c3d3e3f40414243' ++ secret = b'h:303132333435363738393a3b3c3d3e3f40414243' + Config.mode_oath_hotp(secret) + + data = Config.to_frame(slot=2).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + + def test_oath_hotp_like_windows2(self): + """ Test OATH-HOTP with NIST test vector and token identifier """ + +- expected = ['\x01\x02\x03\x04\x05\x06\x00\x80', +- '\x00\x00\x40\x41\x42\x43\x00\x82', +- '\x00\x30\x31\x32\x33\x34\x35\x83', +- '\x36\x37\x38\x39\x3a\x3b\x3c\x84', +- '\x3d\x3e\x3f\x00\x00\x00\x00\x85', +- '\x00\x00\x06\x00\x40\x42\x00\x86', +- '\x00\x0e\xec\x00\x00\x00\x00\x87', +- '\x00\x03\x95\x56\x00\x00\x00\x89', ++ expected = [b'\x01\x02\x03\x04\x05\x06\x00\x80', ++ b'\x00\x00\x40\x41\x42\x43\x00\x82', ++ b'\x00\x30\x31\x32\x33\x34\x35\x83', ++ b'\x36\x37\x38\x39\x3a\x3b\x3c\x84', ++ b'\x3d\x3e\x3f\x00\x00\x00\x00\x85', ++ b'\x00\x00\x06\x00\x40\x42\x00\x86', ++ b'\x00\x0e\xec\x00\x00\x00\x00\x87', ++ b'\x00\x03\x95\x56\x00\x00\x00\x89', + ] + + Config = self.Config +- secret = 'h:303132333435363738393a3b3c3d3e3f40414243' +- Config.mode_oath_hotp(secret, digits=8, factor_seed='', omp=0x01, tt=0x02, mui='\x03\x04\x05\x06') ++ secret = b'h:303132333435363738393a3b3c3d3e3f40414243' ++ Config.mode_oath_hotp(secret, digits=8, factor_seed='', omp=0x01, tt=0x02, mui=b'\x03\x04\x05\x06') + Config.config_flag('OATH_FIXED_MODHEX2', True) + + data = Config.to_frame(slot=2).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + + def test_oath_hotp_like_windows_factory_seed(self): + """ Test OATH-HOTP factor_seed """ + +- expected = ['\x01\x02\x03\x04\x05\x06\x00\x80', +- '\x00\x00\x40\x41\x42\x43\x01\x82', +- '\x21\x30\x31\x32\x33\x34\x35\x83', +- '\x36\x37\x38\x39\x3a\x3b\x3c\x84', +- '\x3d\x3e\x3f\x00\x00\x00\x00\x85', +- '\x00\x00\x06\x00\x40\x42\x00\x86', +- '\x00\x03\xea\x00\x00\x00\x00\x87', +- '\x00\x03\x95\x56\x00\x00\x00\x89', ++ expected = [b'\x01\x02\x03\x04\x05\x06\x00\x80', ++ b'\x00\x00\x40\x41\x42\x43\x01\x82', ++ b'\x21\x30\x31\x32\x33\x34\x35\x83', ++ b'\x36\x37\x38\x39\x3a\x3b\x3c\x84', ++ b'\x3d\x3e\x3f\x00\x00\x00\x00\x85', ++ b'\x00\x00\x06\x00\x40\x42\x00\x86', ++ b'\x00\x03\xea\x00\x00\x00\x00\x87', ++ b'\x00\x03\x95\x56\x00\x00\x00\x89', + ] + + Config = self.Config +- secret = 'h:303132333435363738393a3b3c3d3e3f40414243' +- Config.mode_oath_hotp(secret, digits=8, factor_seed=0x2101, omp=0x01, tt=0x02, mui='\x03\x04\x05\x06') ++ secret = b'h:303132333435363738393a3b3c3d3e3f40414243' ++ Config.mode_oath_hotp(secret, digits=8, factor_seed=0x2101, omp=0x01, tt=0x02, mui=b'\x03\x04\x05\x06') + Config.config_flag('OATH_FIXED_MODHEX2', True) + + data = Config.to_frame(slot=2).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + + def test_fixed_length_hmac_like_windows(self): + """ Test fixed length HMAC SHA1 """ + +- expected = ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\x00\x40\x41\x42\x43\x00\x82', +- '\x00\x30\x31\x32\x33\x34\x35\x83', +- '\x36\x37\x38\x39\x3a\x3b\x3c\x84', +- '\x3d\x3e\x3f\x00\x00\x00\x00\x85', +- '\x00\x00\x00\x00\x40\x22\x00\x86', +- '\x00\xe9\x0f\x00\x00\x00\x00\x87', +- '\x00\x03\x95\x56\x00\x00\x00\x89', ++ expected = [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\x00\x40\x41\x42\x43\x00\x82', ++ b'\x00\x30\x31\x32\x33\x34\x35\x83', ++ b'\x36\x37\x38\x39\x3a\x3b\x3c\x84', ++ b'\x3d\x3e\x3f\x00\x00\x00\x00\x85', ++ b'\x00\x00\x00\x00\x40\x22\x00\x86', ++ b'\x00\xe9\x0f\x00\x00\x00\x00\x87', ++ b'\x00\x03\x95\x56\x00\x00\x00\x89', + ] + + Config = self.Config +- secret = 'h:303132333435363738393a3b3c3d3e3f40414243' ++ secret = b'h:303132333435363738393a3b3c3d3e3f40414243' + Config.mode_challenge_response(secret, type='HMAC', variable=False) + + data = Config.to_frame(slot=2).to_feature_reports() + +- print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(''.join(expected)), +- yubico.yubico_util.hexdump(''.join(data)))) ++ print("EXPECT:\n%s\nGOT:\n%s\n" %( yubico.yubico_util.hexdump(b''.join(expected)), ++ yubico.yubico_util.hexdump(b''.join(data)))) + + self.assertEqual(data, expected) + +@@ -292,14 +292,14 @@ def test_version_required_4(self): + capa = yubico.yubikey_usb_hid.YubiKeyUSBHIDCapabilities( \ + model = 'YubiKey', version = version, default_answer = False) + Config = YubiKeyConfigUSBHID(ykver = version, capabilities = capa) +- secret = 'h:303132333435363738393a3b3c3d3e3f40414243' ++ secret = b'h:303132333435363738393a3b3c3d3e3f40414243' + self.assertRaises(yubico.yubikey.YubiKeyVersionError, Config.mode_challenge_response, secret) + + def test_version_required_5(self): + """ Test YubiKey 2.2 with v2.2 mode """ + + Config = self.Config +- secret = 'h:303132333435363738393a3b3c3d3e3f' ++ secret = b'h:303132333435363738393a3b3c3d3e3f' + Config.mode_challenge_response(secret, type='OTP') + self.assertEqual('CHAL_RESP', Config._mode) + +diff --git a/test/test_yubikey_frame.py b/test/test_yubikey_frame.py +index 5437922..e1308c4 100644 +--- a/test/test_yubikey_frame.py ++++ b/test/test_yubikey_frame.py +@@ -17,7 +17,7 @@ def test_get_ykframe(self): + self.assertEqual(len(buffer), 70, "yubikey command buffer should always be 70 bytes") + + # check that empty payload works (64 * '\x00') +- all_zeros = '\x00' * 64 ++ all_zeros = b'\x00' * 64 + + self.assertTrue(buffer.startswith(all_zeros)) + +@@ -26,25 +26,25 @@ def test_get_ykframe_feature_reports(self): + """ Test normal use """ + res = YubiKeyFrame(command=0x32).to_feature_reports() + +- self.assertEqual(res, ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\x32\x6b\x5b\x00\x00\x00\x89' ++ self.assertEqual(res, [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\x32\x6b\x5b\x00\x00\x00\x89' + ]) + + + def test_get_ykframe_feature_reports2(self): + """ Test one serie of non-zero bytes in the middle of the payload """ +- payload = '\x00' * 38 +- payload += '\x01\x02\x03' +- payload += '\x00' * 23 ++ payload = b'\x00' * 38 ++ payload += b'\x01\x02\x03' ++ payload += b'\x00' * 23 + res = YubiKeyFrame(command=0x32, payload=payload).to_feature_reports() + +- self.assertEqual(res, ['\x00\x00\x00\x00\x00\x00\x00\x80', +- '\x00\x00\x00\x01\x02\x03\x00\x85', +- '\x002\x01s\x00\x00\x00\x89']) ++ self.assertEqual(res, [b'\x00\x00\x00\x00\x00\x00\x00\x80', ++ b'\x00\x00\x00\x01\x02\x03\x00\x85', ++ b'\x002\x01s\x00\x00\x00\x89']) + + def test_bad_payload(self): + """ Test that we get an exception for four bytes payload """ +- self.assertRaises(yubico_exception.InputError, YubiKeyFrame, command=0x32, payload='test') ++ self.assertRaises(yubico_exception.InputError, YubiKeyFrame, command=0x32, payload=b'test') + + def test_repr(self): + """ Test string representation of object """ +diff --git a/test/test_yubikey_usb_hid.py b/test/test_yubikey_usb_hid.py +index f801a33..c7e105c 100644 +--- a/test/test_yubikey_usb_hid.py ++++ b/test/test_yubikey_usb_hid.py +@@ -41,9 +41,9 @@ def test_status(self): + def test_challenge_response(self): + """ Test challenge-response, assumes a NIST PUB 198 A.2 20 bytes test vector in Slot 2 (variable input) """ + +- secret = struct.pack('64s', 'Sample #2') ++ secret = struct.pack('64s', b'Sample #2') + response = self.YK.challenge_response(secret, mode='HMAC', slot=2) +- self.assertEqual(response, '\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24') ++ self.assertEqual(response, b'\x09\x22\xd3\x40\x5f\xaa\x3d\x19\x4f\x82\xa4\x58\x30\x73\x7d\x5c\xc6\xc7\x5d\x24') + + #@unittest.skipIf(self.YK is None, "No USB HID YubiKey found") + def test_serial(self): +diff --git a/yubico/yubico_util.py b/yubico/yubico_util.py +index 5af3956..38056e6 100644 +--- a/yubico/yubico_util.py ++++ b/yubico/yubico_util.py +@@ -15,20 +15,37 @@ + # classes + ] + ++import sys ++import string ++ + from .yubico_version import __version__ + from . import yubikey_defs + from . import yubico_exception +-import string + + _CRC_OK_RESIDUAL = 0xf0b8 + ++def ord_byte(byte): ++ """Convert a byte to its integer value""" ++ if sys.version_info < (3, 0): ++ return ord(byte) ++ else: ++ # In Python 3, single bytes are represented as integers ++ return int(byte) ++ ++def chr_byte(number): ++ """Convert an integer value to a length-1 bytestring""" ++ if sys.version_info < (3, 0): ++ return chr(number) ++ else: ++ return bytes([number]) ++ + def crc16(data): + """ +- Calculate an ISO13239 CRC checksum of the input buffer. ++ Calculate an ISO13239 CRC checksum of the input buffer (bytestring). + """ + m_crc = 0xffff + for this in data: +- m_crc ^= ord(this) ++ m_crc ^= ord_byte(this) + for _ in range(8): + j = m_crc & 1 + m_crc >>= 1 +@@ -39,6 +56,8 @@ def crc16(data): + def validate_crc16(data): + """ + Validate that the CRC of the contents of buffer is the residual OK value. ++ ++ The input is a bytestring. + """ + return crc16(data) == _CRC_OK_RESIDUAL + +@@ -74,27 +93,30 @@ def disable(self): + self.enabled = False + + def hexdump(src, length=8, colorize=False): +- """ Produce a string hexdump of src, for debug output.""" ++ """ Produce a string hexdump of src, for debug output. ++ ++ Input: bytestring; output: text string ++ """ + if not src: + return str(src) +- if type(src) is not str: +- raise yubico_exception.InputError('Hexdump \'src\' must be string (got %s)' % type(src)) ++ if type(src) is not bytes: ++ raise yubico_exception.InputError('Hexdump \'src\' must be bytestring (got %s)' % type(src)) + offset = 0 + result = '' + for this in group(src, length): + if colorize: +- last, this = this[-1:], this[:-1] ++ last, this = this[-1], this[:-1] + colors = DumpColors() + color = colors.get('RESET') +- if ord(last) & yubikey_defs.RESP_PENDING_FLAG: ++ if ord_byte(last) & yubikey_defs.RESP_PENDING_FLAG: + # write to key + color = colors.get('BLUE') +- elif ord(last) & yubikey_defs.SLOT_WRITE_FLAG: ++ elif ord_byte(last) & yubikey_defs.SLOT_WRITE_FLAG: + color = colors.get('GREEN') +- hex_s = color + ' '.join(["%02x" % ord(x) for x in this]) + colors.get('RESET') +- hex_s += " %02x" % ord(last) ++ hex_s = color + ' '.join(["%02x" % ord_byte(x) for x in this]) + colors.get('RESET') ++ hex_s += " %02x" % ord_byte(last) + else: +- hex_s = ' '.join(["%02x" % ord(x) for x in this]) ++ hex_s = ' '.join(["%02x" % ord_byte(x) for x in this]) + result += "%04X %s\n" % (offset, hex_s) + offset += length + return result +@@ -104,17 +126,25 @@ def group(data, num): + return [data[i:i+num] for i in range(0, len(data), num)] + + def modhex_decode(data): +- """ Convert a modhex string to ordinary hex. """ +- t_map = string.maketrans("cbdefghijklnrtuv", "0123456789abcdef") ++ """ Convert a modhex bytestring to ordinary hex. """ ++ try: ++ maketrans = string.maketrans ++ except AttributeError: ++ # Python 3 ++ maketrans = bytes.maketrans ++ t_map = maketrans(b"cbdefghijklnrtuv", b"0123456789abcdef") + return data.translate(t_map) + + def hotp_truncate(hmac_result, length=6): +- """ Perform the HOTP Algorithm truncating. """ ++ """ Perform the HOTP Algorithm truncating. ++ ++ Input is a bytestring. ++ """ + if len(hmac_result) != 20: + raise yubico_exception.YubicoError("HMAC-SHA-1 not 20 bytes long") +- offset = ord(hmac_result[19]) & 0xf +- bin_code = (ord(hmac_result[offset]) & 0x7f) << 24 \ +- | (ord(hmac_result[offset+1]) & 0xff) << 16 \ +- | (ord(hmac_result[offset+2]) & 0xff) << 8 \ +- | (ord(hmac_result[offset+3]) & 0xff) ++ offset = ord_byte(hmac_result[19]) & 0xf ++ bin_code = (ord_byte(hmac_result[offset]) & 0x7f) << 24 \ ++ | (ord_byte(hmac_result[offset+1]) & 0xff) << 16 \ ++ | (ord_byte(hmac_result[offset+2]) & 0xff) << 8 \ ++ | (ord_byte(hmac_result[offset+3]) & 0xff) + return bin_code % (10 ** length) +diff --git a/yubico/yubikey_config.py b/yubico/yubikey_config.py +index 3a02114..430ec7a 100644 +--- a/yubico/yubikey_config.py ++++ b/yubico/yubikey_config.py +@@ -17,6 +17,7 @@ + + from .yubico_version import __version__ + ++import sys + import struct + import binascii + from . import yubico_util +@@ -118,16 +119,16 @@ def __init__(self, ykver=None, capabilities=None, update=False, swap=False): + self.yk_req_version = (0, 0) + self.ykver = ykver + +- self.fixed = '' +- self.uid = '' +- self.key = '' +- self.access_code = '' ++ self.fixed = b'' ++ self.uid = b'' ++ self.key = b'' ++ self.access_code = b'' + + self.ticket_flags = YubiKeyConfigBits(0x0) + self.config_flags = YubiKeyConfigBits(0x0) + self.extended_flags = YubiKeyConfigBits(0x0) + +- self.unlock_code = '' ++ self.unlock_code = b'' + self._mode = '' + if update or swap: + self._require_version(major=2, minor=3) +@@ -233,10 +234,10 @@ def unlock_key(self, data): + """ + Access code to allow re-programming of your YubiKey. + +- Supply data as either a raw string, or a hexlified string prefixed by 'h:'. ++ Supply data as either a raw bytestring, or a hexlified bytestring prefixed by 'h:'. + The result, after any hex decoding, must be 6 bytes. + """ +- if data.startswith('h:'): ++ if data.startswith(b'h:'): + new = binascii.unhexlify(data[2:]) + else: + new = data +@@ -256,7 +257,7 @@ def access_key(self, data): + Supply data as either a raw string, or a hexlified string prefixed by 'h:'. + The result, after any hex decoding, must be 6 bytes. + """ +- if data.startswith('h:'): ++ if data.startswith(b'h:'): + new = binascii.unhexlify(data[2:]) + else: + new = data +@@ -272,7 +273,7 @@ def mode_yubikey_otp(self, private_uid, aes_key): + if not self.capabilities.have_yubico_OTP(): + raise yubikey_base.YubiKeyVersionError('Yubico OTP not available in %s version %d.%d' \ + % (self.capabilities.model, self.ykver[0], self.ykver[1])) +- if private_uid.startswith('h:'): ++ if private_uid.startswith(b'h:'): + private_uid = binascii.unhexlify(private_uid[2:]) + if len(private_uid) != yubikey_defs.UID_SIZE: + raise yubico_exception.InputError('Private UID must be %i bytes' % (yubikey_defs.UID_SIZE)) +@@ -299,7 +300,7 @@ def mode_oath_hotp(self, secret, digits=6, factor_seed=None, omp=0x0, tt=0x0, mu + self.config_flag('OATH_HOTP8', True) + if omp or tt or mui: + decoded_mui = self._decode_input_string(mui) +- fixed = chr(omp) + chr(tt) + decoded_mui ++ fixed = yubico_util.chr_byte(omp) + yubico_util.chr_byte(tt) + decoded_mui + self.fixed_string(fixed) + if factor_seed: + self.uid = self.uid + struct.pack('= (3, 0) and isinstance(data, str): ++ data = data.encode('ascii') ++ if data.startswith(b'm:'): ++ data = b'h:' + yubico_util.modhex_decode(data[2:]) ++ if data.startswith(b'h:'): + return(binascii.unhexlify(data[2:])) + else: + return(data) +@@ -503,10 +506,10 @@ def _set_20_bytes_key(self, data): + """ + Set a 20 bytes key. This is used in CHAL_HMAC and OATH_HOTP mode. + +- Supply data as either a raw string, or a hexlified string prefixed by 'h:'. ++ Supply data as either a raw bytestring, or a hexlified bytestring prefixed by 'h:'. + The result, after any hex decoding, must be 20 bytes. + """ +- if data.startswith('h:'): ++ if data.startswith(b'h:'): + new = binascii.unhexlify(data[2:]) + else: + new = data +diff --git a/yubico/yubikey_frame.py b/yubico/yubikey_frame.py +index 80919c9..c43c6a1 100644 +--- a/yubico/yubikey_frame.py ++++ b/yubico/yubikey_frame.py +@@ -28,11 +28,13 @@ class YubiKeyFrame: + flags. + """ + +- def __init__(self, command, payload=''): +- if payload is '': +- payload = '\x00' * 64 ++ def __init__(self, command, payload=b''): ++ if not payload: ++ payload = b'\x00' * 64 + if len(payload) != 64: + raise yubico_exception.InputError('payload must be empty or 64 bytes') ++ if not isinstance(payload, bytes): ++ raise yubico_exception.InputError('payload must be a bytestring') + self.payload = payload + self.command = command + self.crc = yubico_util.crc16(payload) +@@ -59,7 +61,7 @@ def to_string(self): + # unsigned short crc; + # unsigned char filler[3]; + # } YKFRAME; +- filler = '' ++ filler = b'' + return struct.pack('<64sBH3s', + self.payload, self.command, self.crc, filler) + +@@ -77,11 +79,11 @@ def to_feature_reports(self, debug=False): + this, rest = rest[:7], rest[7:] + if seq > 0 and rest: + # never skip first or last serie +- if this != '\x00\x00\x00\x00\x00\x00\x00': +- this += chr(yubikey_defs.SLOT_WRITE_FLAG + seq) ++ if this != b'\x00\x00\x00\x00\x00\x00\x00': ++ this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq) + out.append(self._debug_string(debug, this)) + else: +- this += chr(yubikey_defs.SLOT_WRITE_FLAG + seq) ++ this += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq) + out.append(self._debug_string(debug, this)) + seq += 1 + return out +diff --git a/yubico/yubikey_neo_usb_hid.py b/yubico/yubikey_neo_usb_hid.py +index 0543b8e..88fceba 100644 +--- a/yubico/yubikey_neo_usb_hid.py ++++ b/yubico/yubikey_neo_usb_hid.py +@@ -20,6 +20,7 @@ + from . import yubikey_usb_hid + from . import yubikey_frame + from . import yubico_exception ++from . import yubico_util + + # commands from ykdef.h + _SLOT_NDEF = 0x08 # Write YubiKey NEO NDEF +@@ -127,11 +128,11 @@ class YubiKeyNEO_NDEF(): + + ndef_type = _NDEF_URI_TYPE + ndef_str = None +- access_code = chr(0x0) * _ACC_CODE_SIZE ++ access_code = yubico_util.chr_byte(0x0) * _ACC_CODE_SIZE + # For _NDEF_URI_TYPE + ndef_uri_rt = 0x0 # No prepending + # For _NDEF_TEXT_TYPE +- ndef_text_lang = 'en' ++ ndef_text_lang = b'en' + ndef_text_enc = 'UTF-8' + + def __init__(self, data, access_code = None): +@@ -199,7 +200,7 @@ def to_frame(self, slot=_SLOT_NDEF): + Return the current configuration as a YubiKeyFrame object. + """ + data = self.to_string() +- payload = data.ljust(64, chr(0x0)) ++ payload = data.ljust(64, b'\0') + return yubikey_frame.YubiKeyFrame(command = slot, payload = payload) + + def _encode_ndef_uri_type(self, data): +@@ -211,11 +212,11 @@ def _encode_ndef_uri_type(self, data): + """ + t = 0x0 + for (code, prefix) in uri_identifiers: +- if data[:len(prefix)].lower() == prefix: ++ if data[:len(prefix)].decode('latin-1').lower() == prefix: + t = code + data = data[len(prefix):] + break +- data = chr(t) + data ++ data = yubico_util.chr_byte(t) + data + return data + + def _encode_ndef_text_params(self, data): +@@ -226,4 +227,4 @@ def _encode_ndef_text_params(self, data): + status = len(self.ndef_text_lang) + if self.ndef_text_enc == 'UTF16': + status = status & 0b10000000 +- return chr(status) + self.ndef_text_lang + data ++ return yubico_util.chr_byte(status) + self.ndef_text_lang + data +diff --git a/yubico/yubikey_usb_hid.py b/yubico/yubikey_usb_hid.py +index 3cf47a4..3a90701 100644 +--- a/yubico/yubikey_usb_hid.py ++++ b/yubico/yubikey_usb_hid.py +@@ -248,16 +248,16 @@ def _challenge_response(self, challenge, mode, slot, variable, may_block): + raise yubico_exception.InputError('Mode HMAC challenge too big (%i/%i)' \ + % (yubikey_defs.SHA1_MAX_BLOCK_SIZE, len(challenge))) + if len(challenge) < yubikey_defs.SHA1_MAX_BLOCK_SIZE: +- pad_with = chr(0x0) +- if variable and challenge[-1] == pad_with: +- pad_with = chr(0xff) ++ pad_with = b'\0' ++ if variable and challenge[-1:] == pad_with: ++ pad_with = b'\xff' + challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, pad_with) + response_len = yubikey_defs.SHA1_DIGEST_SIZE + elif mode == 'OTP': + if len(challenge) != yubikey_defs.UID_SIZE: + raise yubico_exception.InputError('Mode OTP challenge must be %i bytes (got %i)' \ + % (yubikey_defs.UID_SIZE, len(challenge))) +- challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, chr(0x0)) ++ challenge = challenge.ljust(yubikey_defs.SHA1_MAX_BLOCK_SIZE, b'\0') + response_len = 16 + else: + raise yubico_exception.InputError('Invalid mode supplied (%s, valid values are HMAC and OTP)' \ +@@ -297,7 +297,7 @@ def _read_response(self, may_block=False): + # continue reading while response pending is set + while True: + this = self._read() +- flags = ord(this[7]) ++ flags = yubico_util.ord_byte(this[7]) + if flags & yubikey_defs.RESP_PENDING_FLAG: + seq = flags & 0b00011111 + if res and (seq == 0): +@@ -321,7 +321,7 @@ def _read(self): + self._debug("Failed reading %i bytes (got %i) from USB HID YubiKey.\n" + % (_FEATURE_RPT_SIZE, recv)) + raise YubiKeyUSBHIDError('Failed reading from USB HID YubiKey') +- data = ''.join(chr(c) for c in recv) ++ data = b''.join(yubico_util.chr_byte(c) for c in recv) + self._debug("READ : %s" % (yubico_util.hexdump(data, colorize=True))) + return data + +@@ -344,7 +344,7 @@ def _write_reset(self): + """ + Reset read mode by issuing a dummy write. + """ +- data = '\x00\x00\x00\x00\x00\x00\x00\x8f' ++ data = b'\x00\x00\x00\x00\x00\x00\x00\x8f' + self._raw_write(data) + self._waitfor_clear(yubikey_defs.SLOT_WRITE_FLAG) + return True +@@ -401,7 +401,7 @@ def _waitfor(self, mode, mask, may_block, timeout=2): + resp_timeout = False # YubiKey hasn't indicated RESP_TIMEOUT (yet) + while not finished: + this = self._read() +- flags = ord(this[7]) ++ flags = yubico_util.ord_byte(this[7]) + + if flags & yubikey_defs.RESP_TIMEOUT_WAIT_FLAG: + if not resp_timeout: + +From adde4903eb9efa34b9b5fbd2bf988f934333fd11 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 1 Jul 2015 20:02:38 +0200 +Subject: [PATCH 6/6] Unshadow a hexdump test + +The test_hexdump method was defined twice, the second overwriting +the first. Use a unique name to have them both run. +--- + test/test_yubico.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/test_yubico.py b/test/test_yubico.py +index cd87f42..de2e2a5 100644 +--- a/test/test_yubico.py ++++ b/test/test_yubico.py +@@ -35,7 +35,7 @@ def test_hexdump(self): + self.assertEqual(yubico_util.hexdump(bytes, length=4), \ + '0000 01 02 03 04\n0004 05 06 07 08\n') + +- def test_hexdump(self): ++ def test_hexdump2(self): + """ Test hexdump function, with colors """ + bytes = b'\x01\x02\x03\x04\x05\x06\x07\x08' + self.assertEqual(yubico_util.hexdump(bytes, length=4, colorize=True), \ diff --git a/python-yubico-python3_util_examples.patch b/python-yubico-python3_util_examples.patch new file mode 100644 index 0000000..8deb620 --- /dev/null +++ b/python-yubico-python3_util_examples.patch @@ -0,0 +1,547 @@ +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) diff --git a/python-yubico.spec b/python-yubico.spec index 749f4ec..da9024f 100644 --- a/python-yubico.spec +++ b/python-yubico.spec @@ -3,15 +3,25 @@ %{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %endif +%if 0%{?rhel} != 0 && 0%{?rhel} <= 7 +# Do not build python3 subpackage for RHEL <= 7 +%bcond_with python3 +%else +%bcond_without python3 +%endif + Name: python-yubico Version: 1.2.3 -Release: 2%{?dist} +Release: 3%{?dist} Summary: Pure-python library for interacting with Yubikeys License: BSD URL: https://github.com/Yubico/python-yubico Source0: https://github.com/Yubico/python-yubico/archive/%{name}-%{version}.tar.gz +Patch0: %{name}-python3.patch +Patch1: %{name}-python3_util_examples.patch + BuildArch: noarch BuildRequires: python2-devel BuildRequires: python-setuptools @@ -21,33 +31,98 @@ BuildRequires: pyusb %endif Requires: pyusb +%if %{with python3} +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-nose +BuildRequires: python3-pyusb +%endif + %description Pure-python library for interacting with Yubikeys +%if %{with python3} +%package -n python3-yubico +Summary: Pure-python library for interacting with Yubikeys + +%description -n python3-yubico +Pure-python library for interacting with Yubikeys. For Python 3. +%endif + %prep -%setup -q -n %{name}-%{name}-%{version} +%setup -qc + +mv %{name}-%{name}-%{version} python2 + +pushd python2 +sed -i 's|setup_requires=|tests_require=|' setup.py +popd + +%if %{with python3} +cp -a python{2,3} + +pushd python3 +%patch0 -p1 +%patch1 -p1 +popd +%endif %build -sed -i 's|setup_requires=|tests_require=|' setup.py +pushd python2 %{__python2} setup.py build +popd + +%if %{with python3} +pushd python3 +%{__python3} setup.py build +popd +%endif %install +pushd python2 %{__python2} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT +popd + +%if %{with python3} +pushd python3 +%{__python3} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT +popd +%endif %files -%doc COPYING NEWS README +%doc python2/COPYING python2/NEWS python2/README %{python2_sitelib}/* +%if %{with python3} +%files -n python3-yubico +%doc python3/COPYING python3/NEWS python3/README +%{python3_sitelib}/* +%endif + %if 0%{?rhel} == 0 || 0%{?rhel} >= 7 %check + +pushd python2 # Exclude tests that require a physical yubikey attached. nosetests -e test_challenge_response -e test_serial -e test_status -%endif +popd + +%if %{with python3} +pushd python3 +# Exclude tests that require a physical yubikey attached. +nosetests-%{python3_version} -e test_challenge_response -e test_serial -e test_status +popd +%endif # with python3 + +%endif # rhel %changelog +* Mon Jul 20 2015 Miro HronĨok - 1.2.3-3 +- Add Python 3 subpackage (#1244237) + * Thu Jun 18 2015 Fedora Release Engineering - 1.2.3-2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild