import sys
import base64
import time
from datetime import datetime
import dns.resolver
import dns.dnssec
import dns.message
import dns.query
import dns.rdatatype
import dns.rcode

# Protokoll-Mappings für Klartext-Ausgaben
ALGORITHMS = {
    1: "RSAMD5", 3: "DSA", 5: "RSASHA1", 7: "RSASHA1-NSEC3-SHA1", 
    8: "RSASHA256", 10: "RSASHA512", 13: "ECDSA P-256 mit SHA-256", 
    14: "ECDSA P-384 mit SHA-384", 15: "ED25519"
}
DIGEST_TYPES = {1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"}

ROOT_SERVERS = ['198.41.0.4', '193.0.14.129']
ROOT_TRUST_ANCHOR_TAGS = [20326, 38696]

def print_level_header(level_num, name):
    print(f"\n\033[1;35m" + "#"*80)
    print(f" LEVEL {level_num}: VERIFIKATION DER ZONE '{name}'")
    print("#"*80 + "\033[0m")

def print_sub_step(title):
    print(f"\n\033[1;34m   [➔] Sub-Schritt: {title}\033[0m")

def print_success(msg):
    print(f"   \033[1;32m✓ {msg}\033[0m")

def print_failed(msg):
    print(f"   \033[1;31m❌ {msg}\033[0m")

def print_prop(label, val):
    print(f"       • {label:<28}: \033[1m{val}\033[0m")

def format_time(timestamp):
    return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S UTC')

def get_ns_ips_for_zone(zone):
    if zone == ".": return ROOT_SERVERS
    try:
        ns_records = dns.resolver.resolve(zone, dns.rdatatype.NS)
        ips = []
        for ns in ns_records:
            try:
                a_records = dns.resolver.resolve(ns.target, dns.rdatatype.A)
                for a in a_records: ips.append(a.to_text())
            except: continue
        if ips: return ips
    except Exception as e:
        print(f"       ⚠️ Fehler bei NS-Auflösung für {zone}: {e}")
    return ['1.1.1.1']

def verify_zone_level(level_num, zone_name, rdtype_text, server_ips, parent_ds_rrset=None):
    print_level_header(level_num, zone_name)
    rdtype = dns.rdatatype.from_text(rdtype_text)
    server_ip = server_ips[0]
    
    print_prop("Zuständiger Nameserver", f"{server_ip}")
    print_prop("Anfragetyp für diese Ebene", f"{rdtype_text}")

    # -------------------------------------------------------------------------
    # SUB-SCHRITT A: REKORDS & SIGNATUREN HOLEN
    # -------------------------------------------------------------------------
    print_sub_step(f"Nutzdaten und zugehörige RRSIG-Signatur abrufen")
    request = dns.message.make_query(zone_name, rdtype, want_dnssec=True)
    
    start_time = time.time()
    response = dns.query.udp(request, server_ip, timeout=4)
    duration = (time.time() - start_time) * 1000
    
    print_prop("Netzwerk-Antwortzeit", f"{duration:.2f} ms")
    print_prop("DNS-Response Code", f"{dns.rcode.to_text(response.rcode())}")

    data_rrset, rrsig_rrset = None, None
    for rrset in (response.answer + response.authority):
        if rrset.rdtype == rdtype and rrset.name.to_text() == zone_name:
            data_rrset = rrset
        elif rrset.rdtype == dns.rdatatype.RRSIG and rrset.covers == rdtype:
            rrsig_rrset = rrset

    if not data_rrset or not rrsig_rrset:
        print_failed("Keine signierten Daten auf dieser Ebene gefunden!")
        return None

    print_success("Rohdaten erfolgreich vom Server isoliert.")
    print_prop("Gefundene Datensätze", f"{len(data_rrset)} Einträge")
    for item in data_rrset:
        print_prop(" -> Inhalt", f"{item}")

    # RRSIG Metadaten ausgeben
    print_sub_step("Analyse der digitalen Signatur (RRSIG)")
    sig = rrsig_rrset[0]
    
    # KORREKTUR: type_covered ist das richtige Attribut für den signierten Datentyp
    print_prop("Signierter Record-Typ", f"{dns.rdatatype.to_text(sig.type_covered)}")
    print_prop("Krypto-Algorithmus", f"{ALGORITHMS.get(sig.algorithm, sig.algorithm)}")
    print_prop("Verwendeter Key-Tag (ID)", f"{sig.key_tag}")
    print_prop("Signatur erstellt am", f"{format_time(sig.inception)}")
    print_prop("Signatur läuft ab am", f"{format_time(sig.expiration)}")
    sig_b64 = base64.b64encode(sig.signature).decode()
    print_prop("Signatur-Wert (Base64)", f"{sig_b64[:40]}... [{len(sig.signature)} Bytes]")

    # -------------------------------------------------------------------------
    # SUB-SCHRITT B: SCHLÜSSEL DER ZONE HOLEN
    # -------------------------------------------------------------------------
    print_sub_step(f"Öffentliche Schlüssel (DNSKEY) der Zone anfordern")
    key_request = dns.message.make_query(zone_name, dns.rdatatype.DNSKEY, want_dnssec=True)
    key_response = dns.query.udp(key_request, server_ip, timeout=4)
    
    dnskey_rrset = None
    for rrset in key_response.answer:
        if rrset.rdtype == dns.rdatatype.DNSKEY:
            dnskey_rrset = rrset

    if not dnskey_rrset:
        print_failed("Zone weigert sich, ihre DNSKEY-Schlüssel herauszugeben!")
        return None

    print_success(f"{len(dnskey_rrset)} öffentliche(r) Schlüssel erfolgreich empfangen:")
    for idx, key in enumerate(dnskey_rrset, 1):
        key_tag = dns.dnssec.key_id(key)
        is_ksk = (key.flags == 257)
        print(f"\n       [Key #{idx}] Tag: {key_tag}")
        print_prop("  Flags (Rolle)", f"{key.flags} -> {'KSK (Key Signing Key)' if is_ksk else 'ZSK (Zone Signing Key)'}")
        print_prop("  Protokoll-ID", f"{key.protocol} (Standard: 3)")
        print_prop("  Krypto-Algorithmus", f"{ALGORITHMS.get(key.algorithm, key.algorithm)}")
        key_pub_b64 = base64.b64encode(key.key).decode()
        print_prop("  Public Key (Base64)", f"{key_pub_b64[:35]}...")

    # -------------------------------------------------------------------------
    # SUB-SCHRITT C: LOKALE MATHEMATISCHE VERIFIKATION
    # -------------------------------------------------------------------------
    print_sub_step("Lokale kryptografische Verifikation")
    print_prop("Aktion", f"Berechne mathematische Signaturprüfung mit Key-Tag {sig.key_tag}")
    try:
        dns.dnssec.validate(data_rrset, rrsig_rrset, {dns.name.from_text(zone_name): dnskey_rrset})
        print_success("MATHEMATISCHER BEWEIS ERBRACHT: Signatur korrespondiert exakt mit den Daten.")
    except dns.dnssec.ValidationFailure as e:
        print_failed(f"Krypto-Prüfung fehlgeschlagen: {e}")
        return None

    # -------------------------------------------------------------------------
    # SUB-SCHRITT D: VERTRAUENSKETTE (CHAIN OF TRUST) PRÜFEN
    # -------------------------------------------------------------------------
    if zone_name == ".":
        print_sub_step("Abgleich gegen den globalen Ur-Schlüssel (Trust Anchor)")
        root_ksk_found = False
        for key in dnskey_rrset:
            if key.flags == 257:
                tag = dns.dnssec.key_id(key)
                print_prop("Geprüfter Root-KSK Tag", tag)
                if tag in ROOT_TRUST_ANCHOR_TAGS:
                    root_ksk_found = True
                    print_success(f"SENSATIONS-MATCH! Root-Schlüssel entspricht dem offiziellen ICANN Trust Anchor ({tag}).")
        if not root_ksk_found:
            print_failed(f"Root-Schlüssel stimmt mit keinem der hinterlegten Trust Anchors {ROOT_TRUST_ANCHOR_TAGS} überein!")
    else:
        if parent_ds_rrset:
            print_sub_step("Abgleich mit dem DS-Fingerabdruck des Parents")
            matched = False
            name_obj = dns.name.from_text(zone_name)
            for key in dnskey_rrset:
                if key.flags == 257:
                    local_tag = dns.dnssec.key_id(key)
                    print_prop("Analysiere KSK Tag", local_tag)
                    for ds in parent_ds_rrset:
                        print_prop(" -> Gegen Parent-DS Tag", ds.key_tag)
                        generated_ds = dns.dnssec.make_ds(name_obj, key, ds.digest_type)
                        
                        print_prop("    Lokal generierter Hash", generated_ds.digest.hex()[:40] + "...")
                        print_prop("    Vom Parent erwar. Hash", ds.digest.hex()[:40] + "...")
                        
                        if generated_ds.digest == ds.digest:
                            matched = True
                            print_success(f"MATCH! Parent-Zone bestätigt den KSK-Schlüssel {local_tag} via {DIGEST_TYPES.get(ds.digest_type)} Hash.")
            if not matched:
                print_failed("KETTEN-BRUCH: Kein lokaler KSK passt zum DS-Record des Parents!")
                return None
    return data_rrset

def fetch_ds_record_from_parent(parent_ips, child_zone_name):
    request = dns.message.make_query(child_zone_name, dns.rdatatype.DS, want_dnssec=True)
    response = dns.query.udp(request, parent_ips[0], timeout=4)
    for rrset in (response.answer + response.authority):
        if rrset.rdtype == dns.rdatatype.DS and rrset.name.to_text() == child_zone_name:
            return rrset
    return None

def main(target_domain):
    print(f"\033[1;36m================================================================================\033[0m")
    print(f"\033[1;36m   ULTRATIEFE DNSSEC-KETTENANALYSE (ROOT ➔ TLD ➔ DOMAIN)\033[0m")
    print(f"\033[1;36m================================================================================\033[0m")
    
    parts = target_domain.strip(".").split(".")
    tld_zone = parts[-1] + "."
    full_zone = target_domain + "."
    
    # LEVEL 1: ROOT ZONE
    root_ips = ROOT_SERVERS
    verify_zone_level(1, ".", "DNSKEY", root_ips)
    
    # DS für TLD holen
    print(f"\n\033[1;33m[Aktion] Fordere DS-Fingerabdruck für TLD '{tld_zone}' von der Root-Zone an...\033[0m")
    tld_ds_set = fetch_ds_record_from_parent(root_ips, tld_zone)
    if tld_ds_set:
        print_success(f"DS-Record erhalten. Typ: DS, Algorithmus: {DIGEST_TYPES.get(tld_ds_set[0].digest_type)}")
    else:
        print_failed(f"Kein DS-Record für {tld_zone} auf dem Root-Server gefunden. Kette endet hier.")
        return
    
    # LEVEL 2: TLD ZONE
    tld_ips = get_ns_ips_for_zone(tld_zone)
    verify_zone_level(2, tld_zone, "DNSKEY", tld_ips, parent_ds_rrset=tld_ds_set)
    
    # DS für Domain holen
    print(f"\n\033[1;33m[Aktion] Fordere DS-Fingerabdruck für Domain '{full_zone}' von der TLD-Zone '{tld_zone}' an...\033[0m")
    domain_ds_set = fetch_ds_record_from_parent(tld_ips, full_zone)
    if domain_ds_set:
        print_success(f"DS-Record erhalten. Typ: DS, Algorithmus: {DIGEST_TYPES.get(domain_ds_set[0].digest_type)}")
    else:
        print_failed(f"Kein DS-Record für {full_zone} auf dem TLD-Server gefunden. Die Zieldomain ist nicht DNSSEC-signiert.")
        return

    # LEVEL 3: TARGET DOMAIN
    domain_ips = get_ns_ips_for_zone(full_zone)
    verify_zone_level(3, full_zone, "A", domain_ips, parent_ds_rrset=domain_ds_set)
    
    print(f"\n\033[1;32m================================================================================\033[0m")
    print(f"\033[1;32m FINAL-FAZIT: DIE INTEGRITÄTSKETTE IST LÜCKENLOS VERIFIZIERT!\033[0m")
    print(f" Jedes mathematische Glied hält der Überprüfung vom ICANN Trust Anchor stand.")
    print(f" Manipulationen auf dem Transportweg sind mathematisch ausgeschlossen.")
    print(f"\033[1;32m================================================================================\033[0m")

if __name__ == "__main__":
    domain = "internetsociety.org"
    if len(sys.argv) > 1:
        domain = sys.argv[1]
    main(domain)