Fix issue 14 (#15)
handle duplicate key tags, gen duplicate key tags, other minor cleanup
This commit is contained in:
parent
5fef1dcf24
commit
6118ae718e
19
ChangeLog
19
ChangeLog
@ -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
|
||||||
|
@ -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).
|
||||||
|
@ -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">
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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<>();
|
||||||
|
Loading…
Reference in New Issue
Block a user