writeonce/runtime/test/gen_x509.py
shoney.arickathil 3a1ba15851 feat(tls): X.509 basicConstraints + EKU chain hardening (rv2 9 F3c-net decision 6)
- crypto.c: x509_find_ext (generic extension walker) + wo_x509_basic_constraints
  (cA / pathLenConstraint, absent => not a CA) + wo_x509_eku_serverauth_ok
  (EKU absent, serverAuth, or anyEKU => usable; else not)
- wo_tls_verify_chain enforces decision 6: the leaf must be server-usable
  (EKU), every server-sent issuer and the signing anchor must be a CA
  (basicConstraints CA:TRUE) with a pathLenConstraint covering the
  intermediates below it — stops a leaf masquerading as a CA
- gen_x509.py extended (folds in the wildcard leaf, adds EKU clientAuth-only,
  EKU serverAuth, a non-CA intermediate + a leaf issued under it); vectors
  regenerated
- KATs: extractors (test_crypto 104) + chain enforcement (test_tls 103) —
  EKU serverAuth accepted, clientAuth-only rejected, leaf-under-non-CA
  rejected though every signature verifies; existing chains still pass.
  ASan/UBSan clean

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

122 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Generate the wo_x509 KAT certificate vectors.
Two real chains (RSA CA+leaf, EC P-256 CA+leaf) for signature/SPKI/validity/SAN,
a wildcard-SAN leaf for hostname matching, and the phase-6 (decision 6) negatives:
a leaf whose EKU is clientAuth-only, a leaf with EKU serverAuth, a non-CA
intermediate, and a leaf issued under that non-CA intermediate.
Regenerate with: python3 runtime/test/gen_x509.py > runtime/test/x509_vectors.h
"""
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, ec
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 base(subject, issuer, pubkey):
return (x509.CertificateBuilder()
.subject_name(mkname(subject)).issuer_name(mkname(issuer))
.public_key(pubkey)
.serial_number(x509.random_serial_number())
.not_valid_before(NB).not_valid_after(NA))
def selfsigned(key, cn, ca=True):
b = base(cn, cn, key.public_key()).add_extension(
x509.BasicConstraints(ca=ca, path_length=None), True)
return b.sign(key, hashes.SHA256())
def leafcert(leafkey, cakey, ca_cert, cn, sanhost, eku=None):
b = (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))
if eku:
b = b.add_extension(x509.ExtendedKeyUsage(eku), False)
return b.sign(cakey, hashes.SHA256())
def intermediate(intkey, cakey, ca_cert, cn, ca):
"""An intermediate signed by ca; ca flag sets basicConstraints."""
return (x509.CertificateBuilder()
.subject_name(mkname(cn)).issuer_name(ca_cert.subject)
.public_key(intkey.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(NB).not_valid_after(NA)
.add_extension(x509.BasicConstraints(ca=ca, path_length=None), True)
.sign(cakey, hashes.SHA256()))
def cbytes(name, der, note=""):
out = ("/* %s */\n" % note if note else "") + "static const uint8_t %s[] = {" % name
for i, b in enumerate(der):
if i % 12 == 0:
out += "\n "
out += "0x%02x," % b
return out + "\n};\n"
def der(c):
return c.public_bytes(Encoding.DER)
# --- RSA chain ---
rsa_ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
rsa_ca = selfsigned(rsa_ca_key, "wo-rsa-ca")
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", "leaf.example.com")
# --- EC chain ---
ec_ca_key = ec.generate_private_key(ec.SECP256R1())
ec_ca = selfsigned(ec_ca_key, "wo-ec-ca")
ec_leaf_key = ec.generate_private_key(ec.SECP256R1())
ec_leaf = leafcert(ec_leaf_key, ec_ca_key, ec_ca, "leaf.example.org", "leaf.example.org")
# --- wildcard-SAN leaf (self-signed EC, SAN *.example.com) ---
wild_key = ec.generate_private_key(ec.SECP256R1())
wild_leaf = (base("wild", "wild", wild_key.public_key())
.add_extension(x509.SubjectAlternativeName([x509.DNSName("*.example.com")]), False)
.sign(wild_key, hashes.SHA256()))
# --- decision-6 negatives (all signed by rsa_ca so signatures verify) ---
# leaf whose EKU is clientAuth only -> must be rejected as a server cert
eku_client_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
leaf_eku_client = leafcert(eku_client_key, rsa_ca_key, rsa_ca, "leaf.example.com",
"leaf.example.com", eku=[ExtendedKeyUsageOID.CLIENT_AUTH])
# leaf with EKU serverAuth -> must be accepted
eku_server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
leaf_eku_server = leafcert(eku_server_key, rsa_ca_key, rsa_ca, "leaf.example.com",
"leaf.example.com", eku=[ExtendedKeyUsageOID.SERVER_AUTH])
# non-CA intermediate, and a leaf issued under it -> chain must be rejected
noca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
noca_mid = intermediate(noca_key, rsa_ca_key, rsa_ca, "wo-noca-mid", ca=False)
under_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
leaf_under_noca = leafcert(under_key, noca_key, noca_mid, "leaf.example.com", "leaf.example.com")
print("/* Generated by runtime/test/gen_x509.py — python cryptography %s.\n"
" * RSA + EC chains, a wildcard-SAN leaf, and decision-6 negatives\n"
" * (EKU clientAuth-only, EKU serverAuth, a non-CA intermediate + a\n"
" * leaf issued under it). Vectors for the wo_x509 KATs. */" %
__import__("cryptography").__version__)
print(cbytes("kat_rsa_ca", der(rsa_ca)))
print(cbytes("kat_rsa_leaf", der(rsa_leaf)))
print(cbytes("kat_ec_ca", der(ec_ca)))
print(cbytes("kat_ec_leaf", der(ec_leaf)))
print(cbytes("kat_wild_leaf", der(wild_leaf), "wildcard-SAN leaf (*.example.com)"))
print(cbytes("kat_leaf_eku_client", der(leaf_eku_client), "EKU clientAuth only -> reject as server"))
print(cbytes("kat_leaf_eku_server", der(leaf_eku_server), "EKU serverAuth -> accept"))
print(cbytes("kat_noca_mid", der(noca_mid), "intermediate with basicConstraints CA:FALSE"))
print(cbytes("kat_leaf_under_noca", der(leaf_under_noca), "leaf issued under the non-CA intermediate"))