"""Signed-content verification example with synthetic fixtures.

Requires signxml, lxml and cryptography. This is not a UIDAI integration or an
approved certificate profile. Production eligibility, source certificates,
certificate lifecycle, freshness, account binding and retention need review.
"""

from lxml import etree
from signxml import XMLVerifier


def verified_identity(xml_bytes, trusted_certificate):
    if len(xml_bytes) > 1_048_576:
        raise ValueError("XML exceeds the example size limit")
    if b"<!DOCTYPE" in xml_bytes.upper():
        raise ValueError("Document type declarations are outside this example profile")
    parser = etree.XMLParser(resolve_entities=False, no_network=True)
    document = etree.fromstring(xml_bytes, parser=parser)
    result = XMLVerifier().verify(document, x509_cert=trusted_certificate)
    signed = result.signed_xml
    if signed.tag != "OfflinePaperlessKyc":
        raise ValueError("Unexpected signed document")
    identities = signed.findall("UidData/Poi")
    if len(identities) != 1 or not identities[0].get("name"):
        raise ValueError("Verified identity is incomplete or ambiguous")
    return {"name": identities[0].get("name")}


def self_test():
    from datetime import UTC, datetime, timedelta

    from cryptography import x509
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.x509.oid import NameOID
    from signxml import XMLSigner
    from signxml.exceptions import InvalidInput, InvalidSignature

    def fixture_certificate(key):
        name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Synthetic local fixture")])
        now = datetime.now(UTC)
        return (
            x509.CertificateBuilder()
            .subject_name(name)
            .issuer_name(name)
            .public_key(key.public_key())
            .serial_number(x509.random_serial_number())
            .not_valid_before(now - timedelta(minutes=1))
            .not_valid_after(now + timedelta(days=1))
            .sign(key, hashes.SHA256())
            .public_bytes(serialization.Encoding.PEM)
        )

    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    certificate = fixture_certificate(key)
    other_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    other_certificate = fixture_certificate(other_key)
    document = etree.fromstring(
        b'<OfflinePaperlessKyc><UidData><Poi name="Synthetic Person"/>'
        b'</UidData></OfflinePaperlessKyc>'
    )
    signed = XMLSigner().sign(document, key=key, cert=certificate)
    xml_bytes = etree.tostring(signed)
    assert verified_identity(xml_bytes, certificate) == {"name": "Synthetic Person"}
    bad_inputs = [
        (xml_bytes.replace(b"Synthetic Person", b"Changed Person"), certificate),
        (xml_bytes, other_certificate),
        (etree.tostring(document), certificate),
        (b'<!DOCTYPE sample><OfflinePaperlessKyc/>', certificate),
    ]
    for body, cert in bad_inputs:
        try:
            verified_identity(body, cert)
        except (InvalidInput, InvalidSignature, ValueError):
            continue
        raise AssertionError("Accepted an invalid synthetic XML case")
    print("Passed: signed content, tampering, wrong certificate, unsigned input, DTD rejection")


if __name__ == "__main__":
    self_test()
