1313
1414from cryptography import x509
1515from cryptography .hazmat .primitives import hashes
16+ from cryptography .hazmat .primitives .asymmetric import ec , rsa
1617from cryptography .hazmat .primitives .ciphers import Cipher , modes
1718from cryptography .hazmat .primitives .serialization import Encoding , pkcs12
18- from cryptography .hazmat .primitives .asymmetric import ec , rsa
1919
2020try :
2121 from cryptography .hazmat .decrepit .ciphers .algorithms import TripleDES
2222except ImportError :
23- from cryptography .hazmat .primitives .ciphers .algorithms import TripleDES # type: ignore[no-reattr]
23+ from cryptography .hazmat .primitives .ciphers .algorithms import (
24+ TripleDES , # type: ignore[no-reattr]
25+ )
2426
2527logger = logging .getLogger (__name__ )
2628
2931
3032# SELECT PIV APDU (matches pynitrokey's piv_app.py)
3133PIV_SELECT_APDU = [
32- 0x00 , 0xA4 , 0x04 , 0x00 , 0x0C ,
33- 0xA0 , 0x00 , 0x00 , 0x03 , 0x08 , 0x00 , 0x00 , 0x10 , 0x00 , 0x01 , 0x00 , 0x00 ,
34+ 0x00 ,
35+ 0xA4 ,
36+ 0x04 ,
37+ 0x00 ,
38+ 0x0C ,
39+ 0xA0 ,
40+ 0x00 ,
41+ 0x00 ,
42+ 0x03 ,
43+ 0x08 ,
44+ 0x00 ,
45+ 0x00 ,
46+ 0x10 ,
47+ 0x00 ,
48+ 0x01 ,
49+ 0x00 ,
50+ 0x00 ,
3451]
3552
3653# Map from slot hex string to certificate container object ID
6279
6380# ─── TLV helpers (ported from pynitrokey/tlv.py) ─────────────────────────────
6481
82+
6583def _tlv_build_one (tag : int , data : bytes ) -> bytes :
6684 if tag <= 0xFF :
6785 tag_bytes = bytes ([tag ])
@@ -132,6 +150,7 @@ def find_by_id(tag: int, items: list[tuple[int, bytes]]) -> Optional[bytes]:
132150
133151# ─── Data classes ─────────────────────────────────────────────────────────────
134152
153+
135154@dataclass
136155class PivCertInfo :
137156 subject : str
@@ -179,6 +198,7 @@ def has_cert(self) -> bool:
179198
180199# ─── PIV error ────────────────────────────────────────────────────────────────
181200
201+
182202class PivError (Exception ):
183203 def __init__ (self , status : int , message : str = "" ) -> None :
184204 self .status = status
@@ -207,6 +227,7 @@ def pin_retries(self) -> int:
207227
208228# ─── PivApp ───────────────────────────────────────────────────────────────────
209229
230+
210231class PivApp :
211232 """PIV APDU session over a smartcard (pyscard) connection."""
212233
@@ -234,14 +255,16 @@ def __exit__(self, *args: Any) -> None:
234255 def open (cls ) -> "PivApp" :
235256 """Open a PIV session on the first NK3 found via smartcard interface."""
236257 try :
237- from smartcard .Exceptions import CardConnectionException , NoCardException
238- from smartcard .ExclusiveTransmitCardConnection import ExclusiveTransmitCardConnection
239- from smartcard .System import readers as list_readers
240- except ImportError :
241- raise PivError (
242- 0x0000 ,
243- "pyscard is not installed. Run: pip install pyscard" ,
258+ from smartcard .Exceptions import ( # type: ignore[import-untyped]
259+ CardConnectionException ,
260+ NoCardException ,
261+ )
262+ from smartcard .ExclusiveTransmitCardConnection import ( # type: ignore[import-untyped]
263+ ExclusiveTransmitCardConnection ,
244264 )
265+ from smartcard .System import readers as list_readers # type: ignore[import-untyped]
266+ except ImportError :
267+ raise PivError (0x0000 , "pyscard is not installed. Run: pip install pyscard" ) from None
245268
246269 nk3_atr = list (NK3_ATR )
247270 for reader in list_readers ():
@@ -316,16 +339,19 @@ def authenticate_admin(self, admin_key: bytes = DEFAULT_ADMIN_KEY) -> None:
316339 """Mutual-authenticate with the management key (3DES or AES)."""
317340 if len (admin_key ) == 24 :
318341 from cryptography .hazmat .primitives .ciphers import algorithms as _alg
319- algorithm = TripleDES (admin_key )
342+
343+ algorithm : Any = TripleDES (admin_key )
320344 algo_byte = 0x03
321345 expected_len = 8
322346 elif len (admin_key ) == 16 :
323347 from cryptography .hazmat .primitives .ciphers import algorithms as _alg
348+
324349 algorithm = _alg .AES128 (admin_key )
325350 algo_byte = 0x08
326351 expected_len = 16
327352 elif len (admin_key ) == 32 :
328353 from cryptography .hazmat .primitives .ciphers import algorithms as _alg
354+
329355 algorithm = _alg .AES256 (admin_key )
330356 algo_byte = 0x0C
331357 expected_len = 16
@@ -493,8 +519,7 @@ def generate_key_and_cert(
493519 if modulus_data is None or exponent_data is None :
494520 raise PivError (0x0000 , "No RSA key data in generate key response" )
495521 public_key_rsa = rsa .RSAPublicNumbers (
496- int .from_bytes (exponent_data , "big" ),
497- int .from_bytes (modulus_data , "big" ),
522+ int .from_bytes (exponent_data , "big" ), int .from_bytes (modulus_data , "big" )
498523 ).public_key ()
499524 signer = _RsaPivSigner (self , key_ref , public_key_rsa )
500525 cert = (
@@ -514,25 +539,36 @@ def generate_key_and_cert(
514539 slot_id_hex = format (key_ref , "02X" )
515540 self .write_certificate (slot_id_hex , cert_der )
516541
517- def import_rsa2048 (self , key_ref : int , key : rsa .RSAPrivateNumbers , public_key : rsa .RSAPublicNumbers ) -> None :
542+ def import_rsa2048 (
543+ self , key_ref : int , key : rsa .RSAPrivateNumbers , public_key : rsa .RSAPublicNumbers
544+ ) -> None :
518545 self .send_receive (
519- 0xFE , 0x07 , key_ref ,
520- Tlv .build ([
521- (0x01 , key .p .to_bytes (128 , "big" )),
522- (0x02 , key .q .to_bytes (128 , "big" )),
523- (0x03 , public_key .e .to_bytes ((public_key .e .bit_length () + 7 ) // 8 , "big" )),
524- ]),
546+ 0xFE ,
547+ 0x07 ,
548+ key_ref ,
549+ Tlv .build (
550+ [
551+ (0x01 , key .p .to_bytes (128 , "big" )),
552+ (0x02 , key .q .to_bytes (128 , "big" )),
553+ (0x03 , public_key .e .to_bytes ((public_key .e .bit_length () + 7 ) // 8 , "big" )),
554+ ]
555+ ),
525556 )
526557
527558 def write_certificate (self , slot_id : str , cert_der : bytes ) -> None :
528559 container_id = KEY_TO_CERT_OBJ_ID [slot_id .upper ()]
529- payload = Tlv .build ([
530- (0x5C , container_id ),
531- (0x53 , Tlv .build ([(0x70 , cert_der ), (0x71 , bytes ([0 ]))])),
532- ])
560+ payload = Tlv .build (
561+ [(0x5C , container_id ), (0x53 , Tlv .build ([(0x70 , cert_der ), (0x71 , bytes ([0 ]))]))]
562+ )
533563 self .send_receive (0xDB , 0x3F , 0xFF , payload )
534564
535- def import_p12 (self , slot_id : str , p12_data : bytes , password : Optional [bytes ], admin_key : bytes = DEFAULT_ADMIN_KEY ) -> None :
565+ def import_p12 (
566+ self ,
567+ slot_id : str ,
568+ p12_data : bytes ,
569+ password : Optional [bytes ],
570+ admin_key : bytes = DEFAULT_ADMIN_KEY ,
571+ ) -> None :
536572 """Import a PKCS#12 file (RSA 2048 only) into a slot."""
537573 private_key , certificate , _ = pkcs12 .load_key_and_certificates (p12_data , password )
538574
@@ -545,9 +581,7 @@ def import_p12(self, slot_id: str, p12_data: bytes, password: Optional[bytes], a
545581
546582 self .authenticate_admin (admin_key )
547583 self .import_rsa2048 (
548- key_ref ,
549- private_key .private_numbers (),
550- private_key .public_key ().public_numbers (),
584+ key_ref , private_key .private_numbers (), private_key .public_key ().public_numbers ()
551585 )
552586 self ._select_piv ()
553587 self .authenticate_admin (admin_key )
@@ -568,16 +602,17 @@ def list_slots(self) -> list[PivSlotInfo]:
568602 logger .warning (f"Failed to parse cert for slot { slot_id } : { e } " )
569603 except PivError as e :
570604 logger .debug (f"Error reading slot { slot_id } : { e } " )
571- slots .append (PivSlotInfo (
572- slot_id = slot_id ,
573- display_name = SLOT_DISPLAY_NAMES [slot_id ],
574- cert = cert_info ,
575- ))
605+ slots .append (
606+ PivSlotInfo (
607+ slot_id = slot_id , display_name = SLOT_DISPLAY_NAMES [slot_id ], cert = cert_info
608+ )
609+ )
576610 return slots
577611
578612
579613# ─── Helpers ──────────────────────────────────────────────────────────────────
580614
615+
581616def _encode_pin (pin : str ) -> bytes :
582617 body = pin .encode ("utf-8" )
583618 if len (body ) > 8 :
@@ -599,6 +634,7 @@ def _prepare_pkcs1v15_sha256(data: bytes) -> bytes:
599634
600635# ─── PIV signing proxy classes (wrap device signing for cert generation) ──────
601636
637+
602638class _RsaPivSigner (rsa .RSAPrivateKey ):
603639 def __init__ (self , device : PivApp , key_ref : int , public_key : rsa .RSAPublicKey ) -> None :
604640 self ._device = device
0 commit comments