diff --git a/src/com/verisignlabs/dnssec/cl/SignZone.java b/src/com/verisignlabs/dnssec/cl/SignZone.java index 3e7e688..d0f9287 100644 --- a/src/com/verisignlabs/dnssec/cl/SignZone.java +++ b/src/com/verisignlabs/dnssec/cl/SignZone.java @@ -97,6 +97,7 @@ public class SignZone public byte[] salt = null; public int iterations = 0; public int digest_id = DSRecord.SHA1_DIGEST_ID; + public long nsec3paramttl = -1; public CLIState() { @@ -124,7 +125,7 @@ public class SignZone OptionBuilder.withDescription("verbosity level."); // Argument options opts.addOption(OptionBuilder.create('v')); - + OptionBuilder.hasArg(); OptionBuilder.withArgName("dir"); OptionBuilder.withLongOpt("keyset-directory"); @@ -159,7 +160,7 @@ public class SignZone OptionBuilder.withLongOpt("ksk-file"); OptionBuilder.withDescription("this key is a key signing key (may repeat)."); opts.addOption(OptionBuilder.create('k')); - + OptionBuilder.hasArg(); OptionBuilder.withArgName("file"); OptionBuilder.withLongOpt("include-file"); @@ -176,25 +177,31 @@ public class SignZone OptionBuilder.withArgName("hex value"); OptionBuilder.withDescription("supply a salt value."); opts.addOption(OptionBuilder.create('S')); - + OptionBuilder.hasArg(); OptionBuilder.withLongOpt("random-salt"); OptionBuilder.withArgName("length"); OptionBuilder.withDescription("generate a random salt."); opts.addOption(OptionBuilder.create('R')); - + OptionBuilder.hasArg(); OptionBuilder.withLongOpt("iterations"); OptionBuilder.withArgName("value"); OptionBuilder.withDescription("use this value for the iterations in NSEC3."); opts.addOption(OptionBuilder.create()); + OptionBuilder.hasArg(); + OptionBuilder.withLongOpt("nsec3paramttl"); + OptionBuilder.withArgName("ttl"); + OptionBuilder.withDescription("use this value for the NSEC3PARAM RR ttl"); + opts.addOption(OptionBuilder.create()); + OptionBuilder.hasArg(); OptionBuilder.withArgName("alias:original:mnemonic"); OptionBuilder.withLongOpt("alg-alias"); OptionBuilder.withDescription("Define an alias for an algorithm (may repeat)."); opts.addOption(OptionBuilder.create('A')); - + OptionBuilder.hasArg(); OptionBuilder.withArgName("id"); OptionBuilder.withLongOpt("ds-digest"); @@ -351,6 +358,11 @@ public class SignZone } } + if ((optstr = cli.getOptionValue("nsec3paramttl")) != null) + { + nsec3paramttl = parseInt(optstr, -1); + } + String[] files = cli.getArgs(); if (files.length < 1) @@ -715,7 +727,10 @@ public class SignZone { DnsKeyPair kp = (DnsKeyPair) i.next(); Name keyname = kp.getDNSKEYRecord().getName(); - if (!keyname.equals(zonename)) { return false; } + if (!keyname.equals(zonename)) + { + return false; + } } return true; @@ -852,7 +867,8 @@ public class SignZone state.fullySignKeyset, state.useOptOut, state.includeNames, state.salt, - state.iterations, state.digest_id); + state.iterations, state.digest_id, + state.nsec3paramttl); } else { diff --git a/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java b/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java index 124ab5c..000193e 100644 --- a/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java +++ b/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java @@ -151,7 +151,7 @@ public class JCEDnsSecSigner System.out.println("missing private key that goes with:\n" + pair.getDNSKEYRecord()); throw new GeneralSecurityException( - "cannot sign without a valid Signer (probably missing private key)"); + "cannot sign without a valid Signer (probably missing private key)"); } // sign the data. @@ -246,8 +246,14 @@ public class JCEDnsSecSigner rrset.getType(), last_cut); // we don't sign non-normal sets (delegations, glue, invalid). - if (type == SignUtils.RR_DELEGATION) { return rrset.getName(); } - if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID) { return last_cut; } + if (type == SignUtils.RR_DELEGATION) + { + return rrset.getName(); + } + if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID) + { + return last_cut; + } // check for the zone apex keyset. if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY) @@ -316,7 +322,9 @@ public class JCEDnsSecSigner * If true, then only turn on the Opt-In flag when there are insecure * delegations in the span. Currently this only works for * NSEC_EXP_OPT_IN mode. - * + * @param nsec3paramttl + * The TTL to use for the generated NSEC3PARAM record. Negative + * values will use the SOA TTL. * @return an ordered list of {@link org.xbill.DNS.Record} objects, * representing the signed zone. * @@ -327,7 +335,7 @@ public class JCEDnsSecSigner List zskpairs, Date start, Date expire, boolean fullySignKeyset, int ds_digest_alg, int mode, List includedNames, byte[] salt, int iterations, - boolean beConservative) + long nsec3paramttl, boolean beConservative) throws IOException, GeneralSecurityException { // Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM, @@ -348,20 +356,21 @@ public class JCEDnsSecSigner // Generate the NSEC or NSEC3 records based on 'mode' switch (mode) { - case NSEC_MODE: - SignUtils.generateNSECRecords(zonename, records); - break; - case NSEC3_MODE: - SignUtils.generateNSEC3Records(zonename, records, salt, iterations); - break; - case NSEC3_OPTOUT_MODE: - SignUtils.generateOptOutNSEC3Records(zonename, records, includedNames, - salt, iterations); - break; - case NSEC_EXP_OPT_IN: - SignUtils.generateOptInNSECRecords(zonename, records, includedNames, - beConservative); - break; + case NSEC_MODE: + SignUtils.generateNSECRecords(zonename, records); + break; + case NSEC3_MODE: + SignUtils.generateNSEC3Records(zonename, records, salt, iterations, + nsec3paramttl); + break; + case NSEC3_OPTOUT_MODE: + SignUtils.generateOptOutNSEC3Records(zonename, records, includedNames, + salt, iterations, nsec3paramttl); + break; + case NSEC_EXP_OPT_IN: + SignUtils.generateOptInNSECRecords(zonename, records, includedNames, + beConservative); + break; } // Re-sort so we can assemble into rrsets. @@ -443,7 +452,7 @@ public class JCEDnsSecSigner { return signZone(zonename, records, kskpairs, zskpairs, start, expire, fullySignKeyset, ds_digest_alg, NSEC_MODE, null, null, 0, - false); + 0, false); } /** @@ -478,7 +487,9 @@ public class JCEDnsSecSigner * The number of iterations to use for the NSEC3 hashing. * @param ds_digest_alg * The digest algorithm to use when generating DS records. - * + * @param nsec3paramttl + * The TTL to use for the generated NSEC3PARAM record. Negative + * values will use the SOA TTL. * @return an ordered list of {@link org.xbill.DNS.Record} objects, * representing the signed zone. * @@ -489,20 +500,20 @@ public class JCEDnsSecSigner List zskpairs, Date start, Date expire, boolean fullySignKeyset, boolean useOptOut, List includedNames, byte[] salt, int iterations, - int ds_digest_alg) + int ds_digest_alg, long nsec3paramttl) throws IOException, GeneralSecurityException { if (useOptOut) { return signZone(zonename, records, kskpairs, zskpairs, start, expire, fullySignKeyset, ds_digest_alg, NSEC3_OPTOUT_MODE, - includedNames, salt, iterations, false); + includedNames, salt, iterations, nsec3paramttl, false); } else { return signZone(zonename, records, kskpairs, zskpairs, start, expire, fullySignKeyset, ds_digest_alg, NSEC3_MODE, null, salt, - iterations, false); + iterations, nsec3paramttl, false); } } @@ -546,6 +557,6 @@ public class JCEDnsSecSigner return signZone(zonename, records, kskpairs, zskpairs, start, expire, fullySignKeyset, ds_digest_alg, NSEC_EXP_OPT_IN, - NSECIncludeNames, null, 0, useConservativeOptIn); + NSECIncludeNames, null, 0, 0, useConservativeOptIn); } } diff --git a/src/com/verisignlabs/dnssec/security/SignUtils.java b/src/com/verisignlabs/dnssec/security/SignUtils.java index dc1ea6c..58d90f6 100644 --- a/src/com/verisignlabs/dnssec/security/SignUtils.java +++ b/src/com/verisignlabs/dnssec/security/SignUtils.java @@ -338,16 +338,18 @@ public class SignUtils public static byte[] convertDSASignature(DSAParams params, byte[] signature) throws SignatureException { - if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT) + if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT) { - throw new SignatureException("Invalid ASN.1 signature format: expected SEQ, INT"); + throw new SignatureException( + "Invalid ASN.1 signature format: expected SEQ, INT"); } byte r_pad = (byte) (signature[3] - 20); - if (signature[24 + r_pad] != ASN1_INT) - { - throw new SignatureException("Invalid ASN.1 signature format: expected SEQ, INT, INT"); + if (signature[24 + r_pad] != ASN1_INT) + { + throw new SignatureException( + "Invalid ASN.1 signature format: expected SEQ, INT, INT"); } log.finer("(start) ASN.1 DSA Sig:\n" + base64.toString(signature)); @@ -503,8 +505,7 @@ public class SignUtils // Current record is part of the current RRset. if (rrset.getName().equals(r.getName()) && rrset.getDClass() == r.getDClass() - && ((r.getType() == Type.RRSIG && rrset.getType() == ((RRSIGRecord) r).getTypeCovered()) || - rrset.getType() == r.getType())) + && ((r.getType() == Type.RRSIG && rrset.getType() == ((RRSIGRecord) r).getTypeCovered()) || rrset.getType() == r.getType())) { rrset.addRR(r); continue; @@ -585,7 +586,7 @@ public class SignUtils /** * Given a canonical (by name) ordered list of records in a zone, generate the - * NXT records in place. + * NSEC records in place. * * Note that the list that the records are stored in must support the * listIterator.add() operation. @@ -620,8 +621,8 @@ public class SignUtils break; } } - if (nsec_ttl == 0) - { + if (nsec_ttl == 0) + { throw new IllegalArgumentException("Zone did not contain a SOA record"); } @@ -699,8 +700,31 @@ public class SignUtils log.finer("Generated: " + nsec); } + /** + * Given a canonical (by name) ordered list of records in a zone, generate the + * NSEC3 records in place. + * + * Note that the list that the records are stored in must support the + * listIterator.add() operation. + * + * @param zonename + * the name of the zone (used to distinguish between zone apex NS + * RRsets and delegations). + * @param records + * a list of {@link org.xbill.DNS.Record} objects in DNSSEC canonical + * order. + * @param salt + * The NSEC3 salt to use (may be null or empty for no salt). + * @param iterations + * The number of hash iterations to use. + * @param nsec3param_ttl + * The TTL to use for the generated NSEC3PARAM records (NSEC3 records + * will use the SOA minimum) + * @throws NoSuchAlgorithmException + */ public static void generateNSEC3Records(Name zonename, List records, - byte[] salt, int iterations) + byte[] salt, int iterations, + long nsec3param_ttl) throws NoSuchAlgorithmException { List proto_nsec3s = new ArrayList(); @@ -710,7 +734,6 @@ public class SignUtils Name last_cut = null; long nsec3_ttl = 0; - long nsec3param_ttl = 0; for (Iterator i = records.iterator(); i.hasNext();) { @@ -731,7 +754,10 @@ public class SignUtils { SOARecord soa = (SOARecord) r; nsec3_ttl = soa.getMinimum(); - nsec3param_ttl = soa.getTTL(); + if (nsec3param_ttl < 0) + { + nsec3param_ttl = soa.getTTL(); + } } // For the first iteration, we create our current node. @@ -785,9 +811,38 @@ public class SignUtils } + /** + * Given a canonical (by name) ordered list of records in a zone, generate the + * NSEC3 records in place using Opt-Out NSEC3 records. This means that + * non-apex NS RRs (and glue below those delegations) will, by default, not be + * included in the NSEC3 chain. + * + * Note that the list that the records are stored in must support the + * listIterator.add() operation. + * + * @param zonename + * the name of the zone (used to distinguish between zone apex NS + * RRsets and delegations). + * @param records + * a list of {@link org.xbill.DNS.Record} objects in DNSSEC canonical + * order. + * @param includedNames + * A list of {@link org.xbill.DNS.Name} objects. These names will be + * included in the NSEC3 chain (if they exist in the zone) + * regardless. + * @param salt + * The NSEC3 salt to use (may be null or empty for no salt). + * @param iterations + * The number of hash iterations to use. + * @param nsec3param_ttl + * The TTL to use for the generated NSEC3PARAM records (NSEC3 records + * will use the SOA minimum) + * @throws NoSuchAlgorithmException + */ public static void generateOptOutNSEC3Records(Name zonename, List records, List includedNames, - byte[] salt, int iterations) + byte[] salt, int iterations, + long nsec3param_ttl) throws NoSuchAlgorithmException { List proto_nsec3s = new ArrayList(); @@ -797,7 +852,6 @@ public class SignUtils Name last_cut = null; long nsec3_ttl = 0; - long nsec3param_ttl = 0; HashSet includeSet = null; if (includedNames != null) @@ -824,7 +878,10 @@ public class SignUtils { SOARecord soa = (SOARecord) r; nsec3_ttl = soa.getMinimum(); - nsec3param_ttl = soa.getTTL(); + if (nsec3param_ttl < 0) + { + nsec3param_ttl = soa.getTTL(); + } } // For the first iteration, we create our current node. @@ -883,6 +940,25 @@ public class SignUtils records.add(nsec3param); } + /** + * For a given node (representing all of the RRsets at a given name), generate + * all of the necessary NSEC3 records for it. That is, generate the NSEC3 for + * the node itself, and for any potential empty non-terminals. + * + * @param node + * The node in question. + * @param zonename + * The zonename. + * @param salt + * The salt to use for the NSEC3 RRs + * @param iterations + * The iterations to use for the NSEC3 RRs. + * @param optIn + * If true, the NSEC3 will have the Opt-Out flag set. + * @param nsec3s + * The current list of NSEC3s -- this will be updated. + * @throws NoSuchAlgorithmException + */ private static void generateNSEC3ForNode(NodeInfo node, Name zonename, byte[] salt, int iterations, boolean optIn, List nsec3s) @@ -912,6 +988,27 @@ public class SignUtils nsec3s.add(nsec3); } + /** + * Create a "prototype" NSEC3 record. Basically, a mutable NSEC3 record. + * + * @param name + * The original ownername to use. + * @param zonename + * The zonename to use. + * @param ttl + * The TTL to use. + * @param salt + * The salt to use. + * @param iterations + * The number of hash iterations to use. + * @param optIn + * The value of the Opt-Out flag. + * @param types + * The typecodes present at this name. + * @return A mutable NSEC3 record. + * + * @throws NoSuchAlgorithmException + */ private static ProtoNSEC3 generateNSEC3(Name name, Name zonename, long ttl, byte[] salt, int iterations, boolean optIn, int[] types) @@ -921,14 +1018,26 @@ public class SignUtils iterations, salt); byte flags = (byte) (optIn ? 0x01 : 0x00); - ProtoNSEC3 r = new ProtoNSEC3(hash, name, zonename, DClass.IN, ttl, flags, - NSEC3Record.SHA1_DIGEST_ID, iterations, salt, - null, types); + ProtoNSEC3 r = new ProtoNSEC3(hash, name, zonename, DClass.IN, ttl, + NSEC3Record.SHA1_DIGEST_ID, flags, + iterations, salt, null, types); log.finer("Generated: " + r); return r; } + /** + * Given a list of {@link ProtoNSEC3} object (mutable NSEC3 RRs), convert the + * list into the set of actual {@link org.xbill.DNS.NSEC3Record} objects. This + * will remove duplicates and finalize the records. + * + * @param nsec3s + * The list of ProtoNSEC3 objects + * @param ttl + * The TTL to assign to the finished NSEC3 records. In general, this + * should match the SOA minimum value for the zone. + * @return The list of {@link org.xbill.DNS.NSEC3Record} objects. + */ private static List finishNSEC3s(List nsec3s, long ttl) { if (nsec3s == null) return null;