113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
"""Self-signed local CA + leaf cert generation for the WP-230D E2E harness.
|
|
|
|
Playwright drives a real browser through a real PKCE/BFF login redirect, and
|
|
`OIDCSettings`/`DiscoveryDocument` both hard-require an ``https://`` issuer
|
|
(see `services/control_api/auth/config.py`), so the fake OIDC provider and
|
|
the web reverse-proxy origin the browser visits both need to terminate real
|
|
TLS. This generates one throwaway CA per harness run and a leaf cert for
|
|
127.0.0.1/localhost signed by it, so the *server-side* `OIDCProviderClient`
|
|
can do genuine certificate-chain verification against a real trust root
|
|
(`verify=<ca path>`) rather than disabling verification. The browser side
|
|
separately uses Playwright's `ignoreHTTPSErrors`, since trusting an
|
|
ephemeral local test CA in the OS/browser store is unnecessary for a
|
|
throwaway process-local fixture.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime
|
|
import ipaddress
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from cryptography import x509
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
from cryptography.x509.oid import NameOID
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LocalTLSMaterial:
|
|
ca_cert_path: Path
|
|
cert_path: Path
|
|
key_path: Path
|
|
|
|
|
|
def generate_local_tls_material(output_dir: Path) -> LocalTLSMaterial:
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
now = datetime.datetime.now(datetime.UTC)
|
|
not_before = now - datetime.timedelta(minutes=5)
|
|
not_after = now + datetime.timedelta(hours=6)
|
|
|
|
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "ITWorx MCP Hub E2E Local CA")])
|
|
ca_cert = (
|
|
x509.CertificateBuilder()
|
|
.subject_name(ca_name)
|
|
.issuer_name(ca_name)
|
|
.public_key(ca_key.public_key())
|
|
.serial_number(x509.random_serial_number())
|
|
.not_valid_before(not_before)
|
|
.not_valid_after(not_after)
|
|
.add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True)
|
|
.add_extension(
|
|
x509.KeyUsage(
|
|
digital_signature=False,
|
|
content_commitment=False,
|
|
key_encipherment=False,
|
|
data_encipherment=False,
|
|
key_agreement=False,
|
|
key_cert_sign=True,
|
|
crl_sign=True,
|
|
encipher_only=False,
|
|
decipher_only=False,
|
|
),
|
|
critical=True,
|
|
)
|
|
.add_extension(
|
|
x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), critical=False
|
|
)
|
|
.sign(ca_key, hashes.SHA256())
|
|
)
|
|
|
|
leaf_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
leaf_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "localhost")])
|
|
leaf_cert = (
|
|
x509.CertificateBuilder()
|
|
.subject_name(leaf_name)
|
|
.issuer_name(ca_name)
|
|
.public_key(leaf_key.public_key())
|
|
.serial_number(x509.random_serial_number())
|
|
.not_valid_before(not_before)
|
|
.not_valid_after(not_after)
|
|
.add_extension(
|
|
x509.SubjectAlternativeName(
|
|
[
|
|
x509.DNSName("localhost"),
|
|
x509.IPAddress(ipaddress.ip_address("127.0.0.1")),
|
|
]
|
|
),
|
|
critical=False,
|
|
)
|
|
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
|
.add_extension(
|
|
x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
|
|
critical=False,
|
|
)
|
|
.sign(ca_key, hashes.SHA256())
|
|
)
|
|
|
|
ca_cert_path = output_dir / "ca.pem"
|
|
cert_path = output_dir / "leaf.pem"
|
|
key_path = output_dir / "leaf-key.pem"
|
|
ca_cert_path.write_bytes(ca_cert.public_bytes(serialization.Encoding.PEM))
|
|
cert_path.write_bytes(leaf_cert.public_bytes(serialization.Encoding.PEM))
|
|
key_path.write_bytes(
|
|
leaf_key.private_bytes(
|
|
serialization.Encoding.PEM,
|
|
serialization.PrivateFormat.TraditionalOpenSSL,
|
|
serialization.NoEncryption(),
|
|
)
|
|
)
|
|
return LocalTLSMaterial(ca_cert_path=ca_cert_path, cert_path=cert_path, key_path=key_path)
|