Terugblik op de algoritme-rollover voor .nl

We stapten over van algoritme 8 naar 13

Concept van een veilige digitale ketting

In juli 2023 hebben we het algoritme waarmee de records in de .nl-zone worden ondertekend aangepast naar ECDSA. In een vorige blog legden we uit waarom we dat deden. Nu doen we verslag van het proces van de algoritmetransitie. Voor ons was dit een zeer belangrijke wijziging met een hoog afbreukrisico als er dingen mis zouden gaan. Daarom zijn we zeer behoedzaam te werk gegaan en hebben we ruim de tijd genomen om alles voor te bereiden en te testen. We delen graag onze bevindingen.

Alles blijft veilig

Een heel belangrijk uitgangspunt was dat de veiligheid van ondertekende zones ook tijdens de algoritmetransitie geen moment in het geding mocht zijn. Met andere woorden: DNSSEC-validerende resolvers moesten te allen tijde de volledige DNSSEC-chain kunnen valideren en als veilig blijven zien. Dit hebben we over de gehele periode van de rollover gemeten, door te kijken naar de zogenaamde trust chain. Hiervoor hebben we gebruik gemaakt van RIPE Atlas probes. Zo konden we honderden validerende resolvers over de hele wereld continu testen.

https://images.ctfassets.net/yj8364fopk6s/22IKMSGJROMCy9rLF4MTwp/ef6633f52f2c859bd00e72ce08d85768/Trustchain_IPv6.png
https://images.ctfassets.net/yj8364fopk6s/2GDxEwLceTofheVfI89LOu/e1f827632d2d50b8fa9d4732d7ff6887/Trustchain_IPv4.png

Figuur 1: Het percentage resolvers dat de .nl-zone als veilig ziet (groene lijn) voor IPv6 en IPv4.

De bovenstaande figuren laten zien dat dit goed gegaan is, omdat het percentage van veilige resolvers stabiel bleef. Op deze manier konden we ook zelf de veiligheid van de trust chain bewaken tijdens de hele transitie.

Eigen root zone voor test en acceptatie

Om het hele proces goed in de vingers te krijgen en foutloos een rollover in productie te kunnen doen, hebben we een aantal keer een rollover op onze test- en acceptatie-omgeving gedaan. Een belangrijk verschil met de situatie in productie is echter dat deze volledig los staan van de publieke rootnameservers. Om toch te kunnen testen of er geen problemen ontstaan tijdens de rollover, hebben we lokaal een eigen root zone en een DNSSEC-validerende resolver gemaakt, die in deze omgeving pasten. Dit hield in dat we de eigen root zone hebben voorzien van een eigen KSK en ZSK. Door middel van een trust anchor lieten we de resolver weten dat het pad veilig was.

  • Tijdens het werk op de test- en acceptatie-omgeving lieten we een script meedraaien. Hiermee konden we met behulp van enkele commando’s verschillende zaken bewaken:

  • Geeft een DNS-query op .nl naar een validerende resolver een antwoord met AD-bit?

    • dig @resolver soa nl
    • dig @resolver dnskey nl
  • Leg een audit trail vast van alle configuratiewijzigingen

    • cat /var/lib/opendnssec/enforcer/zones.xml
    • cat /var/lib/opendnssec/signconf/nl.xml
    • cat /etc/opendnssec/kasp.xml
  • Wat is de status van de zone op de signer?

    • dig +short +norec +dnssec @signer dnskey nl
    • dig +short +norec +dnssec @signer soa nl
    • dig +short +norec +dnssec @signer ns nl
    • dig +short +norec +dnssec @signer a nonexistant.nl
  • Wat is de status van de KSK’s en ZSK’s in OpenDNSSEC?

    • ods-hsmutil list
    • ods-enforcer rollover list –z nl
    • ods-enforcer key list –v –z nl
    • ods-enforcer key list –d –z nl
    • ods-enforcer key export –-ds –z nl

Door deze zaken te loggen kon er makkelijk gekeken worden wat de status van de omgeving op een bepaalde tijd was. Aan de hand van deze informatie konden we een betrouwbare tijdlijn maken voor de rollover in de productieomgeving. Het veelvuldig uitvoeren van ods-enforcer-commando’s bracht overigens nog een fout in OpenDNSSEC aan het licht. Deze fout is in versie 2.1.13 opgelost.

OpenDNSSEC-policy’s

Om op onze test- en acceptatie-omgeving een rollover sneller te kunnen doorlopen hebben we een aparte OpenDNSSEC-policy gebruikt. De kasp.xml die we tijdens het testen hebben gebruikt vind je onderaan deze blogpost.

De belangrijkste veranderingen zijn hieronder beschreven.

Keys – RetireSafety = PT360S Keys – PublishSafety = PT360S

Er zijn geen resolvers die cachen in onze omgeving, waardoor we de RetireSafety en PublishSafety erg kort maken.

Keys – Purge = P1D

Dit deden we om snel te kunnen zien dat de oude sleutels goed uit de HSM verwijderd worden. Normaal is dit een veel hogere waarde om eventueel een rollback te kunnen doen.

Keys – ZSK – Lifetime = P5D

Hiermee zorgen we dat er een ZSK-rollover plaatst vindt vlak na de algoritme-rollover. Zo weten we ook dat een rollover met ECDSA goed verloopt.

Zone – PropagationDelay = PT120S Parent – PropagationDelay = PT60S Parent – DS – TTL = PT60S Parent – SOA – TTL = PT600S Parent – SOA – Minimum = PT600S

Omdat we onze eigen rootserver hebben, kunnen we deze waarden erg kort maken.

Activeren ECDSA in productie

Nadat de nieuwe policy actief gemaakt was, ging OpenDNSSEC aan het werk om het nieuwe algoritme actief te maken. Hierbij werden de ECDSA KSK en ZSK als DNSKEY naast het RSA-sleutelmateriaal toegevoegd in de zone samen met RRSIGs op basis van de ECDSA-sleutels. Hieronder zie je hoe de nieuwe DNSKEY-sleutels door resolvers in de wereld gezien werden. Binnen 2 uur hadden alle resolvers van onze meting de nieuwe sleutels in hun cache. Dit is 2 keer langer dan de TTL van de DNSKEY-recordset en laat zien dat het belangrijk is om niet te snel de stappen van de rollover te doorlopen. Het is beter de resolvers wat extra tijd te geven om de nieuwe sleutels en handtekeningen op te pakken. OpenDNSSEC doet dit al automatisch.

In verschillende grafieken staan er getallen in de legenda. Deze getallen zijn de keytags voor de verschillende sleutels.

  • 62920 = RSA ZSK

  • 34112 = RSA KSK

  • 10212 = ECDSA ZSK

  • 17153 = ECDSA KSK

https://images.ctfassets.net/yj8364fopk6s/5gWdTHHqrF7WS9tSIB3cyP/4ba5f6e1f92d82d9c8f3ce49e50c955d/DNSKEYS_Keys_seen_by_resolver_IPv6.png
https://images.ctfassets.net/yj8364fopk6s/5DrILvcKk9vItzoNPHXZkT/2f49f4cdb0075e599aa3ebe4d595dd6c/DNSKEYS_Keys_seen_by_resolver_IPv4.png

Figuur 2: De DNSKEY-records die de resolvers van de RIPE Atlas Probes zien. Metingen draaien elk uur. De oranje lijn laat de ECDSA KSK-zichtbaarheid zien die oploopt.

IANA aan zet

Om de nieuwe sleutels actief te krijgen in de trustchain, moest IANA een DS-record toevoegen aan de root zone. Het moment waarop dit gebeurd is door IANA zie je aan de blauwe lijn in de onderstaande grafieken. Ook hier deden sommige resolvers er langer over dan je op basis van de TTL van de DS-recordset zou verwachten.

https://images.ctfassets.net/yj8364fopk6s/3NjHF4VBSvrfPovccF4hPK/1efc757133f3d6919493e284ce04d821/DS_Keys_seen_by_resolver_IPv6.png
https://images.ctfassets.net/yj8364fopk6s/14SqHSPZoi6bRtKHA9DC2w/3e26d042dee10b879f4f6ad4809d5f4c/DS_Keys_seen_by_resolver_IPv4.png

Figuur 3: De DS-records die de resolvers van de RIPE Atlas Probes zien. Metingen draaien 1 keer per dag.

Toen het DS-record in de root beschikbaar was, konden we OpenDNSSEC met het ‘ods-enforcer key ds-seen --zone=nl --keytag=17153'-commando laten weten dat het verder kon gaan met de rollover. Na enige tijd konden we de procedure voor het verwijderen van het RSA DS-record uit de root zone starten. In deze procedure voert IANA diverse controles uit. Uiteindelijk werd een nieuwe root zone gepubliceerd met alleen nog maar het ECDSA DS-record. Dit werd daarna ook opgepikt door de resolvers in de wereld, wat goed te zien is aan de oranje lijn in de bovenstaande grafieken.

Nadat we er zeker van waren dat de RSA-sleutels niet meer gebruikt werden ergens in de wereld, hebben we met behulp van het commando ‘ods-enforcer key ds-gone --zone=nl --keytag=34112’ aan OpenDNSSEC doorgegeven dat de oude sleutels niet meer gebruikt hoefden te worden. Deze wijziging veroorzaakte een groot verschil tussen 2 opeenvolgende zones, met als gevolg dat het even duurde voordat de nieuwe zone op alle nameservers beschikbaar was. Onderstaand diagram laat een moment zien waarop op nog niet alle nameservers de nieuwe zone beschikbaar was.

https://dnsviz.net/d/nl/ZLt6GQ/dnssec/

Figuur4: Visuele weergave van het moment waarop op nog niet alle nameservers de nieuwe zone beschikbaar is.

In dit figuur zie je een aantal rode uitroeptekens die veroorzaakt werden door het traag verspreiden van de zone. Na een paar uur was dit helemaal weer goed.

https://dnsviz.net/d/nl/ZLuFjA/dnssec/

Figuur 5: Het eindresultaat waarbij op alle nameservers de nieuwe zone beschikbaar is.

Samenvattend

De algoritme rollover is voor de meeste mensen ongemerkt verlopen en dat is precies zoals we dat willen. Wij kijken dus terug op een geslaagde rollover naar algoritme 13, waarmee .nl-zone weer voldoet aan de laatste aanbevelingen op dit gebied. Naast .nl zijn ook .amsterdam, .aw en .politie overgegaan naar algoritme 13. We verwachten dat de volgende rollover een reguliere KSK-rollover zal zijn. Op het moment dat quantumcryptografie voor DNSSEC beschikbaar komt, zullen we weer een algoritme-rollover moeten doen.

Vanuit de DNS-gemeenschap kregen we de vraag of we niet eerst alleen de RRSIGs hadden willen publiceren en daarna pas de DNSKEY RRset, zoals beschreven in RFC 6781. Op basis van onze metingen hebben we geen hinder ondervonden van deze informational RFC. Dit is in overeenstemming met wat in RFC 6840 staat.

De kasp.xml die we tijdens het testen hebben gebruikt.

<?xml version="1.0" encoding="UTF-8"?>
<KASP>
        <Policy name="default">
            <Description>SIDN default (nl)</Description>
            <Signatures>
                <Resign>PT5H</Resign>
                <Refresh>P3D</Refresh>
                <Validity>
                    <Default>P4D</Default>
                    <Denial>P4D</Denial>
                </Validity>
                <Jitter>PT12H</Jitter>
                <InceptionOffset>PT600S</InceptionOffset>
                <MaxZoneTTL>PT3600S</MaxZoneTTL>
            </Signatures>
 
            <Denial>
                <NSEC3>
                    <Resalt>P90D</Resalt>
                    <Hash>
                        <Algorithm>1</Algorithm>
                        <Iterations>0</Iterations>
                        <Salt length="0"/>
                    </Hash>
                </NSEC3>
            </Denial>
 
            <Keys>
                <!-- Parameters for both KSK and ZSK -->
                <TTL>PT3600S</TTL>
                <RetireSafety>PT360S</RetireSafety>
                <PublishSafety>PT360S</PublishSafety>
                <Purge>P1D</Purge>
 
                <!-- Parameters for KSK only -->
                <KSK>
                    <Algorithm length="2048">8</Algorithm>
                    <Lifetime>P6Y</Lifetime>
                    <Repository>HSM-HAgroup</Repository>
                    <Standby>0</Standby>
                    <ManualRollover/>
                </KSK>
 
                <!-- Parameters for ZSK only -->
                <ZSK>
                    <Algorithm length="1024">8</Algorithm>
                    <Lifetime>P5D</Lifetime>
                    <Repository>HSM-HAgroup</Repository>
                    <Standby>0</Standby>
                </ZSK>
            </Keys>
 
            <Zone>
                <PropagationDelay>PT120S</PropagationDelay>
                <SOA>
                    <TTL>PT3600S</TTL>
                    <Minimum>PT600S</Minimum>
                    <Serial>keep</Serial>
                </SOA>
            </Zone>
 
            <Parent>
                <!-- implies good monitoring of the settings in the rootzone! -->
                <PropagationDelay>PT60S</PropagationDelay>
                <DS>
                    <TTL>PT60S</TTL>
                </DS>
                <SOA>
                    <TTL>PT600S</TTL>
                    <Minimum>PT600S</Minimum>
                </SOA>
            </Parent>
        </Policy>
        <Policy name="policy-nl">
            <Description>SIDN default (nl) EC policy</Description>
            <Signatures>
                <Resign>PT5H</Resign>
                <Refresh>P3D</Refresh>
                <Validity>
                    <Default>P4D</Default>
                    <Denial>P4D</Denial>
                </Validity>
                <Jitter>PT12H</Jitter>
                <InceptionOffset>PT600S</InceptionOffset>
                <MaxZoneTTL>PT3600S</MaxZoneTTL>
            </Signatures>
 
            <Denial>
                <NSEC3>
                    <Resalt>P90D</Resalt>
                    <Hash>
                        <Algorithm>1</Algorithm>
                        <Iterations>0</Iterations>
                        <Salt length="0"/>
                    </Hash>
                </NSEC3>
            </Denial>
 
            <Keys>
                <!-- Parameters for both KSK and ZSK -->
                <TTL>PT3600S</TTL>
                <RetireSafety>PT360S</RetireSafety>
                <PublishSafety>PT360S</PublishSafety>
                <Purge>P1D</Purge>
 
                <!-- Parameters for KSK only -->
                <KSK>
                    <Algorithm length="256">13</Algorithm>
                    <Lifetime>P6Y</Lifetime>
                    <Repository>HSM-HAgroup</Repository>
                    <Standby>0</Standby>
                    <ManualRollover/>
                </KSK>
 
                <!-- Parameters for ZSK only -->
                <ZSK>
                    <Algorithm length="256">13</Algorithm>
                    <Lifetime>P5D</Lifetime>
                    <Repository>HSM-HAgroup</Repository>
                    <Standby>0</Standby>
                </ZSK>
            </Keys>
 
            <Zone>
                <PropagationDelay>PT120S</PropagationDelay>
                <SOA>
                    <TTL>PT3600S</TTL>
                    <Minimum>PT600S</Minimum>
                    <Serial>keep</Serial>
                </SOA>
            </Zone>
 
            <Parent>
                <!-- implies good monitoring of the settings in the rootzone! -->
                <PropagationDelay>PT60S</PropagationDelay>
                <DS>
                    <TTL>PT60S</TTL>
                </DS>
                <SOA>
                    <TTL>PT600S</TTL>
                    <Minimum>PT600S</Minimum>
                </SOA>
            </Parent>
        </Policy>
</KASP>