Skip to content

Commit fae1007

Browse files
committed
add: CI runs softhsm tests
1 parent 2dbbecc commit fae1007

2 files changed

Lines changed: 77 additions & 27 deletions

File tree

‎.github/workflows/python-package.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,16 @@ jobs:
3737
uv sync --locked --all-extras --dev
3838
3939
40+
- name: Install SoftHSM2 and OpenSC for PKCS#11 signing tests
41+
run: |
42+
sudo apt-get update
43+
sudo apt-get install -y softhsm2 opensc
44+
4045
- name: Run tests
46+
env:
47+
# Fail instead of skipping the SoftHSM2 signing tests if the token
48+
# cannot be set up or a key type cannot be generated.
49+
PYFF_REQUIRE_SOFTHSM: "1"
4150
run: |
4251
uv run python -m coverage erase
4352
uv run python -m coverage run -m pytest --cov=src/pyff

‎src/pyff/test/test_sigalg.py‎

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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")
400411
def 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'}\nobjectstore.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

Comments
 (0)