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), \