def test_28_yubikey_utils(self): self.assertEquals(modhex_encode(b'\x47'), 'fi') self.assertEquals(modhex_encode(b'\xba\xad\xf0\x0d'), 'nlltvcct') self.assertEquals(modhex_encode(binascii.unhexlify('0123456789abcdef')), 'cbdefghijklnrtuv') self.assertEquals(modhex_encode('Hallo'), 'fjhbhrhrhv') # and the other way around self.assertEquals(modhex_decode('fi'), b'\x47') self.assertEquals(modhex_decode('nlltvcct'), b'\xba\xad\xf0\x0d') self.assertEquals(modhex_decode('cbdefghijklnrtuv'), binascii.unhexlify('0123456789abcdef')) self.assertEquals(modhex_decode('fjhbhrhrhv'), b'Hallo') # now test the crc function self.assertEquals(checksum(b'\x01\x02\x03\x04'), 0xc66e) self.assertEquals(checksum(b'\x01\x02\x03\x04\x919'), 0xf0b8)
def test_28_yubikey_utils(self): self.assertEquals(modhex_encode(b'\x47'), 'fi') self.assertEquals(modhex_encode(b'\xba\xad\xf0\x0d'), 'nlltvcct') self.assertEquals(modhex_encode(binascii.unhexlify('0123456789abcdef')), 'cbdefghijklnrtuv') self.assertEquals(modhex_encode('Hallo'), 'fjhbhrhrhv') # and the other way around self.assertEquals(modhex_decode('fi'), b'\x47') self.assertEquals(modhex_decode('nlltvcct'), b'\xba\xad\xf0\x0d') self.assertEquals(modhex_decode('cbdefghijklnrtuv'), binascii.unhexlify('0123456789abcdef')) self.assertEquals(modhex_decode('fjhbhrhrhv'), b'Hallo') # now test the crc function self.assertEquals(checksum(b'\x01\x02\x03\x04'), 0xc66e) self.assertEquals(checksum(b'\x01\x02\x03\x04\x919'), 0xf0b8)
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ validate the token otp against a given otpvalue :param anOtpVal: the to be verified otpvalue :type anOtpVal: string :param counter: the counter state. It is not used by the Yubikey because the current counter value is sent encrypted inside the OTP value :type counter: int :param window: the counter +window, which is not used in the Yubikey because the current counter value is sent encrypted inside the OTP, allowing a simple comparison between the encrypted counter value and the stored counter value :type window: int :param options: the dict, which could contain token specific info :type options: dict :return: the counter state or an error code (< 0): -1 if the OTP is old (counter < stored counter) -2 if the private_uid sent in the OTP is wrong (different from the one stored with the token) -3 if the CRC verification fails :rtype: int """ res = -1 serial = self.token.serial secret = self.token.get_otpkey() # The prefix is the characters in front of the last 32 chars yubi_prefix = anOtpVal[:-32] # The variable otp val is the last 32 chars yubi_otp = anOtpVal[-32:] try: otp_bin = modhex_decode(yubi_otp) except KeyError: # The OTP value is no yubikey aes otp value and can not be decoded return -4 msg_bin = secret.aes_decrypt(otp_bin) msg_hex = binascii.hexlify(msg_bin) # The checksum is a CRC-16 (16-bit ISO 13239 1st complement) that # occupies the last 2 bytes of the decrypted OTP value. Calculating the # CRC-16 checksum of the whole decrypted OTP should give a fixed # residual # of 0xf0b8 (see Yubikey-Manual - Chapter 6: Implementation details). log.debug("calculated checksum (61624): {0!r}".format( checksum(msg_hex))) if checksum(msg_hex) != 0xf0b8: # pragma: no cover log.warning("CRC checksum for token {0!r} failed".format(serial)) return -3 uid = msg_hex[0:12] log.debug("uid: {0!r}".format(uid)) log.debug("prefix: {0!r}".format( binascii.hexlify(modhex_decode(yubi_prefix)))) # usage_counter can go from 1 – 0x7fff usage_counter = msg_hex[12:16] timestamp = msg_hex[16:22] # session counter can go from 00 to 0xff session_counter = msg_hex[22:24] random = msg_hex[24:28] crc = msg_hex[28:] log.debug("decrypted: usage_count: {0!r}, session_count: {1!r}".format( usage_counter, session_counter)) # create the counter as integer # Note: The usage counter is stored LSB! count_hex = usage_counter[2:4] + usage_counter[0:2] + session_counter count_int = int(count_hex, 16) log.debug('decrypted counter: {0!r}'.format(count_int)) tokenid = self.get_tokeninfo("yubikey.tokenid") if not tokenid: log.debug("Got no tokenid for {0!r}. Setting to {1!r}.".format( serial, uid)) tokenid = uid self.add_tokeninfo("yubikey.tokenid", tokenid) prefix = self.get_tokeninfo("yubikey.prefix") if not prefix: log.debug("Got no prefix for {0!r}. Setting to {1!r}.".format( serial, yubi_prefix)) self.add_tokeninfo("yubikey.prefix", yubi_prefix) if tokenid != uid: # wrong token! log.warning("The wrong token was presented for %r. " "Got %r, expected %r." % (serial, uid, tokenid)) return -2 # TODO: We also could check the timestamp # see http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf log.debug('compare counter to database counter: {0!r}'.format( self.token.count)) if count_int >= self.token.count: res = count_int # on success we save the used counter self.inc_otp_counter(res) return res
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ validate the token otp against a given otpvalue :param anOtpVal: the to be verified otpvalue :type anOtpVal: string :param counter: the counter state. It is not used by the Yubikey because the current counter value is sent encrypted inside the OTP value :type counter: int :param window: the counter +window, which is not used in the Yubikey because the current counter value is sent encrypted inside the OTP, allowing a simple comparison between the encrypted counter value and the stored counter value :type window: int :param options: the dict, which could contain token specific info :type options: dict :return: the counter state or an error code (< 0): -1 if the OTP is old (counter < stored counter) -2 if the private_uid sent in the OTP is wrong (different from the one stored with the token) -3 if the CRC verification fails :rtype: int """ res = -1 serial = self.token.serial secret = self.token.get_otpkey() # The prefix is the characters in front of the last 32 chars yubi_prefix = anOtpVal[:-32] # The variable otp val is the last 32 chars yubi_otp = anOtpVal[-32:] # TODO: We can also check the PREFIX! At the moment, we do not use it! try: otp_bin = modhex_decode(yubi_otp) except KeyError: # The OTP value is no yubikey aes otp value and can not be decoded return -4 msg_bin = secret.aes_decrypt(otp_bin) msg_hex = binascii.hexlify(msg_bin) # The checksum is a CRC-16 (16-bit ISO 13239 1st complement) that # occupies the last 2 bytes of the decrypted OTP value. Calculating the # CRC-16 checksum of the whole decrypted OTP should give a fixed # residual # of 0xf0b8 (see Yubikey-Manual - Chapter 6: Implementation details). log.debug("calculated checksum (61624): %r" % checksum(msg_hex)) if checksum(msg_hex) != 0xf0b8: # pragma: no cover log.warning("CRC checksum for token %r failed" % serial) return -3 uid = msg_hex[0:12] log.debug("uid: %r" % uid) log.debug("prefix: %r" % binascii.hexlify(modhex_decode(yubi_prefix))) # usage_counter can go from 1 – 0x7fff usage_counter = msg_hex[12:16] timestamp = msg_hex[16:22] # session counter can go from 00 to 0xff session_counter = msg_hex[22:24] random = msg_hex[24:28] crc = msg_hex[28:] log.debug("decrypted: usage_count: %r, session_count: %r" % (usage_counter, session_counter)) # create the counter as integer # Note: The usage counter is stored LSB! count_hex = usage_counter[2:4] + usage_counter[0:2] + session_counter count_int = int(count_hex, 16) log.debug('decrypted counter: %r' % count_int) tokenid = self.get_tokeninfo("yubikey.tokenid") if not tokenid: log.debug("Got no tokenid for %r. Setting to %r." % (serial, uid)) tokenid = uid self.add_tokeninfo("yubikey.tokenid", tokenid) if tokenid != uid: # wrong token! log.warning("The wrong token was presented for %r. Got %r, expected %r." % (serial, uid, tokenid)) return -2 # TODO: We also could check the timestamp # see http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf log.debug('compare counter to database counter: %r' % self.token.count) if count_int >= self.token.count: res = count_int # on success we save the used counter self.inc_otp_counter(res) return res