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_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_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 config(ctx): """ Enable/Disable applications. The applications may be enabled and disabled independently over different transports (USB and NFC). The configuration may also be protected by a lock code. Examples: \b Disable PIV over NFC: $ ykman config nfc --disable PIV \b Enable all applications over USB: $ ykman config usb --enable-all \b Generate and set a random application lock code: $ ykman config set-lock-code --generate """ info = ctx.obj["info"] if info.version < (5, 0, 0): ctx.fail("Configuring applications is not supported on this YubiKey. " "Use the `mode` command to configure USB interfaces.") ctx.obj["controller"] = ManagementSession(ctx.obj["conn"])
def set_mode(self, interfaces): interfaces_enabled = 0x00 for usb_interface in interfaces: interfaces_enabled |= USB_INTERFACE[usb_interface] with self._open_device() as conn: try: session = ManagementSession(conn) session.set_mode(Mode(interfaces_enabled)) except ValueError as e: if str(e) == 'Configuration locked!': return failure('interface_config_locked') raise return success()
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 _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 write_config(self, usb_applications, nfc_applications, lock_code): usb_enabled = 0x00 nfc_enabled = 0x00 for app in usb_applications: usb_enabled |= CAPABILITY [app] for app in nfc_applications: nfc_enabled |= CAPABILITY [app] with self._open_device() as conn: if lock_code: lock_code = a2b_hex(lock_code) if len(lock_code) != 16: return failure('lock_code_not_16_bytes') try: session = ManagementSession(conn) session.write_device_config( DeviceConfig( {TRANSPORT.USB: usb_enabled, TRANSPORT.NFC: nfc_enabled}, None, None, None, ), True, lock_code) self._state = None except ApduError as e: if (e.sw == SW.VERIFY_FAIL_NO_RETRY): return failure('wrong_lock_code') raise except ValueError as e: if str(e) == 'Configuration locked!': return failure('interface_config_locked') raise return success()
def call(): otp = session if need_reboot: protocol = session.backend.protocol if transport == TRANSPORT.NFC: protocol.connection.connection.disconnect() conn = protocol.connection conn.connection.connect() else: ManagementSession(protocol.connection).write_device_config(reboot=True) await_reboot() conn = connect_to_device(info.serial, [SmartCardConnection])[0] otp = YubiOtpSession(conn) session.backend = otp.backend return otp.get_config_state()
def mgmt_info(pid, conn): lines = [] try: raw_info = ManagementSession(conn).backend.read_config() lines.append(f"\tRawInfo: {raw_info.hex()}") except Exception as e: lines.append(f"\tFailed to read device info via Management: {e!r}") try: info = read_info(pid, conn) lines.append(f"\t{info}") name = get_name(info, pid.get_type()) lines.append(f"\tDevice name: {name}") except Exception as e: lines.append(f"\tFailed to read device info: {e!r}") return lines
def config(ctx): """ Enable or disable applications. The applications may be enabled and disabled independently over different transports (USB and NFC). The configuration may also be protected by a lock code. Examples: \b Disable PIV over NFC: $ ykman config nfc --disable PIV \b Enable all applications over USB: $ ykman config usb --enable-all \b Generate and set a random application lock code: $ ykman config set-lock-code --generate """ ctx.obj["controller"] = ManagementSession(ctx.obj["conn"])
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, )
def mode(ctx, mode, touch_eject, autoeject_timeout, chalresp_timeout, force): """ Manage connection modes (USB Interfaces). Get the current connection mode of the YubiKey, or set it to MODE. MODE can be a string, such as "OTP+FIDO+CCID", or a shortened form: "o+f+c". It can also be a mode number. Examples: \b Set the OTP and FIDO mode: $ ykman mode OTP+FIDO \b Set the CCID only mode and use touch to eject the smart card: $ ykman mode CCID --touch-eject """ info = ctx.obj["info"] mgmt = ManagementSession(ctx.obj["conn"]) usb_enabled = info.config.enabled_applications[TRANSPORT.USB] my_mode = _mode_from_usb_enabled(usb_enabled) usb_supported = info.supported_applications[TRANSPORT.USB] interfaces_supported = _mode_from_usb_enabled(usb_supported).interfaces pid = ctx.obj["pid"] if pid: key_type = pid.get_type() else: key_type = None if autoeject_timeout: touch_eject = True autoeject = autoeject_timeout if touch_eject else 0 if mode is not None: if mode.interfaces != USB_INTERFACE.CCID: if touch_eject: ctx.fail("--touch-eject can only be used when setting" " CCID-only mode") if not force: if mode == my_mode: click.echo("Mode is already {}, nothing to do...".format(mode)) ctx.exit() elif key_type in (YUBIKEY.YKS, YUBIKEY.YKP): click.echo("Mode switching is not supported on this YubiKey!") ctx.fail("Use --force to attempt to set it anyway.") elif mode.interfaces not in interfaces_supported: click.echo( "Mode {} is not supported on this YubiKey!".format(mode)) ctx.fail("Use --force to attempt to set it anyway.") force or click.confirm("Set mode of YubiKey to {}?".format(mode), abort=True, err=True) try: mgmt.set_mode(mode, chalresp_timeout, autoeject) click.echo("Mode set! You must remove and re-insert your YubiKey " "for this change to take effect.") except Exception as e: logger.debug("Failed to switch mode", exc_info=e) click.echo("Failed to switch mode on the YubiKey. Make sure your " "YubiKey does not have an access code set.") else: click.echo("Current connection mode is: {}".format(my_mode)) mode = _mode_from_usb_enabled( info.supported_applications[TRANSPORT.USB]) supported = ", ".join(t.name for t in USB_INTERFACE if t in mode.interfaces) click.echo("Supported USB interfaces are: {}".format(supported))