Example #1
0
    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]
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
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
Example #6
0
 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)
Example #7
0
    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')
Example #8
0
    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)
Example #9
0
    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')
Example #10
0
    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
Example #11
0
    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
Example #12
0
    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')
Example #13
0
    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)
Example #14
0
    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)
Example #15
0
    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)