Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIV rebase update #565

Merged
merged 6 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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