135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
"""
|
|
module for creating frames of data that can be sent to a YubiKey
|
|
"""
|
|
# Copyright (c) 2010, Yubico AB
|
|
# See the file COPYING for licence statement.
|
|
|
|
__all__ = [
|
|
# constants
|
|
# functions
|
|
# classes
|
|
'YubiKeyFrame',
|
|
]
|
|
|
|
import struct
|
|
|
|
from . import yubico_util
|
|
from . import yubikey_defs
|
|
from . import yubico_exception
|
|
from .yubico_version import __version__
|
|
|
|
from .yubikey_defs import SLOT
|
|
|
|
class YubiKeyFrame:
|
|
"""
|
|
Class containing an YKFRAME (as defined in ykdef.h).
|
|
|
|
A frame is basically 64 bytes of data. When this is to be sent
|
|
to a YubiKey, it is put inside 10 USB HID feature reports. Each
|
|
feature report is 7 bytes of data plus 1 byte of sequencing and
|
|
flags.
|
|
"""
|
|
|
|
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)
|
|
|
|
def __repr__(self):
|
|
return '<%s.%s instance at %s: %s>' % (
|
|
self.__class__.__module__,
|
|
self.__class__.__name__,
|
|
hex(id(self)),
|
|
self.command
|
|
)
|
|
|
|
def to_string(self):
|
|
"""
|
|
Return the frame as a 70 byte string.
|
|
"""
|
|
# From ykdef.h :
|
|
#
|
|
# // Frame structure
|
|
# #define SLOT_DATA_SIZE 64
|
|
# typedef struct {
|
|
# unsigned char payload[SLOT_DATA_SIZE];
|
|
# unsigned char slot;
|
|
# unsigned short crc;
|
|
# unsigned char filler[3];
|
|
# } YKFRAME;
|
|
filler = b''
|
|
return struct.pack('<64sBH3s',
|
|
self.payload, self.command, self.crc, filler)
|
|
|
|
def to_feature_reports(self, debug=False):
|
|
"""
|
|
Return the frame as an array of 8-byte parts, ready to be sent to a YubiKey.
|
|
"""
|
|
rest = self.to_string()
|
|
seq = 0
|
|
out = []
|
|
# When sending a frame to the YubiKey, we can (should) remove any
|
|
# 7-byte serie that only consists of '\x00', besides the first
|
|
# and last serie.
|
|
while rest:
|
|
this, rest = rest[:7], rest[7:]
|
|
if seq > 0 and rest:
|
|
# never skip first or last serie
|
|
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 += yubico_util.chr_byte(yubikey_defs.SLOT_WRITE_FLAG + seq)
|
|
out.append(self._debug_string(debug, this))
|
|
seq += 1
|
|
return out
|
|
|
|
def _debug_string(self, debug, data):
|
|
"""
|
|
Annotate a frames data, if debug is True.
|
|
"""
|
|
if not debug:
|
|
return data
|
|
if self.command in [
|
|
SLOT.CONFIG,
|
|
SLOT.CONFIG2,
|
|
SLOT.UPDATE1,
|
|
SLOT.UPDATE2,
|
|
SLOT.SWAP,
|
|
]:
|
|
# annotate according to config_st (see ykdef.h)
|
|
if yubico_util.ord_byte(data[-1]) == 0x80:
|
|
return (data, "FFFFFFF") # F = Fixed data (16 bytes)
|
|
if yubico_util.ord_byte(data[-1]) == 0x81:
|
|
return (data, "FFFFFFF")
|
|
if yubico_util.ord_byte(data[-1]) == 0x82:
|
|
return (data, "FFUUUUU") # U = UID (6 bytes)
|
|
if yubico_util.ord_byte(data[-1]) == 0x83:
|
|
return (data, "UKKKKKK") # K = Key (16 bytes)
|
|
if yubico_util.ord_byte(data[-1]) == 0x84:
|
|
return (data, "KKKKKKK")
|
|
if yubico_util.ord_byte(data[-1]) == 0x85:
|
|
return (data, "KKKAAAA") # A = Access code to set (6 bytes)
|
|
if yubico_util.ord_byte(data[-1]) == 0x86:
|
|
return (data, "AAlETCr") # l = Length of fixed field (1 byte)
|
|
# E = extFlags (1 byte)
|
|
# T = tktFlags (1 byte)
|
|
# C = cfgFlags (1 byte)
|
|
# r = RFU (2 bytes)
|
|
if yubico_util.ord_byte(data[-1]) == 0x87:
|
|
return (data, "rCRaaaa") # CR = CRC16 checksum (2 bytes)
|
|
# a = Access code to use (6 bytes)
|
|
if yubico_util.ord_byte(data[-1]) == 0x88:
|
|
return (data, 'aa')
|
|
# after payload
|
|
if yubico_util.ord_byte(data[-1]) == 0x89:
|
|
return (data, " Scr")
|
|
|
|
return (data, '')
|