""" module for accessing a USB HID YubiKey 4 """ # Copyright (c) 2012 Yubico AB # See the file COPYING for licence statement. __all__ = [ # constants # functions # classes 'YubiKey4_USBHID', 'YubiKey4_USBHIDError' ] from .yubikey_defs import SLOT, MODE, YK4_CAPA from . import yubikey_frame from . import yubikey_base from . import yubico_exception from . import yubico_util from . import yubikey_neo_usb_hid MODE_CAPABILITIES = { # Required capabilities to support USB mode. MODE.OTP : [YK4_CAPA.OTP], MODE.CCID : [YK4_CAPA.CCID], MODE.OTP_CCID : [YK4_CAPA.OTP, YK4_CAPA.CCID], MODE.U2F : [YK4_CAPA.U2F], MODE.OTP_U2F : [YK4_CAPA.OTP, YK4_CAPA.U2F], MODE.U2F_CCID : [YK4_CAPA.U2F, YK4_CAPA.CCID], MODE.OTP_U2F_CCID : [YK4_CAPA.OTP, YK4_CAPA.U2F, YK4_CAPA.CCID] } class YubiKey4_USBHIDError(yubico_exception.YubicoError): """ Exception raised for errors with the YK4 USB HID communication. """ class YubiKey4_USBHIDCapabilities(yubikey_neo_usb_hid.YubiKeyNEO_USBHIDCapabilities): """ Capabilities of current YubiKey 4. """ _yk4_capa = 0 def _set_yk4_capa(self, yk4_capa): int_val = 0 for b in yk4_capa: int_val <<= 8 int_val += yubico_util.ord_byte(b) self._yk4_capa = int_val def have_nfc_ndef(self, slot=1): return False def have_usb_mode(self, mode): mode &= ~MODE.FLAG_EJECT # Mask away eject flag if self.version < (4, 1, 0): # YK Plus is locked in OTP+U2F return mode == MODE.OTP_U2F for cap_req in MODE_CAPABILITIES.get(mode, [0]): if not self.have_capability(cap_req): return False return True def have_capabilities(self): return self.version >= (4, 1, 0) def have_capability(self, capability): return self._yk4_capa & capability != 0 class YubiKey4_USBHID(yubikey_neo_usb_hid.YubiKeyNEO_USBHID): """ Class for accessing a YubiKey 4 over USB HID. """ model = 'YubiKey 4' description = 'YubiKey 4' _capabilities_cls = YubiKey4_USBHIDCapabilities def __init__(self, debug=False, skip=0, hid_device=None): """ Find and connect to a YubiKey 4 (USB HID). Attributes : skip -- number of YubiKeys to skip debug -- True or False """ super(YubiKey4_USBHID, self).__init__(debug, skip, hid_device) if self.version_num() < (4, 0, 0): raise yubikey_base.YubiKeyVersionError( "Incorrect version for YubiKey 4 %s" % self.version()) elif self.version_num() < (4, 1, 0): self.description = 'YubiKey Plus' elif self.version_num() < (4, 2, 0): self.description = 'YubiKey Edge/Edge-n' if self.capabilities.have_capabilities(): data = yubico_util.tlv_parse(self._read_capabilities()) self.capabilities._set_yk4_capa(data.get(YK4_CAPA.TAG.CAPA, b'')) def _read_capabilities(self): """ Read the capabilities list from a YubiKey >= 4.0.0 """ frame = yubikey_frame.YubiKeyFrame(command=SLOT.YK4_CAPABILITIES) self._device._write(frame) response = self._device._read_response() r_len = yubico_util.ord_byte(response[0]) # 1 byte length, 2 byte CRC. if not yubico_util.validate_crc16(response[:r_len+3]): raise YubiKey4_USBHIDError("Read from device failed CRC check") return response[1:r_len+1]