diff --git a/.travis.yml b/.travis.yml index e0abce3..00c74ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +branches: + only: + - master language: java after_success: - mvn clean test jacoco:report coveralls:report diff --git a/README.md b/README.md index 96fc463..ed294fd 100644 --- a/README.md +++ b/README.md @@ -145,11 +145,18 @@ The file from which the trust anchor should be loaded. There is no default. It must be formatted like a DNS zone master file. It can only contain DS or DNSKEY records. -### org.jitsi.dnssec.digest_preference +### org.jitsi.dnssec.digest\_preference Defines the preferred DS record digest algorithm if a zone has registered multiple DS records. The list is comma-separated, highest preference first. If this property is not specified, the DS record with the highest [digest ID] (http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml) is chosen. To stay compliant with the RFCs, the mandatory digest IDs must be listed in -this property. The GOST digest is not (yet) implemented. +this property. + +The GOST digest is not (yet) implemented. + +### org.jitsi.dnssec.harden\_algo\_downgrade +Prevent algorithm downgrade when multiple algorithms are advertised in a zones +DS records. If `false`, allows any algorithm to validate the zone. +Default is `true`. diff --git a/src/main/java/org/jitsi/dnssec/validator/ValUtils.java b/src/main/java/org/jitsi/dnssec/validator/ValUtils.java index 2d6eced..5550e67 100644 --- a/src/main/java/org/jitsi/dnssec/validator/ValUtils.java +++ b/src/main/java/org/jitsi/dnssec/validator/ValUtils.java @@ -73,6 +73,7 @@ */ public class ValUtils { public static final String DIGEST_PREFERENCE = "org.jitsi.dnssec.digest_preference"; + public static final String DIGEST_HARDEN_DOWNGRADE = "org.jitsi.dnssec.harden_algo_downgrade"; private static final Logger logger = LoggerFactory.getLogger(ValUtils.class); private static final Name WILDCARD = Name.fromConstantString("*"); @@ -80,6 +81,7 @@ public class ValUtils { /** A local copy of the verifier object. */ private DnsSecVerifier verifier; private int[] digestPreference = null; + private boolean digestHardenDowngrade = true; /** * Creates a new instance of this class. @@ -89,8 +91,11 @@ public ValUtils() { } /** - * Initialize the module. The only recognized configuration value is - * {@link #DIGEST_PREFERENCE}. + * Initialize the module. The recognized configuration value are + * . * * @param config The configuration data for this module. */ @@ -106,6 +111,8 @@ public void init(Properties config) { } } } + + this.digestHardenDowngrade = Boolean.parseBoolean(config.getProperty(DIGEST_HARDEN_DOWNGRADE)); } /** @@ -198,7 +205,7 @@ public KeyEntry verifyNewDNSKEYs(SRRset dnskeyRrset, SRRset dsRrset, long badKey int favoriteDigestID = this.favoriteDSDigestID(dsRrset); for (Iterator i = dsRrset.rrs(); i.hasNext();) { DSRecord ds = (DSRecord)i.next(); - if (ds.getDigestID() != favoriteDigestID) { + if (this.digestHardenDowngrade && ds.getDigestID() != favoriteDigestID) { continue; } diff --git a/src/test/java/org/jitsi/dnssec/unbound/rpl/Rpl.java b/src/test/java/org/jitsi/dnssec/unbound/rpl/Rpl.java index 27706f2..ee6ad30 100644 --- a/src/test/java/org/jitsi/dnssec/unbound/rpl/Rpl.java +++ b/src/test/java/org/jitsi/dnssec/unbound/rpl/Rpl.java @@ -27,4 +27,5 @@ public class Rpl { public Map checks; public TreeMap nsec3iterations; public String digestPreference; + public boolean hardenAlgoDowngrade; } diff --git a/src/test/java/org/jitsi/dnssec/unbound/rpl/RplParser.java b/src/test/java/org/jitsi/dnssec/unbound/rpl/RplParser.java index 06a56c6..2cae5d0 100644 --- a/src/test/java/org/jitsi/dnssec/unbound/rpl/RplParser.java +++ b/src/test/java/org/jitsi/dnssec/unbound/rpl/RplParser.java @@ -129,6 +129,9 @@ else if (line.matches("\\s*val-nsec3-keysize-iterations:.*")) { else if (line.matches("\\s*val-digest-preference:.*")) { rpl.digestPreference = line.substring(line.indexOf("\"") + 1, line.length() - 1); } + else if (line.matches("\\s*harden-algo-downgrade:.*")) { + rpl.hardenAlgoDowngrade = !"no".equalsIgnoreCase(line.split(":")[1].trim()); + } else if (line.startsWith("CONFIG_END")) { state = ParseState.Zero; } diff --git a/src/test/java/org/jitsi/dnssec/unbound/rpl/UnboundTests.java b/src/test/java/org/jitsi/dnssec/unbound/rpl/UnboundTests.java index 2cd1be4..ff29f20 100644 --- a/src/test/java/org/jitsi/dnssec/unbound/rpl/UnboundTests.java +++ b/src/test/java/org/jitsi/dnssec/unbound/rpl/UnboundTests.java @@ -27,6 +27,7 @@ import org.jitsi.dnssec.SRRset; import org.jitsi.dnssec.TestBase; +import org.jitsi.dnssec.validator.ValUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; @@ -58,9 +59,11 @@ public void runUnboundTest() throws ParseException, IOException { } if (rpl.digestPreference != null) { - config.put("org.jitsi.dnssec.digest_preference", rpl.digestPreference); + config.put(ValUtils.DIGEST_PREFERENCE, rpl.digestPreference); } + config.put(ValUtils.DIGEST_HARDEN_DOWNGRADE, Boolean.toString(rpl.hardenAlgoDowngrade)); + for (Message m : rpl.replays) { add(stripAdditional(m)); for (RRset set : m.getSectionRRsets(Section.AUTHORITY)) { @@ -172,8 +175,8 @@ else if (s.getType() == Type.DNAME) { for (Check c : rpl.checks.values()) { Message s = resolver.send(c.query); - assertEquals(c.response.getHeader().getFlag(Flags.AD), s.getHeader().getFlag(Flags.AD)); - assertEquals(Rcode.string(c.response.getRcode()), Rcode.string(s.getRcode())); + assertEquals("AD Flag must match", c.response.getHeader().getFlag(Flags.AD), s.getHeader().getFlag(Flags.AD)); + assertEquals("RCode must match", Rcode.string(c.response.getRcode()), Rcode.string(s.getRcode())); } } @@ -471,6 +474,11 @@ public void val_ds_sha2_downgrade_override() throws ParseException, IOException runUnboundTest(); } + @Test + public void val_ds_sha2_lenient() throws ParseException, IOException { + runUnboundTest(); + } + @Test public void val_entds() throws ParseException, IOException { runUnboundTest(); diff --git a/src/test/resources/unbound/val_ds_gost_downgrade.rpl b/src/test/resources/unbound/val_ds_gost_downgrade.rpl index 00e6e4e..a3930bc 100644 --- a/src/test/resources/unbound/val_ds_gost_downgrade.rpl +++ b/src/test/resources/unbound/val_ds_gost_downgrade.rpl @@ -4,6 +4,10 @@ server: trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" val-override-date: "20070916134226" target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + harden-algo-downgrade: yes stub-zone: name: "." @@ -231,7 +235,7 @@ ENTRY_END STEP 10 CHECK_ANSWER ENTRY_BEGIN MATCH all -REPLY QR RD RA SERVFAIL +REPLY QR RD RA DO SERVFAIL SECTION QUESTION www.sub.example.com. IN A SECTION ANSWER diff --git a/src/test/resources/unbound/val_ds_sha2.rpl b/src/test/resources/unbound/val_ds_sha2.rpl index 111382d..4af6693 100644 --- a/src/test/resources/unbound/val_ds_sha2.rpl +++ b/src/test/resources/unbound/val_ds_sha2.rpl @@ -4,6 +4,10 @@ server: trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" val-override-date: "20070916134226" target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-dsa: yes + fake-sha1: yes + trust-anchor-signaling: no stub-zone: name: "." @@ -188,7 +192,7 @@ ENTRY_END STEP 10 CHECK_ANSWER ENTRY_BEGIN MATCH all -REPLY QR RD RA AD NOERROR +REPLY QR RD RA AD DO NOERROR SECTION QUESTION www.sub.example.com. IN A SECTION ANSWER diff --git a/src/test/resources/unbound/val_ds_sha2_downgrade.rpl b/src/test/resources/unbound/val_ds_sha2_downgrade.rpl index 2418c87..b15f39b 100644 --- a/src/test/resources/unbound/val_ds_sha2_downgrade.rpl +++ b/src/test/resources/unbound/val_ds_sha2_downgrade.rpl @@ -4,6 +4,11 @@ server: trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" val-override-date: "20070916134226" target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-dsa: yes + fake-sha1: yes + trust-anchor-signaling: no + harden-algo-downgrade: yes stub-zone: name: "." @@ -211,7 +216,7 @@ ENTRY_END STEP 10 CHECK_ANSWER ENTRY_BEGIN MATCH all -REPLY QR RD RA SERVFAIL +REPLY QR RD RA DO SERVFAIL SECTION QUESTION www.sub.example.com. IN A SECTION ANSWER diff --git a/src/test/resources/unbound/val_ds_sha2_lenient.rpl b/src/test/resources/unbound/val_ds_sha2_lenient.rpl new file mode 100644 index 0000000..631facd --- /dev/null +++ b/src/test/resources/unbound/val_ds_sha2_lenient.rpl @@ -0,0 +1,229 @@ +; config options +; The island of trust is at example.com +server: + trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" + val-override-date: "20070916134226" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-dsa: yes + fake-sha1: yes + trust-anchor-signaling: no + harden-algo-downgrade: no + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test validator with SHA256 DS downgrade to SHA1 lenience + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN DNSKEY +SECTION ANSWER +example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b} +example.com. 3600 IN RRSIG DNSKEY DSA 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFBQRtlR4BEv9ohi+PGFjp+AHsJuHAhRCvz0shggvnvI88DFnBDCczHUcVA== ;{id = 2854} +SECTION AUTHORITY +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} +ENTRY_END + +; response for delegation to sub.example.com. +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +sub.example.com. IN A +SECTION ANSWER +SECTION AUTHORITY +sub.example.com. IN NS ns.sub.example.com. + +; Downgrade attack: false SHA2, correct SHA1 + +; SHA256 DS for sub.example.com. +;sub.example.com. 3600 IN DS 30899 5 2 51be8e847cc663f2775d0f2b6d15e41553c97ecb99b8dd667f18244e2f652033 +; BAD SHA256 DS +sub.example.com. 3600 IN DS 30899 5 2 51be8e847cc663f2775d0f2b6d15e41553c97ecb99b8dd667f18244e2f652000 + +; SHA1 DS for sub.example.com. +sub.example.com. 3600 IN DS 30899 RSASHA1 1 f7ed618f24d5e5202927e1d27bc2e84a141cb4b3 +sub.example.com. 3600 IN RRSIG DS 3 3 3600 20070926135752 20070829135752 2854 example.com. ACqqpk1ow07XJvN1orEpiWOeqMLdDKQtTgWB8Mp6CF/9VTfHuWWmsu8= ;{id = 2854} + +SECTION ADDITIONAL +ns.sub.example.com. IN A 1.2.3.6 +ENTRY_END + +RANGE_END + +; ns.sub.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.6 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +sub.example.com. IN NS +SECTION ANSWER +sub.example.com. IN NS ns.sub.example.com. +sub.example.com. 3600 IN RRSIG NS 5 3 3600 20070926134150 20070829134150 30899 sub.example.com. wcpHeBILHfo8C9uxMhcW03gcURZeUffiKdSTb50ZjzTHgMNhRyMfpcvSpXEd9548A9UTmWKeLZChfr5Z/glONw== ;{id = 30899} +SECTION ADDITIONAL +ns.sub.example.com. IN A 1.2.3.6 +ns.sub.example.com. 3600 IN RRSIG A 5 4 3600 20070926134150 20070829134150 30899 sub.example.com. UF7shD/gt1FOp2UHgLTNbPzVykklSXFMEtJ1xD+Hholwf/PIzd7zoaIttIYibNa4fUXCqMg22H9P7MRhfmFe6g== ;{id = 30899} +ENTRY_END + +; response to DNSKEY priming query +; sub.example.com. 3600 IN DS 30899 RSASHA1 1 f7ed618f24d5e5202927e1d27bc2e84a141cb4b3 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +sub.example.com. IN DNSKEY +SECTION ANSWER +sub.example.com. 3600 IN DNSKEY 256 3 5 AQPQ41chR9DEHt/aIzIFAqanbDlRflJoRs5yz1jFsoRIT7dWf0r+PeDuewdxkszNH6wnU4QL8pfKFRh5PIYVBLK3 ;{id = 30899 (zsk), size = 512b} +sub.example.com. 3600 IN RRSIG DNSKEY 5 3 3600 20070926134150 20070829134150 30899 sub.example.com. uNGp99iznjD7oOX02XnQbDnbg75UwBHRvZSKYUorTKvPUnCWMHKdRsQ+mf+Fx3GZ+Fz9BVjoCmQqpnfgXLEYqw== ;{id = 30899} +SECTION AUTHORITY +sub.example.com. IN NS ns.sub.example.com. +sub.example.com. 3600 IN RRSIG NS 5 3 3600 20070926134150 20070829134150 30899 sub.example.com. wcpHeBILHfo8C9uxMhcW03gcURZeUffiKdSTb50ZjzTHgMNhRyMfpcvSpXEd9548A9UTmWKeLZChfr5Z/glONw== ;{id = 30899} +SECTION ADDITIONAL +ns.sub.example.com. IN A 1.2.3.6 +ns.sub.example.com. 3600 IN RRSIG A 5 4 3600 20070926134150 20070829134150 30899 sub.example.com. UF7shD/gt1FOp2UHgLTNbPzVykklSXFMEtJ1xD+Hholwf/PIzd7zoaIttIYibNa4fUXCqMg22H9P7MRhfmFe6g== ;{id = 30899} +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION ANSWER +www.sub.example.com. IN A 11.11.11.11 +www.sub.example.com. 3600 IN RRSIG A 5 4 3600 20070926134150 20070829134150 30899 sub.example.com. 0DqqRfRtm7VSEQ4mmBbzrKRqQAay3JAE8DPDGmjtokrrjN9F1G/HxozDV7bjdIh2EChlQea8FPwf/GepJMUVxg== ;{id = 30899} +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA REFUSED +SECTION QUESTION +ns.sub.example.com. IN A +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA REFUSED +SECTION QUESTION +ns.sub.example.com. IN AAAA +ENTRY_END + +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.sub.example.com. IN A +ENTRY_END + +; recursion happens here. +; must servfail, BOGUS +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION ANSWER +www.sub.example.com. 3600 IN A 11.11.11.11 +www.sub.example.com. 3600 IN RRSIG A 5 4 3600 20070926134150 20070829134150 30899 sub.example.com. 0DqqRfRtm7VSEQ4mmBbzrKRqQAay3JAE8DPDGmjtokrrjN9F1G/HxozDV7bjdIh2EChlQea8FPwf/GepJMUVxg== ;{id = 30899} +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + +SCENARIO_END