Skip to content

Commit

Permalink
nk3: Add get-config and set-config subcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
robin-nitrokey committed Oct 27, 2023
1 parent ae2ea31 commit ee5ff26
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 0 deletions.
23 changes: 23 additions & 0 deletions pynitrokey/cli/nk3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,29 @@ def status(ctx: Context) -> None:
local_print(f"Variant: {status.variant.name}")


@nk3.command()
@click.pass_obj
@click.argument("key")
def get_config(ctx: Context, key: str) -> None:
"""Query a config value."""
with ctx.connect_device() as device:
admin = AdminApp(device)
value = admin.get_config(key)
print(value)


@nk3.command()
@click.pass_obj
@click.argument("key")
@click.argument("value")
def set_config(ctx: Context, key: str, value: str) -> None:
"""Query a config value."""
with ctx.connect_device() as device:
admin = AdminApp(device)
admin.set_config(key, value)
print(f"Updated configuration {key}.")


@nk3.command()
@click.pass_obj
def version(ctx: Context) -> None:
Expand Down
45 changes: 45 additions & 0 deletions pynitrokey/nk3/admin_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from enum import Enum, IntFlag
from typing import Optional

from fido2 import cbor
from fido2.ctap import CtapError

from pynitrokey.nk3.device import Command, Nitrokey3Device
Expand All @@ -15,6 +16,8 @@
class AdminCommand(Enum):
STATUS = 0x80
TEST_SE050 = 0x81
GET_CONFIG = 0x82
SET_CONFIG = 0x83


@enum.unique
Expand Down Expand Up @@ -54,6 +57,35 @@ class Status:
variant: Optional[Variant] = None


@enum.unique
class ConfigStatus(Enum):
SUCCESS = 0
READ_FAILED = 1
WRITE_FAILED = 2
DESERIALIZATION_FAILED = 3
SERIALIZATION_FAILED = 4
INVALID_KEY = 5
INVALID_VALUE = 6
DATA_TOO_LONG = 7

@classmethod
def from_int(cls, i: int) -> Optional["ConfigStatus"]:
for status in ConfigStatus:
if status.value == i:
return status
return None

@classmethod
def check(cls, i: int, msg: str) -> None:
status = ConfigStatus.from_int(i)
if status != ConfigStatus.SUCCESS:
if status:
error = str(status)
else:
error = f"unknown error {i:x}"
raise Exception(f"{msg}: {error}")


class AdminApp:
def __init__(self, device: Nitrokey3Device) -> None:
self.device = device
Expand Down Expand Up @@ -103,3 +135,16 @@ def version(self) -> Version:

def se050_tests(self) -> Optional[bytes]:
return self._call(AdminCommand.TEST_SE050)

def get_config(self, key: str) -> str:
reply = self._call(AdminCommand.GET_CONFIG, data=key.encode())
if not reply or len(reply) < 1:
raise ValueError("The device returned an empty response")
ConfigStatus.check(reply[0], "Failed to get config value")
return reply[1:].decode()

def set_config(self, key: str, value: str) -> None:
request = cbor.encode({"key": key, "value": value})
reply = self._call(AdminCommand.SET_CONFIG, data=request, response_len=1)
assert reply
ConfigStatus.check(reply[0], "Failed to set config value")

0 comments on commit ee5ff26

Please sign in to comment.