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>
* Released version 0.16

View File

@ -2,7 +2,7 @@
* <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.
@ -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"
includeantruntime="false"
includes="com/verisignlabs/dnssec/"
source="11"
target="11" />
source="17"
target="17" />
</target>
<target name="sectools-jar" depends="usage,sectools">

View File

@ -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")

View File

@ -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 {

View File

@ -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.

View File

@ -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);
}
}

View File

@ -70,20 +70,19 @@ public class DnsSecVerifier {
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();
List<DnsKeyPair> l = mKeyMap.get(n);
List<DnsKeyPair> 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<DnsKeyPair> 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<DnsKeyPair> 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<String> 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) {

View File

@ -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<Record> records) {
mNodeMap = new TreeMap<>();