writeonce/runtime/test/gen_x509.py
shoney.arickathil 5f5d774d76 feat(crypto): X.509 chain-link verification (rv2 9 phase E core)
- defensive ASN.1/DER reader: every length/bound checked; malformation
  is rejection, never over-read (truncated input KAT-gated)
- x509_parse: tbsCertificate span, sig-alg OID, signature,
  SubjectPublicKeyInfo (RSA n/e or EC P-256 x/y), validity dates
- wo_x509_verify_one: one chain link's signature, dispatching to
  phase-D RSA-PKCS1/PSS + ECDSA-P256 by the issuer key type
- wo_x509_parse_spki + wo_x509_check_validity (caller supplies time)
- KAT against real python-generated chains (test/gen_x509.py):
  RSA CA+leaf (SHA256withRSA), EC P-256 CA+leaf (ecdsa-with-SHA256);
  leaf-vs-CA, self-signed CA, wrong-issuer/tampered/truncated reject,
  validity window, SPKI extraction. test_crypto 84 pass, ASan/UBSan clean
- deferred to phase F: SAN/hostname match + multi-cert chain walk to a
  system CA bundle (both need the target host / trust store, known at
  handshake time)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4ec1c75f889df3612e31b9a1a77c4c927fb546c1)
2026-09-15 01:15:31 +02:00

67 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Generate two cert chains (RSA and EC P-256) for the wo_x509 KAT.
Emits C hex byte arrays: RSA CA + RSA leaf, EC CA + EC leaf."""
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
from cryptography.hazmat.primitives.serialization import Encoding
NB = datetime.datetime(2020, 1, 1)
NA = datetime.datetime(2030, 1, 1)
def mkname(cn):
return x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)])
def selfsigned(key, cn, hashalg, sanhost=None):
b = (x509.CertificateBuilder()
.subject_name(mkname(cn)).issuer_name(mkname(cn))
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(NB).not_valid_after(NA)
.add_extension(x509.BasicConstraints(ca=True, path_length=None), True))
if sanhost:
b = b.add_extension(x509.SubjectAlternativeName([x509.DNSName(sanhost)]), False)
return b.sign(key, hashalg)
def leafcert(leafkey, cakey, ca_cert, cn, hashalg, sanhost):
return (x509.CertificateBuilder()
.subject_name(mkname(cn)).issuer_name(ca_cert.subject)
.public_key(leafkey.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(NB).not_valid_after(NA)
.add_extension(x509.SubjectAlternativeName([x509.DNSName(sanhost)]), False)
.sign(cakey, hashalg))
def cbytes(name, der):
out = "static const uint8_t %s[] = {" % name
for i, b in enumerate(der):
if i % 12 == 0:
out += "\n "
out += "0x%02x," % b
out += "\n};\n"
return out
# RSA chain
rsa_ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
rsa_ca = selfsigned(rsa_ca_key, "wo-rsa-ca", hashes.SHA256())
rsa_leaf_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
rsa_leaf = leafcert(rsa_leaf_key, rsa_ca_key, rsa_ca, "leaf.example.com",
hashes.SHA256(), "leaf.example.com")
# EC chain
ec_ca_key = ec.generate_private_key(ec.SECP256R1())
ec_ca = selfsigned(ec_ca_key, "wo-ec-ca", hashes.SHA256())
ec_leaf_key = ec.generate_private_key(ec.SECP256R1())
ec_leaf = leafcert(ec_leaf_key, ec_ca_key, ec_ca, "leaf.example.org",
hashes.SHA256(), "leaf.example.org")
print("/* Generated by scratchpad/gen_x509.py — python cryptography %s.\n"
" * Two real chains: RSA CA+leaf (SHA256withRSA), EC P-256 CA+leaf\n"
" * (ecdsa-with-SHA256). Vectors for the wo_x509 phase-E KAT. */" %
__import__("cryptography").__version__)
print(cbytes("kat_rsa_ca", rsa_ca.public_bytes(Encoding.DER)))
print(cbytes("kat_rsa_leaf", rsa_leaf.public_bytes(Encoding.DER)))
print(cbytes("kat_ec_ca", ec_ca.public_bytes(Encoding.DER)))
print(cbytes("kat_ec_leaf", ec_leaf.public_bytes(Encoding.DER)))