Fix issue 14 (#15)

handle duplicate key tags, gen duplicate key tags, other minor cleanup
This commit is contained in:
David Blacka 2024-03-25 00:38:47 -04:00 committed by GitHub
parent 5fef1dcf24
commit 6118ae718e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 119 additions and 54 deletions

View File

@ -1,3 +1,22 @@
2024-03-25 David Blacka <davidb@verisign.com>
* 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 <davidb@verisign.com>
* Released version 0.17.1
* Add a `-t` option to jdnssec-verifyzone: verify using specified time.
2022-09-21 David Blacka <davidb@verisign.com>
* 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 <davidb@verisign.com> 2019-07-23 David Blacka <davidb@verisign.com>
* Released version 0.16 * Released version 0.16

View File

@ -2,7 +2,7 @@
* <https://github.com/dblacka/jdnssec-tools/wiki> * <https://github.com/dblacka/jdnssec-tools/wiki>
Author: David Blacka (davidb@verisign.com) Author: David Blacka (<davidb@verisign.com>)
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. 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: <https://github.com/d
--- ---
Questions or comments may be directed to the author (mailto:davidb@verisign.com), or by creating issues in the [github issue tracker](https://github.com/dblacka/jdnssec-tools/issues). Questions or comments may be directed to the author (<mailto:davidb@verisign.com>), or by creating issues in the [github issue tracker](https://github.com/dblacka/jdnssec-tools/issues).

View File

@ -1 +1 @@
version=0.17.1 version=0.19

View File

@ -47,8 +47,8 @@
deprecation="true" deprecation="true"
includeantruntime="false" includeantruntime="false"
includes="com/verisignlabs/dnssec/" includes="com/verisignlabs/dnssec/"
source="11" source="17"
target="11" /> target="17" />
</target> </target>
<target name="sectools-jar" depends="usage,sectools"> <target name="sectools-jar" depends="usage,sectools">

View File

@ -101,8 +101,8 @@ public abstract class CLBase {
opts.addOption("m", "multiline", false, opts.addOption("m", "multiline", false,
"Output DNS records using 'multiline' format"); "Output DNS records using 'multiline' format");
opts.addOption(Option.builder("v").longOpt("verbose").argName("level").optionalArg(true).desc( opts.addOption(Option.builder("v").longOpt("verbose").argName("level").hasArg().desc(
"verbosity level -- 0 is silence, 3 is info, 5 is debug information, 6 is trace information. default is level 2 (warning)") "verbosity level -- 0: silence, 1: error, 2: warning, 3: info, 4/5: fine, 6: finest; default: 2 (warning)")
.build()); .build());
opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias") opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias")

View File

@ -53,6 +53,7 @@ public class KeyGen extends CLBase {
public boolean kskFlag = false; public boolean kskFlag = false;
public String owner = null; public String owner = null;
public long ttl = 86400; public long ttl = 86400;
public int givenKeyTag = -1;
public CLIState() { public CLIState() {
super("jdnssec-keygen [..options..] name"); super("jdnssec-keygen [..options..] name");
@ -87,6 +88,8 @@ public class KeyGen extends CLBase {
.desc("generated keyfiles are written to this directory").build()); .desc("generated keyfiles are written to this directory").build());
opts.addOption(Option.builder("T").hasArg().argName("ttl").longOpt("ttl") opts.addOption(Option.builder("T").hasArg().argName("ttl").longOpt("ttl")
.desc("use this TTL for the generated DNSKEY records (default: 86400").build()); .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); ttl = parseInt(optstr, 86400);
} }
if ((optstr = cli.getOptionValue("with-tag")) != null) {
givenKeyTag = parseInt(optstr, -1);
}
String[] args = cli.getArgs(); String[] args = cli.getArgs();
if (args.length < 1) { if (args.length < 1) {
@ -169,11 +176,12 @@ public class KeyGen extends CLBase {
// Calculate our flags // Calculate our flags
int flags = 0; int flags = 0;
if (state.zoneKey) if (state.zoneKey) {
flags |= DNSKEYRecord.Flags.ZONE_KEY; flags |= DNSKEYRecord.Flags.ZONE_KEY;
if (state.kskFlag) }
if (state.kskFlag) {
flags |= DNSKEYRecord.Flags.SEP_KEY; flags |= DNSKEYRecord.Flags.SEP_KEY;
}
log.fine("create key pair with (name = " + ownerName + ", ttl = " + state.ttl log.fine("create key pair with (name = " + ownerName + ", ttl = " + state.ttl
+ ", alg = " + state.algorithm + ", flags = " + flags + ", length = " + ", alg = " + state.algorithm + ", flags = " + flags + ", length = "
+ state.keylength + ")"); + state.keylength + ")");
@ -182,6 +190,12 @@ public class KeyGen extends CLBase {
state.algorithm, flags, state.keylength, state.algorithm, flags, state.keylength,
state.useLargeE); 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) { if (state.outputfile != null) {
BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir); BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
} else { } else {

View File

@ -147,19 +147,18 @@ public class DnsKeyAlgorithm {
// This is so we can use this provider if it is available, but not require // 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. // the user to add it as one of the java.security providers.
try { try {
Class<?> bc_provider_class = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); Class<?> bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
// Provider bc_provider = (Provider) bc_provider_class.newInstance(); Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance();
Provider bc_provider = (Provider) bc_provider_class.getDeclaredConstructor().newInstance(); Security.addProvider(bcProvider);
Security.addProvider(bc_provider);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
log.info("Unable to load BC provider");
} }
// Attempt to add the EdDSA-Java provider. // Attempt to add the EdDSA-Java provider.
try { try {
Class<?> eddsa_provider_class = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider"); Class<?> eddsaProviderClass = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider");
// Provider eddsa_provider = (Provider) eddsa_provider_class.newInstance(); Provider eddsaProvider = (Provider) eddsaProviderClass.getDeclaredConstructor().newInstance();
Provider eddsa_provider = (Provider) eddsa_provider_class.getDeclaredConstructor().newInstance(); Security.addProvider(eddsaProvider);
Security.addProvider(eddsa_provider);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
log.warning("Unable to load EdDSA provider"); 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 // name, we can construct the parameters here. For now, we only do this for
// the ECC-GOST curve. // the ECC-GOST curve.
private ECParameterSpec ECSpecFromAlgorithm(int algorithm) { private ECParameterSpec ECSpecFromAlgorithm(int algorithm) {
switch (algorithm) { if (algorithm == DNSSEC.Algorithm.ECC_GOST) {
case DNSSEC.Algorithm.ECC_GOST: {
// From RFC 4357 Section 11.4 // From RFC 4357 Section 11.4
BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16); BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16);
BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16); BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16);
@ -317,10 +315,8 @@ public class DnsKeyAlgorithm {
EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b); EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b);
return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1); return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1);
} }
default:
return null; return null;
} }
}
// Fetch the curve parameters from a named ECDSA curve. // Fetch the curve parameters from a named ECDSA curve.
private ECParameterSpec ECSpecFromName(String stdName) { private ECParameterSpec ECSpecFromName(String stdName) {

View File

@ -16,6 +16,7 @@
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -127,6 +128,12 @@ public class DnsKeyPair {
setPrivateKeyString(pair.getPrivateKeyString()); setPrivateKeyString(pair.getPrivateKeyString());
} }
public String toString() {
return this.getDNSKEYName() + "/" + this.getDNSKEYAlgorithm() + "/" + this.getDNSKEYFootprint() + "/"
+ this.getDNSKEYPublicPrefix(6);
}
/** @return cached DnsKeyConverter object. */ /** @return cached DnsKeyConverter object. */
protected DnsKeyConverter getKeyConverter() { protected DnsKeyConverter getKeyConverter() {
if (mKeyConverter == null) { if (mKeyConverter == null) {
@ -319,4 +326,26 @@ public class DnsKeyPair {
return kr.getFootprint(); return kr.getFootprint();
return -1; 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);
}
} }

View File

@ -70,20 +70,19 @@ public class DnsSecVerifier {
add(pair); add(pair);
} }
public DnsKeyPair find(Name name, int algorithm, int keyid) { public List<DnsKeyPair> find(Name name, int algorithm, int keyid) {
String n = name.toString().toLowerCase(); String n = name.toString().toLowerCase();
List<DnsKeyPair> l = mKeyMap.get(n); List<DnsKeyPair> l = mKeyMap.get(n);
List<DnsKeyPair> result = new ArrayList<>();
if (l == null) 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) { for (DnsKeyPair p : l) {
if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid) { 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; mCurrentTime = time;
} }
private DnsKeyPair findKey(Name name, int algorithm, int footprint) { private List<DnsKeyPair> findKey(Name name, int algorithm, int footprint) {
return mKeyStore.find(name, algorithm, footprint); return mKeyStore.find(name, algorithm, footprint);
} }
@ -219,21 +218,24 @@ public class DnsSecVerifier {
if (!result) if (!result)
return result; return result;
DnsKeyPair keypair = findKey(sigrec.getSigner(), sigrec.getAlgorithm(), List<DnsKeyPair> keypairs = findKey(sigrec.getSigner(), sigrec.getAlgorithm(),
sigrec.getFootprint()); sigrec.getFootprint());
if (keypair == null) { if (keypairs.isEmpty()) {
if (reasons != null) if (reasons != null)
reasons.add("Could not find matching trusted key"); reasons.add("Could not find matching trusted key");
log.fine("could not find matching trusted key"); log.fine("could not find matching trusted key");
return false; return false;
} }
try { try {
byte[] data = SignUtils.generateSigData(rrset, sigrec); byte[] data = SignUtils.generateSigData(rrset, sigrec);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
// Tolerate duplicate keytags, so we can have more than one DnsKeyPair
List<String> localReasons = new ArrayList<>();
boolean validated = false;
for (DnsKeyPair keypair : keypairs) {
Signature signer = keypair.getVerifier(); Signature signer = keypair.getVerifier();
signer.update(data); signer.update(data);
@ -247,15 +249,20 @@ public class DnsSecVerifier {
sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) { sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) {
sig = SignUtils.convertECDSASignature(sig); sig = SignUtils.convertECDSASignature(sig);
} }
if (signer.verify(sig)) {
if (!signer.verify(sig)) { validated = true;
if (reasons != null) break;
reasons.add("Signature failed to verify cryptographically"); }
log.fine("Signature failed to verify cryptographically"); log.fine("Signature failed to validate cryptographically with " + keypair);
return false; if (localReasons != null) {
localReasons.add("Signature failed to verify cryptographically with " + keypair);
}
} }
return true; if (!validated) {
reasons.addAll(localReasons);
}
return validated;
} catch (IOException e) { } catch (IOException e) {
log.severe("I/O error: " + e); log.severe("I/O error: " + e);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {

View File

@ -148,7 +148,7 @@ public class ZoneVerifier {
/** /**
* Add a record to the various maps. * 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) { private boolean addRR(Record r) {
Name n = r.getName(); Name n = r.getName();
@ -207,7 +207,7 @@ public class ZoneVerifier {
* determine the NSEC3 parameters and signing type. * determine the NSEC3 parameters and signing type.
* *
* @param records * @param records
* @return TODO * @return the number of errors encountered.
*/ */
private int calculateNodes(List<Record> records) { private int calculateNodes(List<Record> records) {
mNodeMap = new TreeMap<>(); mNodeMap = new TreeMap<>();