Skip to content

Commit

Permalink
Merge pull request #565 from Nitrokey/piv-rebase-update
Browse files Browse the repository at this point in the history
PIV rebase update
  • Loading branch information
sosthene-nitrokey authored Sep 17, 2024
2 parents 4367040 + b95e0b9 commit f206e2e
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 56 deletions.
29 changes: 0 additions & 29 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,32 +77,3 @@ jobs:
run: |
. venv/bin/activate
make check-typing
build-onefile:
name: Build onefile
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create virtual environment
run: |
python -m venv venv
.\venv\Scripts\Activate.ps1
.\venv\Scripts\pip install pip
.\venv\Scripts\pip install flit
.\venv\Scripts\flit install --symlink
- name: Create Windows version info file
run: |
.\venv\Scripts\Activate.ps1
create-version-file `
--outfile .\ci-scripts\windows\pyinstaller\file_version_info.txt `
--version "$(Get-Content .\pynitrokey\VERSION)" `
.\ci-scripts\windows\pyinstaller\file_version_info_metadata.yaml
- name: Build onefile
run: |
.\venv\Scripts\Activate.ps1
pyinstaller ci-scripts/windows/pyinstaller/pynitrokey-onefile.spec
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist\nitropy.exe
93 changes: 66 additions & 27 deletions pynitrokey/cli/nk3/piv.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def piv() -> None:
pass


@piv.command()
@piv.command(help="Authenticate with the admin key.")
@click.argument(
"admin-key",
type=click.STRING,
Expand All @@ -45,7 +45,7 @@ def admin_auth(admin_key: str) -> None:
local_print("Authenticated successfully")


@piv.command()
@piv.command(help="Initialize the PIV application.")
@click.argument(
"admin-key",
type=click.STRING,
Expand All @@ -67,7 +67,7 @@ def init(admin_key: str) -> None:
local_print(f"GUID: {guid.hex().upper()}")


@piv.command()
@piv.command(help="Print information about the PIV application.")
def info() -> None:
device = PivApp()
serial_number = device.serial()
Expand All @@ -94,11 +94,12 @@ def info() -> None:
pass


@piv.command()
@piv.command(help="Change the admin key.")
@click.option(
"--current-admin-key",
type=click.STRING,
default="010203040506070801020304050607080102030405060708",
help="Current admin key.",
)
@click.argument(
"new-admin-key",
Expand All @@ -120,64 +121,70 @@ def change_admin_key(current_admin_key: str, new_admin_key: str) -> None:
local_print("Changed key successfully")


@piv.command()
@piv.command(help="Change the PIN.")
@click.option(
"--current-pin",
type=click.STRING,
prompt="Enter the PIN",
hide_input=True,
help="Current PIN.",
)
@click.option(
"--new-pin",
type=click.STRING,
prompt="Enter the PIN",
hide_input=True,
help="New PIN.",
)
def change_pin(current_pin: str, new_pin: str) -> None:
device = PivApp()
device.change_pin(current_pin, new_pin)
local_print("Changed pin successfully")


@piv.command()
@piv.command(help="Change the PUK.")
@click.option(
"--current-puk",
type=click.STRING,
prompt="Enter the current PUK",
hide_input=True,
help="Current PUK.",
)
@click.option(
"--new-puk",
type=click.STRING,
prompt="Enter the new PUK",
hide_input=True,
help="New PUK.",
)
def change_puk(current_puk: str, new_puk: str) -> None:
device = PivApp()
device.change_puk(current_puk, new_puk)
local_print("Changed puk successfully")


@piv.command()
@piv.command(help="Reset the retry counter.")
@click.option(
"--puk",
type=click.STRING,
prompt="Enter the PUK",
hide_input=True,
help="Current PUK.",
)
@click.option(
"--new-pin",
type=click.STRING,
prompt="Enter the new PIN",
hide_input=True,
help="New PIN.",
)
def reset_retry_counter(puk: str, new_pin: str) -> None:
device = PivApp()
device.reset_retry_counter(puk, new_pin)
local_print("Unlocked PIN successfully")


@piv.command()
@piv.command(help="Reset the PIV application.")
def factory_reset() -> None:
device = PivApp()
try:
Expand Down Expand Up @@ -218,11 +225,12 @@ def factory_reset() -> None:
}


@piv.command()
@piv.command(help="Generate a new key and certificate signing request.")
@click.option(
"--admin-key",
type=click.STRING,
default="010203040506070801020304050607080102030405060708",
help="Current admin key",
)
@click.option(
"--key",
Expand Down Expand Up @@ -252,39 +260,47 @@ def factory_reset() -> None:
"93",
"94",
"95",
]
],
case_sensitive=False,
),
default="9A",
help="Key slot for operation.",
)
@click.option(
"--algo",
type=click.Choice(["rsa2048", "nistp256"]),
type=click.Choice(["rsa2048", "nistp256"], case_sensitive=False),
default="nistp256",
help="Algorithm for the key.",
)
@click.option(
"--domain-component",
type=click.STRING,
multiple=True,
help="Domain component for the certificate signing request.",
)
@click.option(
"--subject-name",
type=click.STRING,
multiple=True,
help="Subject name for the certificate signing request.",
)
@click.option(
"--subject-alt-name-upn",
type=click.STRING,
help="Subject alternative name (UPN) for the certificate signing request.",
)
@click.option(
"--pin",
type=click.STRING,
prompt="Enter the PIN",
hide_input=True,
help="Current PIN.",
)
@click.option(
"--out-file",
"--path",
type=click.Path(allow_dash=True),
default="-",
help="Write certificate signing request to path.",
)
def generate_key(
admin_key: str,
Expand All @@ -294,7 +310,7 @@ def generate_key(
subject_name: Optional[Sequence[str]],
subject_alt_name_upn: Optional[str],
pin: str,
out_file: str,
path: str,
) -> None:
try:
admin_key_bytes = bytearray.fromhex(admin_key)
Expand All @@ -303,13 +319,14 @@ def generate_key(
"Key is expected to be an hexadecimal string",
support_hint=False,
)
key_hex = key
key_hex = key.upper()
key_ref = int(key_hex, 16)

device = PivApp()
device.authenticate_admin(admin_key_bytes)
device.login(pin)

algo = algo.lower()
if algo == "rsa2048":
algo_id = b"\x07"
signature_algorithm = "sha256_rsa"
Expand Down Expand Up @@ -509,7 +526,7 @@ def generate_key(
}
)

with click.open_file(out_file, mode="wb") as file:
with click.open_file(path, mode="wb") as file:
file.write(csr.dump())

cert_info = x509.TbsCertificate(
Expand Down Expand Up @@ -563,13 +580,18 @@ def generate_key(
device.send_receive(0xDB, 0x3F, 0xFF, payload)


@piv.command()
@piv.command(help="Write a certificate to a key slot.")
@click.argument(
"admin-key",
type=click.STRING,
default="010203040506070801020304050607080102030405060708",
)
@click.option("--format", type=click.Choice(["DER", "PEM"]), default="PEM")
@click.option(
"--format",
type=click.Choice(["DER", "PEM"], case_sensitive=False),
default="PEM",
help="Format of certificate.",
)
@click.option(
"--key",
type=click.Choice(
Expand Down Expand Up @@ -598,14 +620,17 @@ def generate_key(
"93",
"94",
"95",
]
],
case_sensitive=False,
),
default="9A",
help="Key slot for operation.",
)
@click.option(
"--path",
type=click.Path(allow_dash=True),
default="-",
help="Write certificate to path.",
)
def write_certificate(admin_key: str, format: str, key: str, path: str) -> None:
try:
Expand All @@ -621,6 +646,7 @@ def write_certificate(admin_key: str, format: str, key: str, path: str) -> None:

with click.open_file(path, mode="rb") as f:
cert_bytes = f.read()
format = format.upper()
if format == "DER":
cert_serialized = cert_bytes
cert = cryptography.x509.load_der_x509_certificate(cert_bytes)
Expand All @@ -630,16 +656,21 @@ def write_certificate(admin_key: str, format: str, key: str, path: str) -> None:

payload = Tlv.build(
[
(0x5C, bytes(bytearray.fromhex(KEY_TO_CERT_OBJ_ID_MAP[key]))),
(0x5C, bytes(bytearray.fromhex(KEY_TO_CERT_OBJ_ID_MAP[key.upper()]))),
(0x53, Tlv.build([(0x70, cert_serialized), (0x71, bytes([0]))])),
]
)

device.send_receive(0xDB, 0x3F, 0xFF, payload)


@piv.command()
@click.option("--out-format", type=click.Choice(["DER", "PEM"]), default="PEM")
@piv.command(help="Read a certificate from a key slot.")
@click.option(
"--format",
type=click.Choice(["DER", "PEM"], case_sensitive=False),
default="PEM",
help="Format of certificate.",
)
@click.option(
"--key",
type=click.Choice(
Expand Down Expand Up @@ -668,24 +699,32 @@ def write_certificate(admin_key: str, format: str, key: str, path: str) -> None:
"93",
"94",
"95",
]
],
case_sensitive=False,
),
default="9A",
help="Key slot for operation.",
)
@click.option("--path", type=click.Path(allow_dash=True), default="-")
def read_certificate(out_format: str, key: str, path: str) -> None:
@click.option(
"--path",
type=click.Path(allow_dash=True),
default="-",
help="Read certificate from path.",
)
def read_certificate(format: str, key: str, path: str) -> None:
device = PivApp()

value = device.cert(bytes(bytearray.fromhex(KEY_TO_CERT_OBJ_ID_MAP[key])))
value = device.cert(bytes(bytearray.fromhex(KEY_TO_CERT_OBJ_ID_MAP[key.upper()])))

if value is None:
print("Certificate not found", file=sys.stderr)
return

if out_format == "DER":
format = format.upper()
if format == "DER":
cert_serialized = value
cryptography.x509.load_der_x509_certificate(value)
elif out_format == "PEM":
elif format == "PEM":
cert = cryptography.x509.load_der_x509_certificate(value)
cert_serialized = cert.public_bytes(Encoding.PEM)

Expand Down

0 comments on commit f206e2e

Please sign in to comment.