certauth/tests/test_signing.py
Jarian Cottingham 4bd538b217 fix: YubiKey CA signing, remove inline API copies, env-driven config
Critical: build_leaf_cert self-signed leaves with the leaf key instead of
the YubiKey-held Intermediate CA key. Now extracts TBS, signs via
pkcs11-tool (ECDSA-SHA384), reassembles, and verifies against the
intermediate CA public key before returning.

- setup-certauth.sh: ~1100 lines of stale inline api/ copies replaced with
  copy-from-repo (single source of truth); writes private
  /etc/certauth/certauth.env (0600); DB init loads env, no more
  swallowed errors; systemd unit gets EnvironmentFile=
- config.py: YubiKey serials no longer hard-coded (env, fail closed);
  aarch64-only PKCS#11 path replaced with arch-neutral default; all
  paths env-overridable (CERTAUTH_*)
- main.py: removed dead fastapi.security.CSRFProtection import (crashed
  startup); module-relative static/templates dirs; created_by resolved
  from the authenticated user instead of hard-coded 1; unclosed file
  handles fixed; domain_id 0 stored as NULL (FK bug)
- models.py: certificates.domain_id FK pointed at users(id), now domains(id)
- login: CSRF token now actually sent and validated
- tests: 23 tests (auth, API flows, DER helpers, signing pipeline)
- README, LICENSE, requirements.txt, pyproject.toml
2026-08-20 22:03:01 +00:00

56 lines
1.7 KiB
Python

"""DER helpers and signing pipeline (no YubiKey required)."""
from datetime import datetime, timedelta, timezone
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.oid import NameOID
from signing import _der_read_len, _der_seq, _split_cert_der
from cryptography.hazmat.primitives.serialization import Encoding
def _dummy_cert_der():
key = ec.generate_private_key(ec.SECP384R1())
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "dummy.test")])
cert = (
x509.CertificateBuilder()
.subject_name(name)
.issuer_name(name)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=1))
.sign(key, hashes.SHA384())
)
return cert.public_bytes(Encoding.DER)
def test_split_roundtrip():
der = _dummy_cert_der()
tbs, alg, sig = _split_cert_der(der)
assert alg[0] == 0x30
assert sig[0] == 0x03
# Reassembling the same parts reproduces the original certificate.
reassembled = _der_seq(tbs + alg + sig)
assert reassembled == der
def test_der_read_len_short_and_long():
assert _der_read_len(b"\x05ABC", 0) == (5, 1)
long_len = b"\x81\x10"
assert _der_read_len(long_len + b"A" * 16, 0) == (16, 2)
def test_build_leaf_cert_fails_cleanly_without_ca():
"""Without the CA files the pipeline fails cleanly (no YubiKey available)."""
import pytest
from signing import build_leaf_cert
with pytest.raises(FileNotFoundError):
build_leaf_cert("nope.test", "", 365)