# Copyright (c) 2016 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import logging import click from yubikit.core import TRANSPORT from yubikit.core.fido import FidoConnection from yubikit.core.otp import OtpConnection from yubikit.core.smartcard import SmartCardConnection from yubikit.management import CAPABILITY, USB_INTERFACE from yubikit.oath import OathSession from yubikit.support import get_name from yubikit.yubiotp import YubiOtpSession from ..fido import is_in_fips_mode as ctap_in_fips_mode from ..oath import is_in_fips_mode as oath_in_fips_mode from ..otp import is_in_fips_mode as otp_in_fips_mode from .util import CliFail, click_command, is_yk4_fips, pretty_print logger = logging.getLogger(__name__) def print_app_status_table(supported_apps, enabled_apps): usb_supported = supported_apps.get(TRANSPORT.USB, 0) usb_enabled = enabled_apps.get(TRANSPORT.USB, 0) nfc_supported = supported_apps.get(TRANSPORT.NFC, 0) nfc_enabled = enabled_apps.get(TRANSPORT.NFC, 0) rows = [] for app in CAPABILITY: if app & usb_supported: if app & usb_enabled: usb_status = "Enabled" else: usb_status = "Disabled" else: usb_status = "Not available" if nfc_supported: if app & nfc_supported: if app & nfc_enabled: nfc_status = "Enabled" else: nfc_status = "Disabled" else: nfc_status = "Not available" rows.append([app.display_name, usb_status, nfc_status]) else: rows.append([app.display_name, usb_status]) column_l: list[int] = [] for row in rows: for idx, c in enumerate(row): if len(column_l) > idx: if len(c) > column_l[idx]: column_l[idx] = len(c) else: column_l.append(len(c)) f_apps = "Applications".ljust(column_l[0]) if nfc_supported: f_USB = "USB".ljust(column_l[1]) f_NFC = "NFC".ljust(column_l[2]) click.echo(f"{f_apps}\t{f_USB}\t{f_NFC}") else: click.echo(f"{f_apps}") f_table = "" for row in rows: for idx, c in enumerate(row): f_table += f"{c.ljust(column_l[idx])}\t" f_table = f_table.strip() + "\n" click.echo(f_table, nl=False) def get_overall_fips_status(device, info): statuses = {} usb_enabled = info.config.enabled_capabilities[TRANSPORT.USB] statuses["OTP"] = False if usb_enabled & CAPABILITY.OTP: with device.open_connection(OtpConnection) as conn: otp_app = YubiOtpSession(conn) statuses["OTP"] = otp_in_fips_mode(otp_app) statuses["OATH"] = False if usb_enabled & CAPABILITY.OATH: with device.open_connection(SmartCardConnection) as conn: oath_app = OathSession(conn) statuses["OATH"] = oath_in_fips_mode(oath_app) statuses["FIDO U2F"] = False if usb_enabled & CAPABILITY.U2F: with device.open_connection(FidoConnection) as conn: statuses["FIDO U2F"] = ctap_in_fips_mode(conn) return statuses def _check_fips_status(device, info): fips_status = get_overall_fips_status(device, info) click.echo(f"FIPS Approved Mode: {'Yes' if all(fips_status.values()) else 'No'}") status_keys = list(fips_status.keys()) status_keys.sort() for status_key in status_keys: click.echo(f" {status_key}: {'Yes' if fips_status[status_key] else 'No'}") @click.option( "-c", "--check-fips", help="check if YubiKey is in FIPS Approved mode (4 Series only)", is_flag=True, ) @click_command(connections=[SmartCardConnection, OtpConnection, FidoConnection]) @click.pass_context 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.usb_interfaces key_type = pid.yubikey_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: click.echo(f"Firmware version: {info.version_name}") 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 or str(t) 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: if info.config.nfc_restricted: f_nfc = "restricted" elif info.config.enabled_capabilities.get(TRANSPORT.NFC): f_nfc = "enabled" else: f_nfc = "disabled" click.echo(f"NFC transport is {f_nfc}") if info.pin_complexity: click.echo("PIN complexity is enforced") 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 info.fips_capable: click.echo() click.echo("FIPS approved applications") data = { c.display_name: c in info.fips_approved for c in CAPABILITY if c in info.fips_capable } click.echo("\n".join(pretty_print(data))) if check_fips: click.echo() if is_yk4_fips(info): device = ctx.obj["device"] _check_fips_status(device, info) else: raise CliFail( "Unable to check FIPS Approved mode - Not a YubiKey FIPS (4 Series)" )