Exemplo n.º 1
0
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)
Exemplo n.º 2
0
def _mode_from_usb_enabled(usb_enabled):
    interfaces = USB_INTERFACE(0)
    if CAPABILITY.OTP & usb_enabled:
        interfaces |= USB_INTERFACE.OTP
    if (CAPABILITY.U2F | CAPABILITY.FIDO2) & usb_enabled:
        interfaces |= USB_INTERFACE.FIDO
    if (CAPABILITY.OPENPGP | CAPABILITY.PIV | CAPABILITY.OATH) & usb_enabled:
        interfaces |= USB_INTERFACE.CCID
    return Mode(interfaces)
Exemplo n.º 3
0
def info(ctx, check_fips):
    """
    Show general information.

    Displays information about the attached YubiKey such as serial number,
    firmware version, capabilities, etc.
    """
    info = ctx.obj["info"]
    pid = ctx.obj["pid"]
    if pid is None:
        interfaces = None
        key_type = None
    else:
        interfaces = pid.get_interfaces()
        key_type = pid.get_type()
    device_name = get_name(info, key_type)

    click.echo(f"Device type: {device_name}")
    if info.serial:
        click.echo(f"Serial number: {info.serial}")
    if info.version:
        f_version = ".".join(str(x) for x in info.version)
        click.echo(f"Firmware version: {f_version}")
    else:
        click.echo(
            "Firmware version: Uncertain, re-run with only one YubiKey connected"
        )

    if info.form_factor:
        click.echo(f"Form factor: {info.form_factor!s}")
    if interfaces:
        f_interfaces = ", ".join(t.name for t in USB_INTERFACE
                                 if t in USB_INTERFACE(interfaces))
        click.echo(f"Enabled USB interfaces: {f_interfaces}")
    if TRANSPORT.NFC in info.supported_capabilities:
        f_nfc = ("enabled" if info.config.enabled_capabilities.get(
            TRANSPORT.NFC) else "disabled")
        click.echo(f"NFC transport is {f_nfc}.")
    if info.is_locked:
        click.echo("Configured capabilities are protected by a lock code.")
    click.echo()

    print_app_status_table(info.supported_capabilities,
                           info.config.enabled_capabilities)

    if check_fips:
        if is_fips_version(info.version):
            ctx.obj["conn"].close()
            _check_fips_status(pid, info)
        else:
            cli_fail(
                "Unable to check FIPS Approved mode - Not a YubiKey 4 FIPS")
Exemplo n.º 4
0
    def refresh(self):
        devices, state = scan_devices()
        n_devs = sum(devices.values())
        if n_devs != 1:
            return failure('multiple_devices')

        if state != self._state:
            self._state = state
            try:
                connection, device, info = connect_to_device()
                connection.close()
            except:
                self._state = None
                self._dev_info = None
                return failure('no_device')

            interfaces = USB_INTERFACE(0)
            usb_supported = info.supported_capabilities.get(TRANSPORT.USB)
            if CAPABILITY.OTP & usb_supported:
                interfaces |= USB_INTERFACE.OTP
            if (CAPABILITY.U2F | CAPABILITY.FIDO2) & usb_supported:
                interfaces |= USB_INTERFACE.FIDO
            if (CAPABILITY.OPENPGP | CAPABILITY.PIV | CAPABILITY.OATH) & usb_supported:
                interfaces |= USB_INTERFACE.CCID

            self._dev_info = {
                'name': get_name(info, device.pid.get_type()).replace("YubiKey BIO", "YubiKey Bio"),
                'version': '.'.join(str(x) for x in info.version) if info.version else "",
                'serial': info.serial or '',
                'usb_enabled': [
                    a.name for a in CAPABILITY
                    if a in info.config.enabled_capabilities.get(TRANSPORT.USB)],
                'usb_supported': [
                    a.name for a in CAPABILITY
                    if a in info.supported_capabilities.get(TRANSPORT.USB)],
                'usb_interfaces_supported': [
                    t.name for t in USB_INTERFACE
                    if t in interfaces],
                'nfc_enabled': [
                    a.name for a in CAPABILITY
                    if a in info.config.enabled_capabilities.get(TRANSPORT.NFC, [])],
                'nfc_supported': [
                    a.name for a in CAPABILITY
                    if a in info.supported_capabilities.get(TRANSPORT.NFC, [])],
                'usb_interfaces_enabled': [i.name for i in USB_INTERFACE if i & device.pid.get_interfaces()],
                'can_write_config': info.version and info.version >= (5,0,0),
                'configuration_locked': info.is_locked,
                'form_factor': info.form_factor
            }

        return success({'dev': self._dev_info})
Exemplo n.º 5
0
def _pid_from_name(name):
    if YK_READER_NAME not in name.lower():
        return None

    interfaces = USB_INTERFACE(0)
    for iface in USB_INTERFACE:
        if iface.name in name:
            interfaces |= iface

    if "U2F" in name:
        interfaces |= USB_INTERFACE.FIDO

    key_type = YUBIKEY.NEO if "NEO" in name else YUBIKEY.YK4
    return key_type.get_pid(interfaces)
Exemplo n.º 6
0
 def get_interfaces(self) -> USB_INTERFACE:
     return USB_INTERFACE(
         sum(USB_INTERFACE[x] for x in self.name.split("_")[1:]))
Exemplo n.º 7
0
 def get_pid(self, interfaces: USB_INTERFACE) -> "PID":
     suffix = "_".join(t.name for t in USB_INTERFACE
                       if t in USB_INTERFACE(interfaces))
     return PID[self.name + "_" + suffix]
Exemplo n.º 8
0
def read_info(pid: Optional[PID], conn: Connection) -> DeviceInfo:
    """Read out a DeviceInfo object from a YubiKey, or attempt to synthesize one."""
    if pid:
        key_type: Optional[YUBIKEY] = pid.get_type()
        interfaces = pid.get_interfaces()
    else:  # No PID for NFC connections
        key_type = None
        interfaces = USB_INTERFACE(0)

    if isinstance(conn, SmartCardConnection):
        info = _read_info_ccid(conn, key_type, interfaces)
    elif isinstance(conn, OtpConnection):
        info = _read_info_otp(conn, key_type, interfaces)
    elif isinstance(conn, FidoConnection):
        info = _read_info_ctap(conn, key_type, interfaces)
    else:
        raise TypeError("Invalid connection type")

    logger.debug("Read info: %s", info)

    # Set usb_enabled if missing (pre YubiKey 5)
    if (info.has_transport(TRANSPORT.USB)
            and TRANSPORT.USB not in info.config.enabled_capabilities):
        usb_enabled = info.supported_capabilities[TRANSPORT.USB]
        if usb_enabled == (CAPABILITY.OTP | CAPABILITY.U2F
                           | USB_INTERFACE.CCID):
            # YubiKey Edge, hide unusable CCID interface
            usb_enabled = CAPABILITY.OTP | CAPABILITY.U2F
            info.supported_capabilities = {TRANSPORT.USB: usb_enabled}

        if USB_INTERFACE.OTP not in interfaces:
            usb_enabled &= ~CAPABILITY.OTP
        if USB_INTERFACE.FIDO not in interfaces:
            usb_enabled &= ~(CAPABILITY.U2F | CAPABILITY.FIDO2)
        if USB_INTERFACE.CCID not in interfaces:
            usb_enabled &= ~(USB_INTERFACE.CCID
                             | CAPABILITY.OATH
                             | CAPABILITY.OPENPGP
                             | CAPABILITY.PIV)
        info.config.enabled_capabilities[TRANSPORT.USB] = usb_enabled

    # YK4-based FIPS version
    if is_fips_version(info.version):
        info.is_fips = True

    # Set nfc_enabled if missing (pre YubiKey 5)
    if (info.has_transport(TRANSPORT.NFC)
            and TRANSPORT.NFC not in info.config.enabled_capabilities):
        info.config.enabled_capabilities[
            TRANSPORT.NFC] = info.supported_capabilities[TRANSPORT.NFC]

    # Workaround for invalid configurations.
    if info.version >= (4, 0, 0):
        if info.form_factor in (
                FORM_FACTOR.USB_A_NANO,
                FORM_FACTOR.USB_C_NANO,
                FORM_FACTOR.USB_C_LIGHTNING,
        ) or (info.form_factor is FORM_FACTOR.USB_C_KEYCHAIN and info.version <
              (5, 2, 4)):
            # Known not to have NFC
            info.supported_capabilities.pop(TRANSPORT.NFC, None)
            info.config.enabled_capabilities.pop(TRANSPORT.NFC, None)

    return info
Exemplo n.º 9
0
def read_info(pid: Optional[PID], conn: Connection) -> DeviceInfo:
    """Read out a DeviceInfo object from a YubiKey, or attempt to synthesize one."""
    if pid:
        key_type: Optional[YUBIKEY] = pid.get_type()
        interfaces = pid.get_interfaces()
    else:  # No PID for NFC connections
        key_type = None
        interfaces = USB_INTERFACE(0)

    if isinstance(conn, SmartCardConnection):
        info = _read_info_ccid(conn, key_type, interfaces)
    elif isinstance(conn, OtpConnection):
        info = _read_info_otp(conn, key_type, interfaces)
    elif isinstance(conn, FidoConnection):
        info = _read_info_ctap(conn, key_type, interfaces)
    else:
        raise TypeError("Invalid connection type")

    logger.debug("Read info: %s", info)

    # Set usb_enabled if missing (pre YubiKey 5)
    if (info.has_transport(TRANSPORT.USB)
            and TRANSPORT.USB not in info.config.enabled_capabilities):
        usb_enabled = info.supported_capabilities[TRANSPORT.USB]
        if usb_enabled == (CAPABILITY.OTP | CAPABILITY.U2F
                           | USB_INTERFACE.CCID):
            # YubiKey Edge, hide unusable CCID interface
            usb_enabled = CAPABILITY.OTP | CAPABILITY.U2F
            info.supported_capabilities = {TRANSPORT.USB: usb_enabled}

        if USB_INTERFACE.OTP not in interfaces:
            usb_enabled &= ~CAPABILITY.OTP
        if USB_INTERFACE.FIDO not in interfaces:
            usb_enabled &= ~(CAPABILITY.U2F | CAPABILITY.FIDO2)
        if USB_INTERFACE.CCID not in interfaces:
            usb_enabled &= ~(USB_INTERFACE.CCID
                             | CAPABILITY.OATH
                             | CAPABILITY.OPENPGP
                             | CAPABILITY.PIV)
        info.config.enabled_capabilities[TRANSPORT.USB] = usb_enabled

    # Set nfc_enabled if missing (pre YubiKey 5)
    if (info.has_transport(TRANSPORT.NFC)
            and TRANSPORT.NFC not in info.config.enabled_capabilities):
        info.config.enabled_capabilities[
            TRANSPORT.NFC] = info.supported_capabilities[TRANSPORT.NFC]

    # Workaround for invalid configurations.
    # Assume all form factors except USB_A_KEYCHAIN and
    # USB_C_KEYCHAIN >= 5.2.4 does not support NFC.
    if not (info.version < (4, 0, 0)  # No relevant programming yet
            or (info.form_factor is FORM_FACTOR.USB_A_KEYCHAIN) or
            (info.form_factor is FORM_FACTOR.USB_C_KEYCHAIN and info.version >=
             (5, 2, 4))):
        info.supported_capabilities = {
            TRANSPORT.USB: info.supported_capabilities[TRANSPORT.USB]
        }
        info.config.enabled_capabilities = {
            TRANSPORT.USB: info.config.enabled_capabilities[TRANSPORT.USB]
        }

    return info
Exemplo n.º 10
0
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.")
Exemplo n.º 11
0
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.")