4 * Copyright (c) 2005 VeriSign, Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 package com.versign.tat.dnssec;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.security.SignatureException;
36 import java.security.interfaces.DSAParams;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.Date;
42 import java.util.Iterator;
44 import org.xbill.DNS.DNSKEYRecord;
45 import org.xbill.DNS.DNSOutput;
46 import org.xbill.DNS.Name;
47 import org.xbill.DNS.RRSIGRecord;
48 import org.xbill.DNS.RRset;
49 import org.xbill.DNS.Record;
52 * This class contains a bunch of utility methods that are generally useful in
53 * signing and verifying rrsets.
56 public class SignUtils {
59 * This class implements a basic comparator for byte arrays. It is primarily
60 * useful for comparing RDATA portions of DNS records in doing DNSSEC
63 public static class ByteArrayComparator implements Comparator<byte[]> {
64 private int mOffset = 0;
65 private boolean mDebug = false;
67 public ByteArrayComparator() {
70 public ByteArrayComparator(int offset, boolean debug) {
75 public int compare(byte[] b1, byte[] b2) throws ClassCastException {
76 for (int i = mOffset; i < b1.length && i < b2.length; i++) {
79 System.out.println("offset " + i + " differs (this is "
81 + " bytes in from our offset.)");
83 return (b1[i] & 0xFF) - (b2[i] & 0xFF);
87 return b1.length - b2.length;
91 // private static final int DSA_SIGNATURE_LENGTH = 20;
92 private static final int ASN1_INT = 0x02;
93 private static final int ASN1_SEQ = 0x30;
95 public static final int RR_NORMAL = 0;
96 public static final int RR_DELEGATION = 1;
97 public static final int RR_GLUE = 2;
98 public static final int RR_INVALID = 3;
101 * Generate from some basic information a prototype SIG RR containing
102 * everything but the actual signature itself.
105 * the RRset being signed.
107 * the name of the signing key
109 * the algorithm of the signing key
111 * the keyid (or footprint) of the signing key
113 * the SIG inception time.
115 * the SIG expiration time.
117 * the TTL of the resulting SIG record.
118 * @return a prototype signature based on the RRset and key information.
120 public static RRSIGRecord generatePreRRSIG(RRset rrset, Name signer,
121 int alg, int keyid, Date start,
122 Date expire, long sig_ttl) {
123 return new RRSIGRecord(rrset.getName(), rrset.getDClass(), sig_ttl,
124 rrset.getType(), alg, rrset.getTTL(), expire, start, keyid,
129 * Generate from some basic information a prototype SIG RR containing
130 * everything but the actual signature itself.
133 * the RRset being signed.
135 * the public KEY RR counterpart to the key being used to sign
138 * the SIG inception time.
140 * the SIG expiration time.
142 * the TTL of the resulting SIG record.
143 * @return a prototype signature based on the RRset and key information.
145 public static RRSIGRecord generatePreRRSIG(RRset rrset, DNSKEYRecord key,
146 Date start, Date expire,
148 return generatePreRRSIG(rrset, key.getName(), key.getAlgorithm(),
149 key.getFootprint(), start, expire, sig_ttl);
153 * Generate from some basic information a prototype SIG RR containing
154 * everything but the actual signature itself.
157 * the DNS record being signed (forming an entire RRset).
159 * the public KEY RR counterpart to the key signing the record.
161 * the SIG inception time.
163 * the SIG expiration time.
165 * the TTL of the result SIG record.
166 * @return a prototype signature based on the Record and key information.
168 public static RRSIGRecord generatePreRRSIG(Record rec, DNSKEYRecord key,
169 Date start, Date expire,
171 return new RRSIGRecord(rec.getName(), rec.getDClass(), sig_ttl,
172 rec.getType(), key.getAlgorithm(), rec.getTTL(), expire, start,
173 key.getFootprint(), key.getName(), null);
177 * Generate the binary image of the prototype SIG RR.
180 * the SIG RR prototype.
181 * @return the RDATA portion of the prototype SIG record. This forms the
182 * first part of the data to be signed.
184 private static byte[] generatePreSigRdata(RRSIGRecord presig) {
185 // Generate the binary image;
186 DNSOutput image = new DNSOutput();
188 // precalculate some things
189 int start_time = (int) (presig.getTimeSigned().getTime() / 1000);
190 int expire_time = (int) (presig.getExpire().getTime() / 1000);
191 Name signer = presig.getSigner();
193 // first write out the partial SIG record (this is the SIG RDATA
194 // minus the actual signature.
195 image.writeU16(presig.getTypeCovered());
196 image.writeU8(presig.getAlgorithm());
197 image.writeU8(presig.getLabels());
198 image.writeU32((int) presig.getOrigTTL());
199 image.writeU32(expire_time);
200 image.writeU32(start_time);
201 image.writeU16(presig.getFootprint());
202 image.writeByteArray(signer.toWireCanonical());
204 return image.toByteArray();
208 * Calculate the canonical wire line format of the RRset.
211 * the RRset to convert.
213 * the TTL to use when canonicalizing -- this is generally the
214 * TTL of the signature if there is a pre-existing signature. If
215 * not it is just the ttl of the rrset itself.
217 * the labels field of the signature, or 0.
218 * @return the canonical wire line format of the rrset. This is the second
219 * part of data to be signed.
221 @SuppressWarnings("unchecked")
222 public static byte[] generateCanonicalRRsetData(RRset rrset, long ttl,
224 DNSOutput image = new DNSOutput();
226 if (ttl == 0) ttl = rrset.getTTL();
227 Name n = rrset.getName();
231 // correct for Name()'s conception of label count.
234 boolean wildcardName = false;
235 if (n.labels() != labels) {
236 n = n.wild(n.labels() - labels);
238 // log.trace("Detected wildcard expansion: " + rrset.getName() +
239 // " changed to " + n);
242 // now convert the wire format records in the RRset into a
243 // list of byte arrays.
244 ArrayList<byte[]> canonical_rrs = new ArrayList<byte[]>();
245 for (Iterator i = rrset.rrs(); i.hasNext();) {
246 Record r = (Record) i.next();
247 if (r.getTTL() != ttl || wildcardName) {
248 // If necessary, we need to create a new record with a new ttl
250 // In the TTL case, this avoids changing the ttl in the
252 r = Record.newRecord(n, r.getType(), r.getDClass(), ttl,
253 r.rdataToWireCanonical());
255 byte[] wire_fmt = r.toWireCanonical();
256 canonical_rrs.add(wire_fmt);
259 // put the records into the correct ordering.
260 // Calculate the offset where the RDATA begins (we have to skip
261 // past the length byte)
263 int offset = rrset.getName().toWireCanonical().length + 10;
264 ByteArrayComparator bac = new ByteArrayComparator(offset, false);
266 Collections.sort(canonical_rrs, bac);
268 for (Iterator<byte[]> i = canonical_rrs.iterator(); i.hasNext();) {
269 byte[] wire_fmt_rec = i.next();
270 image.writeByteArray(wire_fmt_rec);
273 return image.toByteArray();
277 * Given an RRset and the prototype signature, generate the canonical data
278 * that is to be signed.
281 * the RRset to be signed.
283 * a prototype SIG RR created using the same RRset.
284 * @return a block of data ready to be signed.
286 public static byte[] generateSigData(RRset rrset, RRSIGRecord presig)
288 byte[] rrset_data = generateCanonicalRRsetData(rrset,
292 return generateSigData(rrset_data, presig);
296 * Given an RRset and the prototype signature, generate the canonical data
297 * that is to be signed.
300 * the RRset converted into canonical wire line format (as per
301 * the canonicalization rules in RFC 2535).
303 * the prototype signature based on the same RRset represented in
304 * <code>rrset_data</code>.
305 * @return a block of data ready to be signed.
307 public static byte[] generateSigData(byte[] rrset_data, RRSIGRecord presig)
309 byte[] sig_rdata = generatePreSigRdata(presig);
311 ByteArrayOutputStream image = new ByteArrayOutputStream(
312 sig_rdata.length + rrset_data.length);
314 image.write(sig_rdata);
315 image.write(rrset_data);
317 return image.toByteArray();
321 * Given the actual signature and the prototype signature, combine them and
322 * return the fully formed RRSIGRecord.
325 * the cryptographic signature, in DNSSEC format.
327 * the prototype RRSIG RR to add the signature to.
328 * @return the fully formed RRSIG RR.
330 public static RRSIGRecord generateRRSIG(byte[] signature, RRSIGRecord presig) {
331 return new RRSIGRecord(presig.getName(), presig.getDClass(),
332 presig.getTTL(), presig.getTypeCovered(),
333 presig.getAlgorithm(), presig.getOrigTTL(), presig.getExpire(),
334 presig.getTimeSigned(), presig.getFootprint(),
335 presig.getSigner(), signature);
339 * Converts from a RFC 2536 formatted DSA signature to a JCE (ASN.1)
340 * formatted signature.
343 * ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R . ANS1_INT
347 * The integers R and S may have a leading null byte to force the integer
351 * the RFC 2536 formatted DSA signature.
352 * @return The ASN.1 formatted DSA signature.
353 * @throws SignatureException
354 * if there was something wrong with the RFC 2536 formatted
357 public static byte[] convertDSASignature(byte[] signature)
358 throws SignatureException {
359 if (signature.length != 41)
360 throw new SignatureException(
361 "RFC 2536 signature not expected length.");
366 // handle initial null byte padding.
367 if (signature[1] < 0) r_pad++;
368 if (signature[21] < 0) s_pad++;
370 // ASN.1 length = R length + S length + (2 + 2 + 2), where each 2
371 // is for a ASN.1 type-length byte pair of which there are three
373 byte sig_length = (byte) (40 + r_pad + s_pad + 6);
375 byte sig[] = new byte[sig_length];
378 sig[pos++] = ASN1_SEQ;
379 sig[pos++] = (byte) (sig_length - 2); // all but the SEQ type+length.
380 sig[pos++] = ASN1_INT;
381 sig[pos++] = (byte) (20 + r_pad);
383 // copy the value of R, leaving a null byte if necessary
384 if (r_pad == 1) sig[pos++] = 0;
386 System.arraycopy(signature, 1, sig, pos, 20);
389 sig[pos++] = ASN1_INT;
390 sig[pos++] = (byte) (20 + s_pad);
392 // copy the value of S, leaving a null byte if necessary
393 if (s_pad == 1) sig[pos++] = 0;
395 System.arraycopy(signature, 21, sig, pos, 20);
401 * Converts from a JCE (ASN.1) formatted DSA signature to a RFC 2536
402 * compliant signature.
405 * rfc2536 format = T . R . S
408 * where T is a number between 0 and 8, which is based on the DSA key
409 * length, and R & S are formatted to be exactly 20 bytes each (no leading
413 * the DSA parameters associated with the DSA key used to
414 * generate the signature.
416 * the ASN.1 formatted DSA signature.
417 * @return a RFC 2536 formatted DSA signature.
418 * @throws SignatureException
419 * if something is wrong with the ASN.1 format.
421 public static byte[] convertDSASignature(DSAParams params, byte[] signature)
422 throws SignatureException {
423 if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT) {
424 throw new SignatureException(
425 "Invalid ASN.1 signature format: expected SEQ, INT");
428 byte r_pad = (byte) (signature[3] - 20);
430 if (signature[24 + r_pad] != ASN1_INT) {
431 throw new SignatureException(
432 "Invalid ASN.1 signature format: expected SEQ, INT, INT");
435 // log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature));
437 byte s_pad = (byte) (signature[25 + r_pad] - 20);
439 byte[] sig = new byte[41]; // all rfc2536 signatures are 41 bytes.
442 sig[0] = (byte) ((params.getP().bitLength() - 512) / 64);
446 System.arraycopy(signature, 4 + r_pad, sig, 1, 20);
448 // R is shorter than 20 bytes, so right justify the number
449 // (r_pad is negative here, remember?).
450 Arrays.fill(sig, 1, 1 - r_pad, (byte) 0);
451 System.arraycopy(signature, 4, sig, 1 - r_pad, 20 + r_pad);
456 System.arraycopy(signature, 26 + r_pad + s_pad, sig, 21, 20);
458 // S is shorter than 20 bytes, so right justify the number
459 // (s_pad is negative here).
460 Arrays.fill(sig, 21, 21 - s_pad, (byte) 0);
461 System.arraycopy(signature, 26 + r_pad, sig, 21 - s_pad, 20 + s_pad);
464 // if (r_pad < 0 || s_pad < 0)
466 // log.trace("(finish ***) RFC 2536 DSA Sig:\n" + base64.toString(sig));
471 // log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig));