@@ -396,48 +396,89 @@ def test_pkcs11_bad_override_fails_before_login(tmp_path):
396396)
397397
398398
399+ # CI sets PYFF_REQUIRE_SOFTHSM=1 so a missing SoftHSM2 or a failing key type
400+ # fails the run instead of being skipped silently.
401+ REQUIRE_SOFTHSM = os .environ .get ("PYFF_REQUIRE_SOFTHSM" , "" ).strip ().lower () in ("1" , "true" , "yes" )
402+
403+
404+ def _softhsm_unavailable (reason ):
405+ if REQUIRE_SOFTHSM :
406+ pytest .fail (f"{ reason } (PYFF_REQUIRE_SOFTHSM is set)" )
407+ pytest .skip (reason )
408+
409+
399410@pytest .fixture (scope = "module" )
400411def softhsm_token (tmp_path_factory ):
401- """A SoftHSM2 token with RSA, EC P-256 and Ed25519 keys, and a cert for each ."""
412+ """An initialized SoftHSM2 token; keys are created per test by ``_softhsm_key`` ."""
402413 if SOFTHSM_MODULE is None or not shutil .which ("softhsm2-util" ) or not shutil .which ("pkcs11-tool" ):
403- pytest . skip ("SoftHSM2 and pkcs11-tool are required" )
414+ _softhsm_unavailable ("SoftHSM2 and pkcs11-tool are required" )
404415
405416 directory = tmp_path_factory .mktemp ("softhsm" )
406417 (directory / "tokens" ).mkdir ()
407418 conf = directory / "softhsm2.conf"
408419 conf .write_text (f"directories.tokendir = { directory / 'tokens' } \n objectstore.backend = file\n " )
409420 env = {** os .environ , "SOFTHSM2_CONF" : str (conf )}
410421 pin , label = "1234" , "pyff-sigalg"
422+ try :
423+ subprocess .run (
424+ ("softhsm2-util" , "--init-token" , "--free" , "--label" , label , "--pin" , pin , "--so-pin" , "5678" ),
425+ check = True ,
426+ env = env ,
427+ capture_output = True ,
428+ )
429+ except subprocess .CalledProcessError as ex :
430+ _softhsm_unavailable (f"Unable to initialize a SoftHSM2 token: { ex .stderr !r} " )
431+
432+ return {
433+ "directory" : directory ,
434+ "env" : env ,
435+ "pin" : pin ,
436+ "label" : label ,
437+ "ca" : ec .generate_private_key (ec .SECP256R1 ()),
438+ "certs" : {},
439+ }
440+
411441
412- def run (* args ):
413- subprocess .run (args , check = True , env = env , capture_output = True )
414-
415- run ("softhsm2-util" , "--init-token" , "--free" , "--label" , label , "--pin" , pin , "--so-pin" , "5678" )
416- ca = ec .generate_private_key (ec .SECP256R1 ())
417- certs = {}
418- for key_label , key_type in (("rsa" , "rsa:2048" ), ("ec" , "EC:prime256v1" ), ("ed" , "EC:edwards25519" )):
419- pkcs11 = ("pkcs11-tool" , "--module" , SOFTHSM_MODULE , "--token-label" , label )
420- try :
421- run (* pkcs11 , "--login" , "--pin" , pin , "--keypairgen" , "--key-type" , key_type , "--label" , key_label )
422- pub_path = directory / f"{ key_label } .pub"
423- run (* pkcs11 , "--read-object" , "--type" , "pubkey" , "--label" , key_label , "-o" , str (pub_path ))
424- except subprocess .CalledProcessError as ex :
425- pytest .skip (f"Unable to create a { key_type } key on SoftHSM2: { ex .stderr !r} " )
426- raw = pub_path .read_bytes ()
427- # pkcs11-tool writes EdDSA public keys as PEM even when asked for DER.
428- public_key = ser .load_pem_public_key (raw ) if raw .startswith (b"-----" ) else ser .load_der_public_key (raw )
429- cert_path = directory / f"{ key_label } .crt"
430- cert_path .write_text (_self_signed (None , cn = key_label , public_key = public_key , issuer_key = ca ))
431- certs [key_label ] = str (cert_path )
432-
433- return {"env" : env , "pin" : pin , "label" : label , "certs" : certs }
442+ def _softhsm_key (token , key_label , key_type ):
443+ """Generate ``key_type`` on the token once and return the path of a cert for it.
444+
445+ A key type the installed SoftHSM2 / pkcs11-tool cannot generate skips only
446+ the tests for that type (or fails them when PYFF_REQUIRE_SOFTHSM is set).
447+ """
448+ if key_label in token ["certs" ]:
449+ return token ["certs" ][key_label ]
450+
451+ directory = token ["directory" ]
452+ pkcs11 = ("pkcs11-tool" , "--module" , SOFTHSM_MODULE , "--token-label" , token ["label" ])
453+ pub_path = directory / f"{ key_label } .pub"
454+ try :
455+ for args in (
456+ ("--login" , "--pin" , token ["pin" ], "--keypairgen" , "--key-type" , key_type , "--label" , key_label ),
457+ ("--read-object" , "--type" , "pubkey" , "--label" , key_label , "-o" , str (pub_path )),
458+ ):
459+ subprocess .run ((* pkcs11 , * args ), check = True , env = token ["env" ], capture_output = True )
460+ except subprocess .CalledProcessError as ex :
461+ _softhsm_unavailable (f"Unable to create a { key_type } key on SoftHSM2: { ex .stderr !r} " )
462+
463+ raw = pub_path .read_bytes ()
464+ # pkcs11-tool writes EdDSA public keys as PEM even when asked for DER.
465+ public_key = ser .load_pem_public_key (raw ) if raw .startswith (b"-----" ) else ser .load_der_public_key (raw )
466+ cert_path = directory / f"{ key_label } .crt"
467+ cert_path .write_text (_self_signed (None , cn = key_label , public_key = public_key , issuer_key = token ["ca" ]))
468+ token ["certs" ][key_label ] = str (cert_path )
469+ return token ["certs" ][key_label ]
434470
435471
436472@pytest .mark .parametrize (
437- "key_label, expected" , [("rsa" , A .RSA_SHA256 ), ("ec" , A .ECDSA_SHA256 ), ("ed" , A .EDDSA_ED25519 )]
473+ "key_label, key_type, expected" ,
474+ [
475+ ("rsa" , "rsa:2048" , A .RSA_SHA256 ),
476+ ("ec" , "EC:prime256v1" , A .ECDSA_SHA256 ),
477+ ("ed" , "EC:edwards25519" , A .EDDSA_ED25519 ),
478+ ],
438479)
439- def test_softhsm_sign (softhsm_token , key_label , expected ):
440- cert = softhsm_token [ "certs" ][ key_label ]
480+ def test_softhsm_sign (softhsm_token , key_label , key_type , expected ):
481+ cert = _softhsm_key ( softhsm_token , key_label , key_type )
441482 args = {
442483 "key" : f"pkcs11://{ SOFTHSM_MODULE } /{ key_label } " ,
443484 "token_label" : softhsm_token ["label" ],
0 commit comments