def _challenge_response(self, challenge, mode, slot, variable, may_block): """ Do challenge-response with a YubiKey > 2.0. """ # Check length and pad challenge if appropriate if mode == 'HMAC': if len(challenge) > yubikey_defs.SHA1_MAX_BLOCK_SIZE: 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) 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)) response_len = 16 else: raise yubico_exception.InputError('Invalid mode supplied (%s, valid values are HMAC and OTP)' \ % (mode)) try: command = _CMD_CHALLENGE[mode][slot] except: raise yubico_exception.InputError('Invalid slot specified (%s)' % (slot)) frame = yubikey_frame.YubiKeyFrame(command=command, payload=challenge) self._write(frame) response = self._read_response(may_block=may_block) if not yubico_util.validate_crc16(response[:response_len + 2]): raise YubiKeyUSBHIDError("Read from device failed CRC check") return response[:response_len]
def mode_challenge_response(self, secret, type='HMAC', variable=True, require_button=False): """ Set the YubiKey up for challenge-response operation. `type' can be 'HMAC' or 'OTP'. `variable' is only applicable to type 'HMAC'. For type HMAC, `secret' is expected to be 20 bytes (160 bits). For type OTP, `secret' is expected to be 16 bytes (128 bits). Requires YubiKey 2.2. """ 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])) self._change_mode('CHAL_RESP', major=2, minor=2) if type.upper() == 'HMAC': self.config_flag('CHAL_HMAC', True) self.config_flag('HMAC_LT64', variable) self._set_20_bytes_key(secret) else: # type is 'OTP', checked above self.config_flag('CHAL_YUBICO', True) self.aes_key(secret) self.config_flag('CHAL_BTN_TRIG', require_button)
def mode_oath_hotp(self, secret, digits=6, factor_seed=None, omp=0x0, tt=0x0, mui=''): """ Set the YubiKey up for OATH-HOTP operation. 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])) if digits != 6 and digits != 8: raise yubico_exception.InputError( 'OATH-HOTP digits must be 6 or 8') self._change_mode('OATH_HOTP', major=2, minor=1) self._set_20_bytes_key(secret) if digits == 8: 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 self.fixed_string(fixed) if factor_seed: self.uid = self.uid + struct.pack('<H', factor_seed)
def mode_challenge_response(self, secret, type='HMAC', variable=True, require_button=False): """ Set the YubiKey up for challenge-response operation. `type' can be 'HMAC' or 'OTP'. `variable' is only applicable to type 'HMAC'. For type HMAC, `secret' is expected to be 20 bytes (160 bits). For type OTP, `secret' is expected to be 16 bytes (128 bits). Requires YubiKey 2.2. """ self._change_mode('CHAL_RESP', major=2, minor=2) if type.upper() == 'HMAC': self.config_flag('CHAL_HMAC', True) self.config_flag('HMAC_LT64', variable) self._set_20_bytes_key(secret) elif type.upper() == 'OTP': self.config_flag('CHAL_YUBICO', True) self.aes_key(secret) else: raise yubico_exception.InputError('Invalid \'type\' (%s)' % type) self.config_flag('CHAL_BTN_TRIG', require_button)
def hexdump(src, length=8, colorize=False): """ Produce a string hexdump of src, for debug output.""" 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)) offset = 0 result = '' for this in group(src, length): if colorize: last, this = this[-1:], this[:-1] colors = DumpColors() color = colors.get('RESET') if ord(last) & yubikey_defs.RESP_PENDING_FLAG: # write to key color = colors.get('BLUE') elif ord(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) else: hex_s = ' '.join(["%02x" % ord(x) for x in this]) result += "%04X %s\n" % (offset, hex_s) offset += length return result
def __init__(self, command, payload=''): if payload is '': payload = '\x00' * 64 if len(payload) != 64: raise yubico_exception.InputError('payload must be empty or 64 bytes') self.payload = payload self.command = command self.crc = yubico_util.crc16(payload)
def access_key(self, data): """ Set a new access code which will be required for future re-programmings of your YubiKey. 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:'): new = binascii.unhexlify(data[2:]) else: new = data if len(new) == 6: self.access_code = new else: raise yubico_exception.InputError( 'Access key must be exactly 6 bytes')
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])) if private_uid.startswith('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)) self._change_mode('YUBIKEY_OTP', major=0, minor=9) self.uid = private_uid self.aes_key(aes_key)
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:'. The result, after any hex decoding, must be 20 bytes. """ if data.startswith('h:'): new = binascii.unhexlify(data[2:]) else: new = data if len(new) == 20: self.key = new[:16] self.uid = new[16:] else: raise yubico_exception.InputError( 'HMAC key must be exactly 20 bytes')
def aes_key(self, data): """ AES128 key to program into YubiKey. Supply data as either a raw string, or a hexlified string prefixed by 'h:'. The result, after any hex decoding, must be 16 bytes. """ old = self.key if data: new = self._decode_input_string(data) if len(new) == 16: self.key = new else: raise yubico_exception.InputError( 'AES128 key must be exactly 16 bytes') return old
def fixed_string(self, data=None): """ The fixed string is used to identify a particular Yubikey device. The fixed string is referred to as the 'Token Identifier' in OATH-HOTP mode. The length of the fixed string can be set between 0 and 16 bytes. Tip: This can also be used to extend the length of a static password. """ old = self.fixed if data != None: new = self._decode_input_string(data) if len(new) <= 16: self.fixed = new else: raise yubico_exception.InputError( 'The "fixed" string must be 0..16 bytes') return old
def unlock_key(self, data): """ Access code to allow re-program your YubiKey. 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:'): new = binascii.unhexlify(data[2:]) else: new = data if len(new) == 6: self.unlock_code = new if not self.access_code: # Don't reset the access code when programming, unless that seems # to be the intent of the calling program. self.access_code = new else: raise yubico_exception.InputError( 'Unlock key must be exactly 6 bytes')
def extended_flag(self, which, new=None): """ Get or set a extended flag. 'which' can be either a string ('SERIAL_API_VISIBLE' etc.), or an integer. You should ALWAYS use a string, unless you really know what you are doing. """ 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), \ 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) value = flag.to_integer() else: if type(which) is not int: raise yubico_exception.InputError( 'Unknown non-integer ExtendedFlag (%s)' % which) value = which return self.extended_flags.get_set(value, new)
def extended_flag(self, which, new=None): """ Get or set a extended flag. 'which' can be either a string ('APPEND_CR' etc.), or an integer. You should ALWAYS use a string, unless you really know what you are doing. """ flag = _get_flag(which, ExtendedFlags) if flag: req_major, req_minor = flag.req_version() if self.ykver and not flag.is_compatible_ver(self.ykver): raise YubiKeyConfigError( 'Config flag %s requires YubiKey %d.%d, and this is %d.%d' % (which, req_major, req_minor, self.ykver[0], self.ykver[1])) self._require_version(major=req_major, minor=req_minor) value = flag.to_integer() else: if type(which) is not int: raise yubico_exception.InputError( 'Unknown non-integer ExtendedFlag (%s)' % which) value = which return self.extended_flags.get_set(value, new)
def mode_challenge_response(self, secret, type='HMAC', variable=True, require_button=False): """ Set the YubiKey up for challenge-response operation. type can be 'HMAC' or 'Yubico'. variable is only applicable to type 'HMAC'. Requires YubiKey 2.2. """ self._change_mode('CHAL_RESP', major=2, minor=2) if type.upper() == 'HMAC': self.config_flag('CHAL_HMAC', True) self.config_flag('HMAC_LT64', variable) elif type.lower() == 'yubico': self.config_flag('CHAL_YUBICO', True) else: raise yubico_exception.InputError('Invalid \'type\' (%s)' % type) self.config_flag('CHAL_BTN_TRIG', require_button) self._set_20_bytes_key(secret)