From 6118ae718ea974821bef43142d9b2afdfb67c9e3 Mon Sep 17 00:00:00 2001 From: David Blacka Date: Mon, 25 Mar 2024 00:38:47 -0400 Subject: [PATCH] Fix issue 14 (#15) handle duplicate key tags, gen duplicate key tags, other minor cleanup --- ChangeLog | 19 ++++++ README.md | 4 +- VERSION | 2 +- build.xml | 4 +- .../com/verisignlabs/dnssec/cl/CLBase.java | 4 +- .../com/verisignlabs/dnssec/cl/KeyGen.java | 20 +++++- .../dnssec/security/DnsKeyAlgorithm.java | 22 +++---- .../dnssec/security/DnsKeyPair.java | 29 +++++++++ .../dnssec/security/DnsSecVerifier.java | 65 ++++++++++--------- .../dnssec/security/ZoneVerifier.java | 4 +- 10 files changed, 119 insertions(+), 54 deletions(-) diff --git a/ChangeLog b/ChangeLog index 89682e5..15b12ed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2024-03-25 David Blacka + + * Released version 0.19 + * Handle duplicate key tags + * jdnssec-keygen can now attempt to generate keys with specified key tags + +2023-07-24 David Blacka + + * Released version 0.17.1 + * Add a `-t` option to jdnssec-verifyzone: verify using specified time. + +2022-09-21 David Blacka + + * Released version 0.17 + * Updated to dnsjava 3.5.1 + * Formatting and linter suggestions + * Use slf4j instead of log4j. + * jdnssec-dstool can now generate CDS records + 2019-07-23 David Blacka * Released version 0.16 diff --git a/README.md b/README.md index d4ceb96..993ff49 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ * -Author: David Blacka (davidb@verisign.com) +Author: David Blacka () This is a collection of DNSSEC tools written in Java. They are intended to be an addition or replacement for the DNSSEC tools that are part of BIND 9. @@ -45,4 +45,4 @@ The source for this project is available in git on github: ), or by creating issues in the [github issue tracker](https://github.com/dblacka/jdnssec-tools/issues). diff --git a/VERSION b/VERSION index beefc2c..82cd05b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -version=0.17.1 +version=0.19 diff --git a/build.xml b/build.xml index adec6ff..a48e9df 100644 --- a/build.xml +++ b/build.xml @@ -47,8 +47,8 @@ deprecation="true" includeantruntime="false" includes="com/verisignlabs/dnssec/" - source="11" - target="11" /> + source="17" + target="17" /> diff --git a/src/main/java/com/verisignlabs/dnssec/cl/CLBase.java b/src/main/java/com/verisignlabs/dnssec/cl/CLBase.java index 03ff1c0..cfd1246 100644 --- a/src/main/java/com/verisignlabs/dnssec/cl/CLBase.java +++ b/src/main/java/com/verisignlabs/dnssec/cl/CLBase.java @@ -101,8 +101,8 @@ public abstract class CLBase { opts.addOption("m", "multiline", false, "Output DNS records using 'multiline' format"); - opts.addOption(Option.builder("v").longOpt("verbose").argName("level").optionalArg(true).desc( - "verbosity level -- 0 is silence, 3 is info, 5 is debug information, 6 is trace information. default is level 2 (warning)") + opts.addOption(Option.builder("v").longOpt("verbose").argName("level").hasArg().desc( + "verbosity level -- 0: silence, 1: error, 2: warning, 3: info, 4/5: fine, 6: finest; default: 2 (warning)") .build()); opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias") diff --git a/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java b/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java index eed0dd4..16b292b 100644 --- a/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java +++ b/src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java @@ -53,6 +53,7 @@ public class KeyGen extends CLBase { public boolean kskFlag = false; public String owner = null; public long ttl = 86400; + public int givenKeyTag = -1; public CLIState() { super("jdnssec-keygen [..options..] name"); @@ -87,6 +88,8 @@ public class KeyGen extends CLBase { .desc("generated keyfiles are written to this directory").build()); opts.addOption(Option.builder("T").hasArg().argName("ttl").longOpt("ttl") .desc("use this TTL for the generated DNSKEY records (default: 86400").build()); + opts.addOption(Option.builder().hasArg().argName("tag").longOpt("with-tag") + .desc("Generate keys until tag is the given value.").build()); } @@ -133,6 +136,10 @@ public class KeyGen extends CLBase { ttl = parseInt(optstr, 86400); } + if ((optstr = cli.getOptionValue("with-tag")) != null) { + givenKeyTag = parseInt(optstr, -1); + } + String[] args = cli.getArgs(); if (args.length < 1) { @@ -169,11 +176,12 @@ public class KeyGen extends CLBase { // Calculate our flags int flags = 0; - if (state.zoneKey) + if (state.zoneKey) { flags |= DNSKEYRecord.Flags.ZONE_KEY; - if (state.kskFlag) + } + if (state.kskFlag) { flags |= DNSKEYRecord.Flags.SEP_KEY; - + } log.fine("create key pair with (name = " + ownerName + ", ttl = " + state.ttl + ", alg = " + state.algorithm + ", flags = " + flags + ", length = " + state.keylength + ")"); @@ -182,6 +190,12 @@ public class KeyGen extends CLBase { state.algorithm, flags, state.keylength, state.useLargeE); + // If we were asked to generate a duplicate keytag, keep trying until we get one + while (state.givenKeyTag >= 0 && pair.getDNSKEYFootprint() != state.givenKeyTag) { + pair = signer.generateKey(ownerName, state.ttl, DClass.IN, state.algorithm, flags, state.keylength, + state.useLargeE); + } + if (state.outputfile != null) { BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir); } else { diff --git a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java index 706dc4f..debecc2 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java +++ b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java @@ -147,19 +147,18 @@ public class DnsKeyAlgorithm { // This is so we can use this provider if it is available, but not require // the user to add it as one of the java.security providers. try { - Class bc_provider_class = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); - // Provider bc_provider = (Provider) bc_provider_class.newInstance(); - Provider bc_provider = (Provider) bc_provider_class.getDeclaredConstructor().newInstance(); - Security.addProvider(bc_provider); + Class bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance(); + Security.addProvider(bcProvider); } catch (ReflectiveOperationException e) { + log.info("Unable to load BC provider"); } // Attempt to add the EdDSA-Java provider. try { - Class eddsa_provider_class = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider"); - // Provider eddsa_provider = (Provider) eddsa_provider_class.newInstance(); - Provider eddsa_provider = (Provider) eddsa_provider_class.getDeclaredConstructor().newInstance(); - Security.addProvider(eddsa_provider); + Class eddsaProviderClass = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider"); + Provider eddsaProvider = (Provider) eddsaProviderClass.getDeclaredConstructor().newInstance(); + Security.addProvider(eddsaProvider); } catch (ReflectiveOperationException e) { log.warning("Unable to load EdDSA provider"); } @@ -304,8 +303,7 @@ public class DnsKeyAlgorithm { // name, we can construct the parameters here. For now, we only do this for // the ECC-GOST curve. private ECParameterSpec ECSpecFromAlgorithm(int algorithm) { - switch (algorithm) { - case DNSSEC.Algorithm.ECC_GOST: { + if (algorithm == DNSSEC.Algorithm.ECC_GOST) { // From RFC 4357 Section 11.4 BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16); BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16); @@ -316,10 +314,8 @@ public class DnsKeyAlgorithm { EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b); return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1); - } - default: - return null; } + return null; } // Fetch the curve parameters from a named ECDSA curve. diff --git a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyPair.java b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyPair.java index ba6d5e7..eb3da10 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/DnsKeyPair.java +++ b/src/main/java/com/verisignlabs/dnssec/security/DnsKeyPair.java @@ -16,6 +16,7 @@ package com.verisignlabs.dnssec.security; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -127,6 +128,12 @@ public class DnsKeyPair { setPrivateKeyString(pair.getPrivateKeyString()); } + public String toString() { + + return this.getDNSKEYName() + "/" + this.getDNSKEYAlgorithm() + "/" + this.getDNSKEYFootprint() + "/" + + this.getDNSKEYPublicPrefix(6); + } + /** @return cached DnsKeyConverter object. */ protected DnsKeyConverter getKeyConverter() { if (mKeyConverter == null) { @@ -319,4 +326,26 @@ public class DnsKeyPair { return kr.getFootprint(); return -1; } + + // This is from a StackOverflow answer. There are number of bytes-to-hex + // converters in the ecosystem, but this avoid extra dependencies + private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + public static String toHex(byte[] bytes) { + byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } + + public String getDNSKEYPublicPrefix(int length) { + DNSKEYRecord kr = getDNSKEYRecord(); + if (kr == null) { + return ""; + } + String hexKey = toHex(kr.getKey()); + return hexKey.substring(0, length); + } } diff --git a/src/main/java/com/verisignlabs/dnssec/security/DnsSecVerifier.java b/src/main/java/com/verisignlabs/dnssec/security/DnsSecVerifier.java index 34af018..dca81b3 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/DnsSecVerifier.java +++ b/src/main/java/com/verisignlabs/dnssec/security/DnsSecVerifier.java @@ -70,20 +70,19 @@ public class DnsSecVerifier { add(pair); } - public DnsKeyPair find(Name name, int algorithm, int keyid) { + public List find(Name name, int algorithm, int keyid) { String n = name.toString().toLowerCase(); List l = mKeyMap.get(n); + List result = new ArrayList<>(); if (l == null) - return null; + return result; - // FIXME: this algorithm assumes that name+alg+footprint is - // unique, which isn't necessarily true. for (DnsKeyPair p : l) { if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid) { - return p; + result.add(p); } } - return null; + return result; } } @@ -138,7 +137,7 @@ public class DnsSecVerifier { mCurrentTime = time; } - private DnsKeyPair findKey(Name name, int algorithm, int footprint) { + private List findKey(Name name, int algorithm, int footprint) { return mKeyStore.find(name, algorithm, footprint); } @@ -219,43 +218,51 @@ public class DnsSecVerifier { if (!result) return result; - DnsKeyPair keypair = findKey(sigrec.getSigner(), sigrec.getAlgorithm(), + List keypairs = findKey(sigrec.getSigner(), sigrec.getAlgorithm(), sigrec.getFootprint()); - if (keypair == null) { + if (keypairs.isEmpty()) { if (reasons != null) reasons.add("Could not find matching trusted key"); log.fine("could not find matching trusted key"); return false; } - try { byte[] data = SignUtils.generateSigData(rrset, sigrec); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); - Signature signer = keypair.getVerifier(); - signer.update(data); - - byte[] sig = sigrec.getSignature(); - - if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA) { - sig = SignUtils.convertDSASignature(sig); + // Tolerate duplicate keytags, so we can have more than one DnsKeyPair + List localReasons = new ArrayList<>(); + boolean validated = false; + for (DnsKeyPair keypair : keypairs) { + Signature signer = keypair.getVerifier(); + signer.update(data); + + byte[] sig = sigrec.getSignature(); + + if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA) { + sig = SignUtils.convertDSASignature(sig); + } + + if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 || + sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) { + sig = SignUtils.convertECDSASignature(sig); + } + if (signer.verify(sig)) { + validated = true; + break; + } + log.fine("Signature failed to validate cryptographically with " + keypair); + if (localReasons != null) { + localReasons.add("Signature failed to verify cryptographically with " + keypair); + } } - if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 || - sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) { - sig = SignUtils.convertECDSASignature(sig); + if (!validated) { + reasons.addAll(localReasons); } - - if (!signer.verify(sig)) { - if (reasons != null) - reasons.add("Signature failed to verify cryptographically"); - log.fine("Signature failed to verify cryptographically"); - return false; - } - - return true; + return validated; } catch (IOException e) { log.severe("I/O error: " + e); } catch (GeneralSecurityException e) { diff --git a/src/main/java/com/verisignlabs/dnssec/security/ZoneVerifier.java b/src/main/java/com/verisignlabs/dnssec/security/ZoneVerifier.java index 4d47326..be2ab22 100644 --- a/src/main/java/com/verisignlabs/dnssec/security/ZoneVerifier.java +++ b/src/main/java/com/verisignlabs/dnssec/security/ZoneVerifier.java @@ -148,7 +148,7 @@ public class ZoneVerifier { /** * Add a record to the various maps. * - * @return TODO + * @return true if the RR was added, false if it wasn't (because it was a duplicate) */ private boolean addRR(Record r) { Name n = r.getName(); @@ -207,7 +207,7 @@ public class ZoneVerifier { * determine the NSEC3 parameters and signing type. * * @param records - * @return TODO + * @return the number of errors encountered. */ private int calculateNodes(List records) { mNodeMap = new TreeMap<>();