Skip to content

Commit

Permalink
CLI: Add ykman config nfc --restrict
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Jun 18, 2024
1 parent 5cce3a4 commit e23a8b2
Showing 1 changed file with 82 additions and 40 deletions.
122 changes: 82 additions & 40 deletions ykman/_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ def set_lock_code(ctx, lock_code, new_lock_code, clear, generate, force):
new_lock_code = click_prompt(
"Enter your new lock code", hide_input=True, confirmation_prompt=True
)
set_code = _parse_lock_code(ctx, new_lock_code)
set_code = _parse_lock_code(new_lock_code)

# Get the current lock code to use
if info.is_locked:
if not lock_code:
lock_code = click_prompt("Enter your current lock code", hide_input=True)
use_code = _parse_lock_code(ctx, lock_code)
use_code = _parse_lock_code(lock_code)
else:
if lock_code:
raise CliFail(
Expand All @@ -224,6 +224,21 @@ def set_lock_code(ctx, lock_code, new_lock_code, clear, generate, force):
raise CliFail("Failed to set the lock code.")


def _get_lock_code(is_locked, lock_code, force):
if force and is_locked and not lock_code:
raise CliFail("Configuration is locked - supply the --lock-code option.")
if lock_code and not is_locked:
raise CliFail("Configuration is not locked - remove the --lock-code option.")

if is_locked and not lock_code:
lock_code = prompt_lock_code()

if lock_code:
lock_code = _parse_lock_code(lock_code)

return lock_code


def _configure_applications(
ctx,
config,
Expand All @@ -234,8 +249,6 @@ def _configure_applications(
lock_code,
force,
):
_require_config(ctx)

info = ctx.obj["info"]
supported = info.supported_capabilities.get(transport)
enabled = info.config.enabled_capabilities.get(transport)
Expand All @@ -244,7 +257,7 @@ def _configure_applications(
raise CliFail(f"{transport} not supported on this YubiKey.")

if enable & disable:
ctx.fail("Invalid options.")
raise CliFail("Invalid options.")

unsupported = ~supported & (enable | disable)
if unsupported:
Expand All @@ -258,7 +271,7 @@ def _configure_applications(

if transport == TRANSPORT.USB:
if sum(CAPABILITY) & new_enabled == 0:
ctx.fail(f"Can not disable all applications over {transport}.")
raise CliFail(f"Can not disable all applications over {transport}.")

reboot = enabled.usb_interfaces != new_enabled.usb_interfaces
else:
Expand All @@ -267,26 +280,18 @@ def _configure_applications(
if reboot:
changes.append("The YubiKey will reboot")

is_locked = info.is_locked

if force and is_locked and not lock_code:
raise CliFail("Configuration is locked - supply the --lock-code option.")
if lock_code and not is_locked:
if lock_code and not info.is_locked:
raise CliFail("Configuration is not locked - remove the --lock-code option.")

click.echo(f"{transport} configuration changes:")
for change in changes:
click.echo(f" {change}")
force or click.confirm("Proceed?", 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)

config.enabled_capabilities = {transport: new_enabled}

lock_code = _get_lock_code(info.is_locked, lock_code, force)

app = ctx.obj["session"]
try:
app.write_device_config(
Expand Down Expand Up @@ -369,25 +374,27 @@ def usb(
"""
_require_config(ctx)

if not (
list_enabled
or enable_all
or enable
or disable
or touch_eject
or no_touch_eject
or autoeject_timeout
or chalresp_timeout
if not any(
[
list_enabled,
enable_all,
enable,
disable,
touch_eject,
no_touch_eject,
autoeject_timeout,
chalresp_timeout,
]
):
ctx.fail("No configuration options chosen.")
raise CliFail("No configuration options chosen.")

if touch_eject and no_touch_eject:
ctx.fail("Invalid options.")
raise CliFail("Invalid options.")

if list_enabled:
_list_apps(ctx, TRANSPORT.USB)

config = DeviceConfig({}, autoeject_timeout, chalresp_timeout, None)
config = DeviceConfig({}, autoeject_timeout, chalresp_timeout)
changes = []
info = ctx.obj["info"]

Expand Down Expand Up @@ -453,20 +460,53 @@ def usb(
metavar="HEX",
help="current application configuration lock code",
)
def nfc(ctx, enable, disable, enable_all, disable_all, list_enabled, lock_code, force):
@click.option(
"-R",
"--restrict",
is_flag=True,
help="Disable NFC for transport, re-enabled by USB power",
)
def nfc(
ctx,
enable,
disable,
enable_all,
disable_all,
list_enabled,
lock_code,
restrict,
force,
):
"""
Enable or disable applications over NFC.
"""
info = ctx.obj["info"]
if TRANSPORT.NFC not in info.supported_capabilities:
raise CliFail("This YubiKey does not support NFC.")

_require_config(ctx)

if not (list_enabled or enable_all or enable or disable_all or disable):
ctx.fail("No configuration options chosen.")
if not any([list_enabled, enable_all, enable, disable_all, disable, restrict]):
raise CliFail("No configuration options chosen.")

if list_enabled:
_list_apps(ctx, TRANSPORT.NFC)

config = DeviceConfig({}, None, None, None)
info = ctx.obj["info"]

if restrict:
if info.version < (5, 7):
raise CliFail("NFC restriction requires YubiKey 5.7 or later.")

config.nfc_restricted = True
lock_code = _get_lock_code(info.is_locked, lock_code, force)
ctx.obj["session"].write_device_config(config, False, lock_code)
click.echo(
"YubiKey NFC disabled. It will be re-enabled automatically the next time "
"it is connected to USB power."
)
ctx.exit()

changes = []

nfc_supported = info.supported_capabilities.get(TRANSPORT.NFC)
Expand Down Expand Up @@ -511,17 +551,19 @@ def _list_apps(ctx, transport):

def _ensure_not_invalid_options(ctx, enable, disable):
if enable & disable:
ctx.fail("Invalid options.")
raise CliFail("Invalid options.")


def _parse_lock_code(ctx, lock_code):
def _parse_lock_code(lock_code):
try:
lock_code = bytes.fromhex(lock_code)
if lock_code and len(lock_code) != 16:
ctx.fail("Lock code must be exactly 16 bytes (32 hexadecimal digits) long.")
raise CliFail(
"Lock code must be exactly 16 bytes (32 hexadecimal digits) long."
)
return lock_code
except Exception:
ctx.fail("Lock code has the wrong format.")
raise CliFail("Lock code has the wrong format.")


# MODE
Expand All @@ -539,7 +581,7 @@ def _parse_mode_string(ctx, param, mode):
mode_int = int(mode)
return Mode.from_code(mode_int)
except IndexError:
ctx.fail(f"Invalid mode: {mode_int}")
raise CliFail(f"Invalid mode: {mode_int}")
except ValueError:
pass # Not a numeric mode, parse string

Expand All @@ -560,7 +602,7 @@ def _parse_mode_string(ctx, param, mode):
if t:
interfaces |= _parse_interface_string(t)
except ValueError:
ctx.fail(f"Invalid mode string: {mode}")
raise CliFail(f"Invalid mode string: {mode}")

return Mode(interfaces)

Expand Down Expand Up @@ -633,7 +675,7 @@ def mode(ctx, mode, touch_eject, autoeject_timeout, chalresp_timeout, force):

if mode.interfaces != USB_INTERFACE.CCID:
if touch_eject:
ctx.fail("--touch-eject can only be used when setting CCID-only mode")
raise CliFail("--touch-eject can only be used when setting CCID-only mode")

if not force:
if mode == my_mode:
Expand Down

0 comments on commit e23a8b2

Please sign in to comment.