102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
import shutil
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
import setup_cert
|
|
|
|
# All of these drive the real `openssl` CLI the way setup_cert does.
|
|
pytestmark = pytest.mark.skipif(
|
|
shutil.which("openssl") is None, reason="openssl CLI not available")
|
|
|
|
UUID = "04700f20-1111-2222-3333-444455556666"
|
|
|
|
|
|
def _make_ca(dir_path):
|
|
"""A throwaway self-signed CA standing in for the AC14K_M signer."""
|
|
cert = dir_path / "ca.pem"
|
|
key = dir_path / "ca.key"
|
|
subprocess.run(
|
|
["openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
|
|
"-keyout", str(key), "-out", str(cert), "-days", "1",
|
|
"-subj", "/CN=AC14K_M"],
|
|
check=True, capture_output=True)
|
|
return cert, key
|
|
|
|
|
|
def test_mint_cert_produces_sha1_leaf_with_uuid(tmp_path):
|
|
ca_cert, ca_key = _make_ca(tmp_path)
|
|
paths = setup_cert.mint_cert(
|
|
UUID, ca_cert, ca_key, [ca_cert], tmp_path / "out")
|
|
|
|
for name in ("key", "leaf", "fullchain"):
|
|
assert paths[name].exists() and paths[name].stat().st_size > 0
|
|
|
|
text = subprocess.run(
|
|
["openssl", "x509", "-in", str(paths["leaf"]), "-noout", "-text"],
|
|
check=True, capture_output=True, text=True).stdout
|
|
assert "sha1WithRSAEncryption" in text # SHA-1 signed leaf
|
|
assert f"URI:urn:uuid:{UUID}" in text # UUID in the SAN
|
|
assert "1.3.6.1.4.1.51414" in text # custom OIDs parsed
|
|
# fullchain is leaf + supplied chain
|
|
assert paths["fullchain"].read_text().count("BEGIN CERTIFICATE") == 2
|
|
|
|
|
|
def test_mint_cert_surfaces_openssl_error(tmp_path):
|
|
"""A genuine signing failure raises CommandError carrying openssl's
|
|
output, instead of a bare non-zero-exit traceback."""
|
|
ca_cert, _ = _make_ca(tmp_path)
|
|
with pytest.raises(setup_cert.CommandError) as exc:
|
|
setup_cert.mint_cert(
|
|
UUID, ca_cert, tmp_path / "missing.key", [ca_cert],
|
|
tmp_path / "out")
|
|
assert "command failed" in str(exc.value)
|
|
assert len(str(exc.value)) > 40 # includes detail, not just an exit code
|
|
|
|
|
|
def test_mint_cert_retries_when_sha1_signing_blocked(tmp_path, monkeypatch):
|
|
"""Simulate a Fedora/RHEL crypto policy rejecting SHA-1: the first
|
|
(plain) signing attempt fails, and the SHA-1-override retry recovers."""
|
|
ca_cert, ca_key = _make_ca(tmp_path)
|
|
real_run = setup_cert.run
|
|
attempts = {"plain": 0}
|
|
|
|
def fake_run(cmd, **kw):
|
|
# Only the plain attempt has no OPENSSL_CONF override in its env.
|
|
if cmd[:3] == ["openssl", "x509", "-req"] and "env" not in kw:
|
|
attempts["plain"] += 1
|
|
raise setup_cert.CommandError(
|
|
"error: sha1 signature disabled by crypto policy")
|
|
return real_run(cmd, **kw)
|
|
|
|
monkeypatch.setattr(setup_cert, "run", fake_run)
|
|
paths = setup_cert.mint_cert(
|
|
UUID, ca_cert, ca_key, [ca_cert], tmp_path / "out")
|
|
|
|
assert attempts["plain"] == 1 # the plain path was exercised
|
|
assert paths["leaf"].exists() # the override retry recovered
|
|
|
|
|
|
def test_sha1_retry_does_not_give_openssl_3_config_to_libressl(monkeypatch):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kw):
|
|
calls.append((cmd, kw))
|
|
if cmd == ["openssl", "version"]:
|
|
return subprocess.CompletedProcess(cmd, 0, "LibreSSL 3.3.6\n", "")
|
|
return subprocess.CompletedProcess(cmd, 0, "", "")
|
|
|
|
monkeypatch.setattr(setup_cert, "run", fake_run)
|
|
monkeypatch.setenv("OPENSSL_CONF", "/synthetic/inherited.cnf")
|
|
|
|
setup_cert.run_allow_sha1(["openssl", "x509", "-req"])
|
|
|
|
assert calls[1][0] == ["openssl", "x509", "-req"]
|
|
assert "OPENSSL_CONF" not in calls[1][1]["env"]
|
|
|
|
|
|
def test_command_error_includes_stderr():
|
|
with pytest.raises(setup_cert.CommandError) as exc:
|
|
setup_cert.run(["openssl", "x509", "-in", "/no/such/file"])
|
|
assert "command failed" in str(exc.value)
|