Repository navigation
Add piv admin rotate-keys and delete-factory-keyset - #28
Merged
Merged
Conversation
`rotate-keys` replaces key version 1 of the PIV security domain in place with one PUT KEY: current keys from --default-keys or PIV_SCP03_*, new keys from PIV_SCP03_NEW_ENC/MAC/DEK, never the command line. It reads the key table first, writes only key version 1 in the type the session speaks (DES under the session DEK on SCP02, AES under the static DEK on SCP03), compares the key check values the card echoes, authenticates once with the new keys and re-reads the table so key version 2 is confirmed untouched. A refused PUT KEY writes nothing; a failed post-check exits non-zero with the recovery hint. --dry-run shows the plan and check values. `delete-factory-keyset` deletes key version 2 (DELETE KEY by version), authenticated with key version 1 from PIV_SCP03_* only, refuses while those are still the test keys, needs the typed DELETE-FACTORY-KEYSET or the --i-understand-this-is-irreversible flag, and re-reads the table. The SCP02 path is verified on hardware; the SCP03 path against a loopback security-domain double that runs the real session, key wrap and check values. cryptography>=43 is now required: the SCP02 code imports its decrepit 3DES module. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||
| def encrypt_key_aes(dek: bytes, key: bytes) -> bytes: | ||
| """SCP03 key data: AES-CBC with a zero ICV under the static DEK.""" | ||
| enc = Cipher(algorithms.AES(dek), modes.CBC(bytes(16))).encryptor() |
| def kcv_aes(key: bytes) -> bytes: | ||
| """GP key check value for an AES key: AES-ECB of sixteen 0x01 bytes, first 3 bytes.""" | ||
| enc = Cipher(algorithms.AES(key), modes.ECB()).encryptor() # noqa: S305 - KCV | ||
| return (enc.update(b"\x01" * 16) + enc.finalize())[:3] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
piv admin rotate-keys: replaces key version 1 of the PIV security domain in place with one PUT KEY (80 D8 01 81). Current keys from--default-keysorPIV_SCP03_ENC/MAC/DEK; new keys fromPIV_SCP03_NEW_ENC/MAC/DEK(hex, 16 bytes each,resolve_new_scp03_keys), never the command line. Reads the key table first; refuses when key version 1 is absent, when the new keys are the test keys, or equal the current ones. The channel is opened at the security domain's own AID: the PIV applet does not proxy PUT KEY. Key values travel in the type the session speaks: DES under the session DEK on SCP02, AES under the static DEK on SCP03 (ssd.put_key_apdu). Post-checks: card-echoed key check values equal ours, a fresh authentication with the new keys, key table re-read with the key version 2 entries unchanged; any failure exits non-zero with the recovery hint.--dry-runprints the plan and the new keys' check values. Confirmation prompt or--yes.piv admin delete-factory-keyset:DELETE KEYby key version (80 E4 00 00 03 D2 01 02), authenticated with key version 1 fromPIV_SCP03_*only (no--default-keys; refuses while those equal the test key). No-op with exit 0 when key version 2 is absent. TypedDELETE-FACTORY-KEYSETor--i-understand-this-is-irreversible;--dry-run. Re-reads the table and fails if version 2 is still listed.ssd.py:kcv_des/kcv_aes(GP check values),encrypt_key_des/encrypt_key_aes,put_key_apdu,delete_key_version_apdu,CUSTOMER_KEY_VERSION = 1,CRYPTNOX_KEY_VERSION = 2.cryptography>=43is now the floor: the SCP02 code importshazmat.decrepit(3DES), absent before 43; an install on a system with cryptography 41 failed at start-up.piv-commands.rst, including that a domain is bound to SCP02 or SCP03 at creation and a keyset of the other type is accepted by the card but can never open a session.Deviation from the plan: 32-byte keys are refused outright rather than hidden behind a flag.
Verification: the SCP02 path end to end on a D600 development card (dry run, rotation, authentication with the new keys, a throwaway key version 2 added and deleted, rollback, default-key check). The SCP03 path against a loopback security-domain double that runs the real session, static-DEK wrap and key check values (
test_admin_rotate_scp03_loopback.py); not on SCP03 silicon, which no card in our hands provides. Known-answer tests pin the check values (8BAF47for the default key under 3DES). Ruff, 533 pytest, Sphinx-Wall green.🤖 Generated with Claude Code