#!/usr/bin/env python3 """Generate a small self-contained RPKI repository for installer tests. The generator uses Python cryptography and the OpenSSL CLI for CMS signing. It does not invoke Barry, Rapport, or a public RIR service. """ from __future__ import annotations import argparse import base64 import hashlib import ipaddress import shutil import subprocess import tempfile from datetime import datetime, timedelta, timezone from pathlib import Path from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat from cryptography.x509 import AccessDescription, AuthorityInformationAccess, DistributionPoint, UniformResourceIdentifier from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID, ObjectIdentifier RRDP_HOST = "host.docker.internal" RRDP_PORT = 18443 RSYNC_PORT = 1873 TAL_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/ta.cer" TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer" RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml" ROOT_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/root/" ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft" CHILD_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/child/" CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft" OID_SIA = ObjectIdentifier("1.3.6.1.5.5.7.1.11") OID_RESOURCES_IP = ObjectIdentifier("1.3.6.1.5.5.7.1.7") OID_RESOURCES_AS = ObjectIdentifier("1.3.6.1.5.5.7.1.8") OID_POLICY_IP_AS = ObjectIdentifier("1.3.6.1.5.5.7.14.2") OID_ROA_ECONTENT = "1.2.840.113549.1.9.16.1.24" OID_MANIFEST_ECONTENT = "1.2.840.113549.1.9.16.1.26" OID_SHA256 = "2.16.840.1.101.3.4.2.1" SIA_CA_REPOSITORY = "1.3.6.1.5.5.7.48.5" SIA_RPKI_MANIFEST = "1.3.6.1.5.5.7.48.10" SIA_SIGNED_OBJECT = "1.3.6.1.5.5.7.48.11" SIA_RPKI_NOTIFY = "1.3.6.1.5.5.7.48.13" UTC = timezone.utc VALID_FROM = datetime(2026, 1, 1, tzinfo=UTC) VALID_TO = datetime(2027, 12, 31, 23, 59, 59, tzinfo=UTC) CHILD_PREFIX = ipaddress.ip_network("203.0.113.0/24") SECOND_PREFIX = ipaddress.ip_network("203.0.113.128/25") OUTSIDE_PREFIX = ipaddress.ip_network("203.0.114.0/24") TEST_ASN = 64496 SESSION_ID = "11111111-2222-4333-8444-555555555555" def tlv(tag: int, value: bytes) -> bytes: length = len(value) if length < 128: encoded_length = bytes([length]) else: raw = length.to_bytes((length.bit_length() + 7) // 8, "big") encoded_length = bytes([0x80 | len(raw)]) + raw return bytes([tag]) + encoded_length + value def seq(*values: bytes) -> bytes: return tlv(0x30, b"".join(values)) def integer(value: int) -> bytes: raw = value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big") if raw[0] & 0x80: raw = b"\x00" + raw return tlv(0x02, raw) def oid(oid_value: str) -> bytes: parts = [int(part) for part in oid_value.split(".")] encoded = bytearray([40 * parts[0] + parts[1]]) for part in parts[2:]: chunks = [part & 0x7F] part >>= 7 while part: chunks.append(0x80 | (part & 0x7F)) part >>= 7 encoded.extend(reversed(chunks)) return tlv(0x06, bytes(encoded)) def octet(value: bytes) -> bytes: return tlv(0x04, value) def uri(value: str) -> bytes: return tlv(0x86, value.encode("ascii")) def bit_string(value: bytes, unused: int = 0) -> bytes: return tlv(0x03, bytes([unused]) + value) def generalized_time(value: datetime) -> bytes: return tlv(0x18, value.astimezone(UTC).strftime("%Y%m%d%H%M%SZ").encode("ascii")) def context_zero(value: bytes) -> bytes: return tlv(0xA0, value) def ip_choice(networks: list[ipaddress._BaseNetwork]) -> bytes: entries = [] for network in networks: length = (network.prefixlen + 7) // 8 raw = network.network_address.packed[:length] entries.append(bit_string(raw, length * 8 - network.prefixlen)) return seq(*entries) def ip_resources(networks: list[ipaddress._BaseNetwork]) -> bytes: families: dict[int, list[ipaddress._BaseNetwork]] = {1: [], 2: []} for network in networks: families[1 if network.version == 4 else 2].append(network) return seq( *( seq(octet(afi.to_bytes(2, "big")), ip_choice(items)) for afi, items in families.items() if items ) ) def ip_inherit() -> bytes: return seq( seq(octet(b"\x00\x01"), tlv(0x05, b"")), seq(octet(b"\x00\x02"), tlv(0x05, b"")), ) def as_resources(asns: list[int] | None) -> bytes: if asns is None: return seq(context_zero(tlv(0x05, b""))) return seq(context_zero(seq(*(integer(asn) for asn in asns)))) def sia(entries: list[tuple[str, str]]) -> bytes: return seq(*(seq(oid(method), uri(location)) for method, location in entries)) def write(path: Path, data: bytes, mode: int | None = None) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data) if mode is not None: path.chmod(mode) def pem_cert(cert: x509.Certificate) -> bytes: return cert.public_bytes(Encoding.PEM) def pem_key(key: rsa.RSAPrivateKey) -> bytes: return key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()) def write_key(path: Path, key: rsa.RSAPrivateKey) -> None: write(path, pem_key(key), 0o600) def sign_cert( subject: x509.Name, issuer: x509.Name, public_key, signing_key: rsa.RSAPrivateKey, serial: int, is_ca: bool, ski_key: rsa.RSAPrivateKey, aki_key: rsa.RSAPrivateKey | None = None, ip_ext: bytes | None = None, as_ext: bytes | None = None, ca_repository: str | None = None, manifest_uri: str | None = None, notify_uri: str | None = None, signed_object_uri: str | None = None, crl_uri: str | None = None, ca_issuer_uri: str | None = None, not_before: datetime = VALID_FROM, not_after: datetime = VALID_TO, ) -> x509.Certificate: builder = ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(public_key) .serial_number(serial) .not_valid_before(not_before) .not_valid_after(not_after) ) if is_ca: builder = builder.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) if is_ca: usage = x509.KeyUsage(False, False, False, False, False, True, True, False, False) else: usage = x509.KeyUsage(True, False, False, False, False, False, False, False, False) builder = builder.add_extension(usage, critical=True) builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(ski_key.public_key()), critical=False) if aki_key is not None: builder = builder.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(aki_key.public_key()), critical=False) builder = builder.add_extension( x509.CertificatePolicies([x509.PolicyInformation(OID_POLICY_IP_AS, None)]), critical=True, ) if crl_uri is not None: builder = builder.add_extension( x509.CRLDistributionPoints( [DistributionPoint([UniformResourceIdentifier(crl_uri)], None, None, None)] ), critical=False, ) if ca_issuer_uri is not None: builder = builder.add_extension( AuthorityInformationAccess( [AccessDescription(AuthorityInformationAccessOID.CA_ISSUERS, UniformResourceIdentifier(ca_issuer_uri))] ), critical=False, ) sia_entries: list[tuple[str, str]] = [] if ca_repository is not None: sia_entries.append((SIA_CA_REPOSITORY, ca_repository)) if manifest_uri is not None: sia_entries.append((SIA_RPKI_MANIFEST, manifest_uri)) if notify_uri is not None: sia_entries.append((SIA_RPKI_NOTIFY, notify_uri)) if signed_object_uri is not None: sia_entries.append((SIA_SIGNED_OBJECT, signed_object_uri)) if sia_entries: builder = builder.add_extension(x509.UnrecognizedExtension(OID_SIA, sia(sia_entries)), critical=False) if ip_ext is not None: builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_IP, ip_ext), critical=True) if as_ext is not None: builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_AS, as_ext), critical=True) return builder.sign(signing_key, hashes.SHA256()) def make_crl(subject: x509.Name, key: rsa.RSAPrivateKey, number: int) -> x509.CertificateRevocationList: return ( x509.CertificateRevocationListBuilder() .issuer_name(subject) .last_update(VALID_FROM + timedelta(days=30)) .next_update(VALID_TO - timedelta(days=30)) .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()), critical=False) .add_extension(x509.CRLNumber(number), critical=False) .sign(key, hashes.SHA256()) ) def roa_content(prefix: ipaddress._BaseNetwork, max_length: int) -> bytes: length = (prefix.prefixlen + 7) // 8 raw = prefix.network_address.packed[:length] address = seq(bit_string(raw, length * 8 - prefix.prefixlen), integer(max_length)) family = seq(octet((1 if prefix.version == 4 else 2).to_bytes(2, "big")), seq(address)) return seq(integer(TEST_ASN), seq(family)) def manifest_content(number: int, files: list[tuple[str, bytes]]) -> bytes: entries = [ seq(tlv(0x16, name.encode("ascii")), bit_string(hashlib.sha256(content).digest())) for name, content in files ] return seq( integer(number), generalized_time(VALID_FROM + timedelta(days=30)), generalized_time(VALID_TO - timedelta(days=30)), oid(OID_SHA256), seq(*entries), ) def write_cms(content: bytes, content_type: str, signer_cert: x509.Certificate, signer_key: rsa.RSAPrivateKey, output: Path, work: Path) -> None: content_path = work / f"{output.name}.content.der" signer_path = work / f"{output.name}.signer.pem" key_path = work / f"{output.name}.key.pem" write(content_path, content) write(signer_path, pem_cert(signer_cert)) write_key(key_path, signer_key) subprocess.run( [ "openssl", "cms", "-sign", "-binary", "-in", str(content_path), "-signer", str(signer_path), "-inkey", str(key_path), "-outform", "DER", "-nodetach", "-nosmimecap", "-keyid", "-md", "sha256", "-econtent_type", content_type, "-out", str(output), ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, ) def make_roa( name: str, prefix, max_length: int, not_before: datetime, not_after: datetime, child_name, child_key, common: Path, work: Path, certificate_prefix=None, ) -> None: key = rsa.generate_private_key(public_exponent=65537, key_size=2048) cert = sign_cert( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"#142 EE {name}")]), child_name, key.public_key(), child_key, x509.random_serial_number(), False, key, child_key, ip_ext=ip_resources([certificate_prefix or prefix]), signed_object_uri=f"{CHILD_REPO_URI}{name}", crl_uri=f"{CHILD_REPO_URI}child.crl", ca_issuer_uri=f"{CHILD_REPO_URI}child.cer", not_before=not_before, not_after=not_after, ) write_cms(roa_content(prefix, max_length), OID_ROA_ECONTENT, cert, key, common / name, work) def make_manifest(name: str, number: int, files: list[tuple[str, bytes]], child_name, child_key, common: Path, work: Path) -> bytes: key = rsa.generate_private_key(public_exponent=65537, key_size=2048) cert = sign_cert( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"#142 Manifest EE {name}")]), child_name, key.public_key(), child_key, x509.random_serial_number(), False, key, child_key, ip_ext=ip_inherit(), as_ext=as_resources(None), signed_object_uri=CHILD_MFT_URI if name != "root" else ROOT_MFT_URI, crl_uri=f"{CHILD_REPO_URI}child.crl" if name != "root" else f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{CHILD_REPO_URI}child.cer" if name != "root" else f"{ROOT_REPO_URI}ta.cer", ) output = common / f"{name}.mft" write_cms(manifest_content(number, files), OID_MANIFEST_ECONTENT, cert, key, output, work) return output.read_bytes() def publish(uri: str, path: Path) -> str: encoded = base64.b64encode(path.read_bytes()).decode("ascii") return f' {encoded}\n' def snapshot(case_root: Path, names: list[str], serial: int, extra_root_names: list[str] | None = None) -> bytes: repository = case_root / "repository" objects = [ (f"{ROOT_REPO_URI}root.crl", repository / "root/root.crl"), (f"{ROOT_REPO_URI}child.cer", repository / "root/child.cer"), *[ (f"{ROOT_REPO_URI}{name}", repository / f"root/{name}") for name in extra_root_names or [] ], (f"{CHILD_REPO_URI}child.crl", repository / "child/child.crl"), *[(f"{CHILD_REPO_URI}{name}", repository / f"child/{name}") for name in names], (ROOT_MFT_URI, repository / "root/root.mft"), (CHILD_MFT_URI, repository / "child/child.mft"), ] body = "".join(publish(uri, path) for uri, path in objects) return ( f'\n' f"{body}\n" ).encode() def notification(serial: int, snap: bytes, delta: bytes | None) -> bytes: lines = [ f'', f' ', ] if delta is not None: lines.append( f' ' ) return ("\n".join(lines) + "\n\n").encode() def corrupt_first_hash(xml: bytes) -> bytes: marker = b'hash="' start = xml.index(marker) + len(marker) end = xml.index(b'"', start) value = bytearray(xml[start:end]) value[0] = ord("0") if value[0] != ord("0") else ord("1") return xml[:start] + bytes(value) + xml[end:] def delta(add_name: str, add_path: Path, new_mft: Path) -> bytes: body = publish(CHILD_MFT_URI, new_mft) body += publish(f"{CHILD_REPO_URI}{add_name}", add_path) return f'\n{body}\n'.encode() def build_case( output: Path, common: Path, tal: bytes, ta: bytes, name: str, number: int, names: list[str], child_mft: bytes, change: bytes | None = None, root_mft: bytes | None = None, extra_root_objects: list[tuple[str, bytes]] | None = None, notification_hash_mismatch: bool = False, ) -> None: case_root = output / "cases" / name repo = case_root / "repository" (repo / "root").mkdir(parents=True, exist_ok=True) (repo / "child").mkdir(parents=True, exist_ok=True) shutil.copy2(common / "ta.cer", repo / "ta.cer") for item in ("root.crl", "child.cer"): shutil.copy2(common / item, repo / "root" / item) (repo / "root/root.mft").write_bytes(root_mft if root_mft is not None else (common / "root.mft").read_bytes()) for item, content in extra_root_objects or []: (repo / "root" / item).write_bytes(content) for item in ( "child.crl", "valid.roa", "valid2.roa", "expired.roa", "max-length.roa", "max-length-invalid.roa", "roa-prefix-outside.roa", "out-of-resource.roa", "bad-format.roa", ): if (common / item).exists(): shutil.copy2(common / item, repo / "child" / item) (repo / "child/child.mft").write_bytes(child_mft) snap = snapshot(case_root, names, number, [item for item, _ in extra_root_objects or []]) (case_root / "http/rrdp").mkdir(parents=True, exist_ok=True) (case_root / "http/tal").mkdir(parents=True, exist_ok=True) (case_root / "http/ta").mkdir(parents=True, exist_ok=True) (case_root / "http/rrdp/snapshot.xml").write_bytes(snap) notification_xml = notification(number, snap, change) if notification_hash_mismatch: notification_xml = corrupt_first_hash(notification_xml) (case_root / "http/rrdp/notification.xml").write_bytes(notification_xml) if change is not None: (case_root / "http/rrdp/delta-2.xml").write_bytes(change) (case_root / "http/tal/custom.tal").write_bytes(tal) (case_root / "http/ta/custom-ta.cer").write_bytes(ta) (case_root / "CASE-MANIFEST.txt").write_text( f"case={name}\nserial={number}\nobjects={','.join(names)}\nsnapshot_sha256={hashlib.sha256(snap).hexdigest()}\n", encoding="utf-8", ) def make_https_certificates(output: Path) -> None: ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 fixed RRDP test 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(VALID_FROM).not_valid_after(VALID_TO) .add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True) .add_extension(x509.KeyUsage(False, False, False, False, False, True, True, False, False), critical=True) .sign(ca_key, hashes.SHA256()) ) server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, RRDP_HOST)]) try: server_host = x509.IPAddress(ipaddress.ip_address(RRDP_HOST)) except ValueError: server_host = x509.DNSName(RRDP_HOST) san_entries = [server_host] if RRDP_HOST != "localhost": san_entries.append(x509.DNSName("localhost")) server_cert = ( x509.CertificateBuilder() .subject_name(server_name).issuer_name(ca_cert.subject).public_key(server_key.public_key()) .serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO) .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) .add_extension(x509.KeyUsage(True, False, False, False, False, False, False, False, False), critical=True) .add_extension(x509.SubjectAlternativeName(san_entries), critical=False) .sign(ca_key, hashes.SHA256()) ) write(output / "certs/rrdp-ca.pem", pem_cert(ca_cert)) write_key(output / "certs/rrdp-ca.key", ca_key) write(output / "certs/rrdp-server.pem", pem_cert(server_cert)) write_key(output / "certs/rrdp-server.key", server_key) def main() -> None: global RRDP_HOST, TAL_URI, TAL_HTTPS_URI, RRDP_URI global ROOT_REPO_URI, ROOT_MFT_URI, CHILD_REPO_URI, CHILD_MFT_URI parser = argparse.ArgumentParser() parser.add_argument("--output", type=Path, required=True) parser.add_argument( "--host", default=RRDP_HOST, help="host embedded in TAL, certificate SIA and RRDP data (default: host.docker.internal)", ) args = parser.parse_args() RRDP_HOST = args.host TAL_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/ta.cer" TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer" RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml" ROOT_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/root/" ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft" CHILD_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/child/" CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft" output = args.output.resolve() if output.exists(): shutil.rmtree(output) for directory in ("tal", "ta", "certs", "services", "keys"): (output / directory).mkdir(parents=True) root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) child_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) root_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Root")]) child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Child")]) root_cert = sign_cert( root_name, root_name, root_key.public_key(), root_key, 0x14200001, True, root_key, ip_ext=ip_resources([CHILD_PREFIX]), as_ext=as_resources([TEST_ASN]), ca_repository=ROOT_REPO_URI, manifest_uri=ROOT_MFT_URI, notify_uri=RRDP_URI, ) child_cert = sign_cert( child_name, root_name, child_key.public_key(), root_key, 0x14200002, True, child_key, root_key, ip_ext=ip_resources([CHILD_PREFIX]), as_ext=as_resources([TEST_ASN]), ca_repository=CHILD_REPO_URI, manifest_uri=CHILD_MFT_URI, notify_uri=RRDP_URI, crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer", ) bad_child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Overclaiming Child")]) bad_child_repo_uri = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/bad-child/" bad_child_cert = sign_cert( bad_child_name, root_name, child_key.public_key(), root_key, 0x14200003, True, child_key, root_key, ip_ext=ip_resources([OUTSIDE_PREFIX]), as_ext=as_resources([TEST_ASN]), ca_repository=bad_child_repo_uri, manifest_uri=f"{bad_child_repo_uri}bad-child.mft", notify_uri=RRDP_URI, crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer", ) root_crl = make_crl(root_name, root_key, 1) child_crl = make_crl(child_name, child_key, 1) tal = ( f"{TAL_URI}\n{TAL_HTTPS_URI}\n\n" + base64.b64encode(root_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)).decode() + "\n" ).encode() write(output / "tal/custom.tal", tal) write(output / "ta/custom-ta.cer", root_cert.public_bytes(Encoding.DER)) with tempfile.TemporaryDirectory(prefix="custom-rpki-cms-") as temp: work = Path(temp) common = output / ".common" common.mkdir() write(common / "ta.cer", root_cert.public_bytes(Encoding.DER)) write(common / "child.cer", child_cert.public_bytes(Encoding.DER)) write(common / "bad-child.cer", bad_child_cert.public_bytes(Encoding.DER)) write(common / "root.crl", root_crl.public_bytes(Encoding.DER)) write(common / "child.crl", child_crl.public_bytes(Encoding.DER)) for name, prefix, max_length, before, after, certificate_prefix in ( ("valid.roa", CHILD_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), ("valid2.roa", SECOND_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), ("expired.roa", CHILD_PREFIX, 24, VALID_FROM - timedelta(days=30), VALID_FROM - timedelta(days=1), None), ("max-length.roa", CHILD_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), ("max-length-invalid.roa", CHILD_PREFIX, 33, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), ("roa-prefix-outside.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), CHILD_PREFIX), ("out-of-resource.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), ): make_roa( name, prefix, max_length, before, after, child_name, child_key, common, work, certificate_prefix=certificate_prefix, ) bad = bytearray((common / "valid.roa").read_bytes()) bad[-1] ^= 1 write(common / "bad-format.roa", bytes(bad)) root_mft = make_manifest( "root", 1, [ ("root.crl", (common / "root.crl").read_bytes()), ("child.cer", (common / "child.cer").read_bytes()), ], root_name, root_key, common, work, ) write(common / "root.mft", root_mft) bad_root_mft = make_manifest( "root", 1, [ ("root.crl", (common / "root.crl").read_bytes()), ("child.cer", (common / "child.cer").read_bytes()), ("bad-child.cer", (common / "bad-child.cer").read_bytes()), ], root_name, root_key, common, work, ) write(common / "root.mft", root_mft) case_specs = ( ("baseline-v1", 1, ["child.crl", "valid.roa"]), ("sync-hash-mismatch", 1, ["child.crl", "valid.roa"]), ("baseline-v2", 2, ["child.crl", "valid.roa", "valid2.roa"]), ("validation-expired", 1, ["child.crl", "expired.roa"]), ("validation-max-length", 1, ["child.crl", "max-length.roa"]), ("validation-max-length-invalid", 1, ["child.crl", "max-length-invalid.roa"]), ("validation-roa-prefix-outside", 1, ["child.crl", "roa-prefix-outside.roa"]), ("validation-out-of-resource", 1, ["child.crl", "out-of-resource.roa"]), ("validation-ca-over-resource", 1, ["child.crl", "valid.roa"]), ("validation-nonstandard", 1, ["child.crl", "bad-format.roa"]), ) child_mfts = {} for name, number, names in case_specs: child_mfts[name] = make_manifest( name, number, [(item, (common / item).read_bytes()) for item in names], child_name, child_key, common, work, ) for name, number, names in case_specs: change = None if name == "baseline-v2": change = delta("valid2.roa", common / "valid2.roa", common / "baseline-v2.mft") build_case( output, common, tal, root_cert.public_bytes(Encoding.DER), name, number, names, child_mfts[name], change, root_mft=bad_root_mft if name == "validation-ca-over-resource" else None, extra_root_objects=[("bad-child.cer", (common / "bad-child.cer").read_bytes())] if name == "validation-ca-over-resource" else None, notification_hash_mismatch=name == "sync-hash-mismatch", ) write_key(output / "keys/root.key", root_key) write_key(output / "keys/child.key", child_key) make_https_certificates(output) shutil.rmtree(output / ".common") (output / "FIXTURE-MANIFEST.txt").write_text( "fixture_schema_version=1\nfixture_kind=custom-tal-fixed-local-rpki\n" f"tal_uri={TAL_URI}\nrrdp_uri={RRDP_URI}\nrsync_module=custom\n" f"rrdp_port={RRDP_PORT}\nrsync_port={RSYNC_PORT}\n" "cases=baseline-v1,sync-hash-mismatch,baseline-v2,validation-expired,validation-max-length,validation-max-length-invalid," "validation-roa-prefix-outside,validation-out-of-resource,validation-ca-over-resource,validation-nonstandard\n", encoding="utf-8", ) print(f"generated custom fixture: {output}") if __name__ == "__main__": main()