Codebase list python-yubico / fresh-releases/main yubico / yubikey_4_usb_hid.py
fresh-releases/main

Tree @fresh-releases/main (Download .tar.gz)

yubikey_4_usb_hid.py @fresh-releases/mainraw · history · blame

"""
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]