def _parse_mode_string(ctx, param, mode): try: mode_int = int(mode) return Mode.from_code(mode_int) except IndexError: ctx.fail(f"Invalid mode: {mode_int}") except ValueError: pass # Not a numeric mode, parse string try: if mode[0] in ["+", "-"]: info = ctx.obj["info"] usb_enabled = info.config.enabled_capabilities[TRANSPORT.USB] interfaces = USB_INTERFACE.for_capabilities(usb_enabled) for mod in re.findall(r"[+-][A-Z]+", mode.upper()): interface = _parse_interface_string(mod[1:]) if mod.startswith("+"): interfaces |= interface else: interfaces ^= interface else: interfaces = USB_INTERFACE(0) for t in re.split(r"[+]+", mode.upper()): if t: interfaces |= _parse_interface_string(t) except ValueError: ctx.fail(f"Invalid mode string: {mode}") return Mode(interfaces)
def mode(ctx, mode, touch_eject, autoeject_timeout, chalresp_timeout, force): """ Manage connection modes (USB Interfaces). This command is generaly used with YubiKeys prior to the 5 series. Use "ykman config usb" for more granular control on YubiKey 5 and later. 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 config mode OTP+FIDO \b Set the CCID only mode and use touch to eject the smart card: $ ykman config mode CCID --touch-eject """ info = ctx.obj["info"] mgmt = ctx.obj["controller"] usb_enabled = info.config.enabled_capabilities[TRANSPORT.USB] my_mode = Mode(USB_INTERFACE.for_capabilities(usb_enabled)) usb_supported = info.supported_capabilities[TRANSPORT.USB] interfaces_supported = USB_INTERFACE.for_capabilities(usb_supported) pid = ctx.obj["pid"] if pid: key_type = pid.get_type() else: key_type = None if autoeject_timeout: # autoeject implies touch eject touch_eject = True autoeject = autoeject_timeout if touch_eject else 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: cli_fail(f"Mode is already {mode}, nothing to do...", 0) elif key_type in (YUBIKEY.YKS, YUBIKEY.YKP): cli_fail("Mode switching is not supported on this YubiKey!\n" "Use --force to attempt to set it anyway.") elif mode.interfaces not in interfaces_supported: cli_fail(f"Mode {mode} is not supported on this YubiKey!\n" + "Use --force to attempt to set it anyway.") force or click.confirm( f"Set mode of YubiKey to {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.")
def usb( ctx, enable, disable, list_enabled, enable_all, touch_eject, no_touch_eject, autoeject_timeout, chalresp_timeout, lock_code, force, ): """ Enable or disable applications over USB. """ _require_config(ctx) def ensure_not_all_disabled(ctx, usb_enabled): for app in CAPABILITY: if app & usb_enabled: return ctx.fail("Can not disable all applications over USB.") if not (list_enabled or enable_all or enable or disable or touch_eject or no_touch_eject or autoeject_timeout or chalresp_timeout): ctx.fail("No configuration options chosen.") info = ctx.obj["info"] usb_supported = info.supported_capabilities[TRANSPORT.USB] usb_enabled = info.config.enabled_capabilities[TRANSPORT.USB] usb_interfaces = USB_INTERFACE.for_capabilities(usb_enabled) flags = info.config.device_flags if enable_all: enable = [c for c in CAPABILITY if c in usb_supported] _ensure_not_invalid_options(ctx, enable, disable) if touch_eject and no_touch_eject: ctx.fail("Invalid options.") if not usb_supported: cli_fail("USB not supported on this YubiKey.") if list_enabled: _list_apps(ctx, usb_enabled) if touch_eject: flags |= DEVICE_FLAG.EJECT if no_touch_eject: flags &= ~DEVICE_FLAG.EJECT for app in enable: if app & usb_supported: usb_enabled |= app else: cli_fail(f"{app.name} not supported over USB on this YubiKey.") for app in disable: if app & usb_supported: usb_enabled &= ~app else: cli_fail(f"{app.name} not supported over USB on this YubiKey.") ensure_not_all_disabled(ctx, usb_enabled) reboot = usb_interfaces != USB_INTERFACE.for_capabilities(usb_enabled) f_confirm = "" if enable: f_confirm += f"Enable {', '.join(str(app) for app in enable)}.\n" if disable: f_confirm += f"Disable {', '.join(str(app) for app in disable)}.\n" if touch_eject: f_confirm += "Set touch eject.\n" elif no_touch_eject: f_confirm += "Disable touch eject.\n" if autoeject_timeout: f_confirm += f"Set autoeject timeout to {autoeject_timeout}.\n" if chalresp_timeout: f_confirm += f"Set challenge-response timeout to {chalresp_timeout}.\n" if reboot: f_confirm += "This will cause the YubiKey to reboot.\n" f_confirm += "Configure USB?" is_locked = info.is_locked if force and is_locked and not lock_code: cli_fail( "Configuration is locked - please supply the --lock-code option.") if lock_code and not is_locked: cli_fail( "Configuration is not locked - please remove the --lock-code option." ) force or click.confirm(f_confirm, abort=True, err=True) if is_locked and not lock_code: lock_code = prompt_lock_code() if lock_code: lock_code = _parse_lock_code(ctx, lock_code) app = ctx.obj["controller"] try: app.write_device_config( DeviceConfig( {TRANSPORT.USB: usb_enabled}, autoeject_timeout, chalresp_timeout, flags, ), reboot, lock_code, ) except Exception as e: logger.error("Failed to write config", exc_info=e) cli_fail("Failed to configure USB applications.")