def get_name(info: DeviceInfo, key_type: Optional[YUBIKEY]) -> str: """Determine the product name of a YubiKey""" usb_supported = info.supported_capabilities[TRANSPORT.USB] if not key_type: if info.serial is None and _fido_only(usb_supported): key_type = YUBIKEY.SKY elif info.version[0] == 3: key_type = YUBIKEY.NEO else: key_type = YUBIKEY.YK4 device_name = key_type.value if key_type == YUBIKEY.SKY: if CAPABILITY.FIDO2 not in usb_supported: device_name = "FIDO U2F Security Key" # SKY 1 if info.has_transport(TRANSPORT.NFC): device_name = "Security Key NFC" elif key_type == YUBIKEY.YK4: if info.version[0] == 0: return "Yubikey (%d.%d.%d)" % info.version if _is_preview(info.version): device_name = "YubiKey Preview" elif is_fips_version(info.version): device_name = "YubiKey FIPS" elif usb_supported == CAPABILITY.OTP | CAPABILITY.U2F: device_name = "YubiKey Edge" elif info.version >= (5, 1, 0): if info.form_factor in (FORM_FACTOR.USB_A_BIO, FORM_FACTOR.USB_C_BIO): device_name = "YubiKey " if info.form_factor == FORM_FACTOR.USB_C_BIO: device_name += "C " device_name += "BIO" if _fido_only(usb_supported): device_name += " (FIDO Edition)" else: device_name = "YubiKey 5" if info.form_factor == FORM_FACTOR.USB_A_KEYCHAIN: if info.has_transport(TRANSPORT.NFC): device_name += " NFC" else: device_name += "A" elif info.form_factor == FORM_FACTOR.USB_A_NANO: device_name += " Nano" elif info.form_factor == FORM_FACTOR.USB_C_KEYCHAIN: device_name += "C" if info.has_transport(TRANSPORT.NFC): device_name += " NFC" elif info.form_factor == FORM_FACTOR.USB_C_NANO: device_name += "C Nano" elif info.form_factor == FORM_FACTOR.USB_C_LIGHTNING: device_name += "Ci" return device_name
def get_name(info: DeviceInfo, key_type: Optional[YUBIKEY]) -> str: """Determine the product name of a YubiKey""" usb_supported = info.supported_applications[TRANSPORT.USB] if not key_type: if info.serial is None and _fido_only(usb_supported): key_type = YUBIKEY.SKY elif info.version[0] == 3: key_type = YUBIKEY.NEO else: key_type = YUBIKEY.YK4 device_name = key_type.value if key_type == YUBIKEY.SKY: if APPLICATION.FIDO2 not in usb_supported: device_name = "FIDO U2F Security Key" # SKY 1 if info.has_transport(TRANSPORT.NFC): device_name = "Security Key NFC" elif key_type == YUBIKEY.YK4: if usb_supported == APPLICATION.OTP | APPLICATION.U2F: device_name = "YubiKey Edge" elif (5, 0, 0) <= info.version < (5, 1, 0) or info.version in [ (5, 2, 0), (5, 2, 1), (5, 2, 2), ]: device_name = "YubiKey Preview" elif info.version >= (5, 1, 0): device_name = "YubiKey 5" if info.form_factor == FORM_FACTOR.USB_A_KEYCHAIN: if info.has_transport(TRANSPORT.NFC): device_name += " NFC" else: device_name += "A" elif info.form_factor == FORM_FACTOR.USB_A_NANO: device_name += " Nano" elif info.form_factor == FORM_FACTOR.USB_C_KEYCHAIN: device_name += "C" if info.has_transport(TRANSPORT.NFC): device_name += " NFC" elif info.form_factor == FORM_FACTOR.USB_C_NANO: device_name += "C Nano" elif info.form_factor == FORM_FACTOR.USB_C_LIGHTNING: device_name += "Ci" elif is_fips_version(info.version): device_name = "YubiKey FIPS" return device_name
def _read_info_ctap(conn, key_type, interfaces): try: mgmt = ManagementSession(conn) return mgmt.read_device_info() except Exception: # SKY 1 or NEO version = (3, 0, 0) # Guess, no way to know supported_apps = {TRANSPORT.USB: CAPABILITY.U2F} if key_type == YUBIKEY.NEO: supported_apps[TRANSPORT.USB] |= BASE_NEO_APPS supported_apps[TRANSPORT.NFC] = supported_apps[TRANSPORT.USB] return DeviceInfo( config=DeviceConfig( enabled_capabilities={}, # Populated later auto_eject_timeout=0, challenge_response_timeout=0, device_flags=0, ), serial=None, version=version, form_factor=FORM_FACTOR.USB_A_KEYCHAIN, supported_capabilities=supported_apps, is_locked=False, )
def _read_info_ctap(conn, key_type, interfaces): try: mgmt = ManagementSession(conn) return mgmt.read_device_info() except Exception: # SKY 1 or NEO version = (3, 0, 0) # Guess, no way to know enabled_apps = {TRANSPORT.USB: APPLICATION.U2F} if USB_INTERFACE.CCID in interfaces: enabled_apps[TRANSPORT.USB] |= ( APPLICATION.OPGP | APPLICATION.PIV | APPLICATION.OATH ) if USB_INTERFACE.OTP in interfaces: enabled_apps[TRANSPORT.USB] |= APPLICATION.OTP supported_apps = {TRANSPORT.USB: APPLICATION.U2F} if key_type == YUBIKEY.NEO: supported_apps[TRANSPORT.USB] |= BASE_NEO_APPS supported_apps[TRANSPORT.NFC] = supported_apps[TRANSPORT.USB] enabled_apps[TRANSPORT.NFC] = supported_apps[TRANSPORT.NFC] return DeviceInfo( config=DeviceConfig( enabled_applications=enabled_apps, auto_eject_timeout=0, challenge_response_timeout=0, device_flags=0, ), serial=None, version=version, form_factor=FORM_FACTOR.USB_A_KEYCHAIN, supported_applications=supported_apps, is_locked=False, )
def _read_info_otp(conn, key_type, interfaces): otp = None serial = None try: mgmt = ManagementSession(conn) except ApplicationNotAvailableError: otp = YubiOtpSession(conn) # Retry during potential reclaim timeout period (~3s). for _ in range(8): try: if otp is None: try: return mgmt.read_device_info() # Rejected while reclaim except NotSupportedError: otp = YubiOtpSession(conn) serial = otp.get_serial( ) # Rejected if reclaim (or not API_SERIAL_VISIBLE) break except CommandRejectedError: sleep(0.5) # Potential reclaim else: otp = YubiOtpSession(conn) # Synthesize info logger.debug("Unable to get info via Management application, use fallback") version = otp.version if key_type == YUBIKEY.NEO: usb_supported = BASE_NEO_APPS if USB_INTERFACE.FIDO in interfaces or version >= (3, 3, 0): usb_supported |= CAPABILITY.U2F capabilities = { TRANSPORT.USB: usb_supported, TRANSPORT.NFC: usb_supported, } elif key_type == YUBIKEY.YKP: capabilities = { TRANSPORT.USB: CAPABILITY.OTP | TRANSPORT.U2F, } else: capabilities = { TRANSPORT.USB: CAPABILITY.OTP, } return DeviceInfo( config=DeviceConfig( enabled_capabilities={}, # Populated later auto_eject_timeout=0, challenge_response_timeout=0, device_flags=0, ), serial=serial, version=version, form_factor=FORM_FACTOR.UNKNOWN, supported_capabilities=capabilities.copy(), is_locked=False, )
def info(form_factor): return DeviceInfo( config=None, serial=None, version=(5, 3, 0), form_factor=form_factor, supported_capabilities={TRANSPORT.USB: 0xFF}, is_locked=False, is_fips=False, )
def mgmt_info(conn): try: raw_info = ManagementSession(conn).backend.read_config() info = DeviceInfo.parse(raw_info, Version(0, 0, 0)) return [ f"\t{info}", f"\tRawInfo: {raw_info.hex()}", ] except Exception as e: return [f"\tFailed to read device info: {e}"]
def info(form_factor): return DeviceInfo( config=cast(DeviceConfig, None), serial=None, version=Version(5, 3, 0), form_factor=form_factor, supported_capabilities={TRANSPORT.USB: 0xFF}, # type: ignore is_locked=False, is_fips=False, )
def _read_info_otp(conn, key_type, interfaces): try: mgmt = ManagementSession(conn) return mgmt.read_device_info() except (ApplicationNotAvailableError, NotSupportedError): logger.debug("Unable to get info via Management application, use fallback") # Synthesize info version, serial = _otp_read_data(conn) if key_type == YUBIKEY.NEO: usb_supported = BASE_NEO_APPS if USB_INTERFACE.FIDO in interfaces or version >= (3, 3, 0): usb_supported |= APPLICATION.U2F applications = { TRANSPORT.USB: usb_supported, TRANSPORT.NFC: usb_supported, } elif key_type == YUBIKEY.YKP: applications = { TRANSPORT.USB: APPLICATION.OTP | TRANSPORT.U2F, } else: applications = { TRANSPORT.USB: APPLICATION.OTP, } return DeviceInfo( config=DeviceConfig( enabled_applications=applications.copy(), auto_eject_timeout=0, challenge_response_timeout=0, device_flags=0, ), serial=serial, version=version, form_factor=FORM_FACTOR.UNKNOWN, supported_applications=applications.copy(), is_locked=False, )
def get_name(info: DeviceInfo, key_type: Optional[YUBIKEY]) -> str: """Determine the product name of a YubiKey""" usb_supported = info.supported_capabilities[TRANSPORT.USB] if not key_type: if info.serial is None and _fido_only(usb_supported): key_type = YUBIKEY.SKY elif info.version[0] == 3: key_type = YUBIKEY.NEO else: key_type = YUBIKEY.YK4 device_name = key_type.value if key_type == YUBIKEY.SKY: if CAPABILITY.FIDO2 not in usb_supported: device_name = "FIDO U2F Security Key" # SKY 1 if info.has_transport(TRANSPORT.NFC): device_name = "Security Key NFC" elif key_type == YUBIKEY.YK4: if info.version[0] == 0: return "Yubikey (%d.%d.%d)" % info.version if _is_preview(info.version): device_name = "YubiKey Preview" elif is_fips_version(info.version): # YK4 FIPS device_name = "YubiKey FIPS" elif usb_supported == CAPABILITY.OTP | CAPABILITY.U2F: device_name = "YubiKey Edge" elif info.version >= (5, 1, 0): is_nano = info.form_factor in ( FORM_FACTOR.USB_A_NANO, FORM_FACTOR.USB_C_NANO, ) is_bio = info.form_factor in (FORM_FACTOR.USB_A_BIO, FORM_FACTOR.USB_C_BIO) is_c = info.form_factor in ( # Does NOT include Ci FORM_FACTOR.USB_C_KEYCHAIN, FORM_FACTOR.USB_C_NANO, FORM_FACTOR.USB_C_BIO, ) name_parts = ["YubiKey"] if not is_bio: name_parts.append("5") if is_c: name_parts.append("C") elif info.form_factor == FORM_FACTOR.USB_C_LIGHTNING: name_parts.append("Ci") if is_nano: name_parts.append("Nano") if info.has_transport(TRANSPORT.NFC): name_parts.append("NFC") elif info.form_factor == FORM_FACTOR.USB_A_KEYCHAIN: name_parts.append("A") # Only for non-NFC A Keychain. if is_bio: name_parts.append("Bio") if _fido_only(usb_supported): name_parts.append("- FIDO Edition") if info.is_fips: name_parts.append("FIPS") device_name = " ".join(name_parts).replace("5 C", "5C").replace( "5 A", "5A") return device_name
def _read_info_ccid(conn, key_type, interfaces): try: mgmt = ManagementSession(conn) version = mgmt.version try: return mgmt.read_device_info() except NotSupportedError: # Workaround to "de-select" the Management Applet needed for NEO conn.send_and_receive(b"\xa4\x04\x00\x08") except ApplicationNotAvailableError: logger.debug("Unable to select Management application, use fallback.") version = None # Synthesize data capabilities = CAPABILITY(0) # Try to read serial (and version if needed) from OTP application try: otp_version, serial = _otp_read_data(conn) capabilities |= CAPABILITY.OTP if version is None: version = otp_version except ApplicationNotAvailableError: logger.debug("Unable to select OTP application") serial = None if version is None: version = (3, 0, 0) # Guess, no way to know # Scan for remaining capabilities protocol = SmartCardProtocol(conn) for aid, code in SCAN_APPLETS.items(): try: logger.debug("Check for %s", code) protocol.select(aid) capabilities |= code logger.debug("Found applet: aid: %s, capability: %s", aid, code) except ApplicationNotAvailableError: logger.debug("Missing applet: aid: %s, capability: %s", aid, code) except Exception as e: logger.error( "Error selecting aid: %s, capability: %s", aid, code, exc_info=e, ) # Assume U2F on devices >= 3.3.0 if USB_INTERFACE.FIDO in interfaces or version >= (3, 3, 0): capabilities |= CAPABILITY.U2F return DeviceInfo( config=DeviceConfig( enabled_capabilities={}, # Populated later auto_eject_timeout=0, challenge_response_timeout=0, device_flags=0, ), serial=serial, version=version, form_factor=FORM_FACTOR.UNKNOWN, supported_capabilities={ TRANSPORT.USB: capabilities, TRANSPORT.NFC: capabilities, }, is_locked=False, )