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;
50 import org.xbill.DNS.utils.base64;
53 * This class contains a bunch of utility methods that are generally useful in
54 * signing and verifying rrsets.
56 * @author David Blacka (original)
61 public class SignUtils
65 * This class implements a basic comparitor for byte arrays. It is primarily
66 * useful for comparing RDATA portions of DNS records in doing DNSSEC
69 * @author David Blacka (original)
71 public static class ByteArrayComparator implements Comparator
73 private int mOffset = 0;
74 private boolean mDebug = false;
76 public ByteArrayComparator()
80 public ByteArrayComparator(int offset, boolean debug)
86 public int compare(Object o1, Object o2) throws ClassCastException
88 byte[] b1 = (byte[]) o1;
89 byte[] b2 = (byte[]) o2;
91 for (int i = mOffset; i < b1.length && i < b2.length; i++)
97 System.out.println("offset " + i + " differs (this is "
98 + (i - mOffset) + " bytes in from our offset.)");
100 return (b1[i] & 0xFF) - (b2[i] & 0xFF);
104 return b1.length - b2.length;
108 // private static final int DSA_SIGNATURE_LENGTH = 20;
109 private static final int ASN1_INT = 0x02;
110 private static final int ASN1_SEQ = 0x30;
112 public static final int RR_NORMAL = 0;
113 public static final int RR_DELEGATION = 1;
114 public static final int RR_GLUE = 2;
115 public static final int RR_INVALID = 3;
118 * Generate from some basic information a prototype SIG RR containing
119 * everything but the actual signature itself.
121 * @param rrset the RRset being signed.
122 * @param signer the name of the signing key
123 * @param alg the algorithm of the signing key
124 * @param keyid the keyid (or footprint) of the signing key
125 * @param start the SIG inception time.
126 * @param expire the SIG expiration time.
127 * @param sig_ttl the TTL of the resulting SIG record.
128 * @return a prototype signature based on the RRset and key information.
130 public static RRSIGRecord generatePreRRSIG(RRset rrset, Name signer,
131 int alg, int keyid, Date start, Date expire, long sig_ttl)
133 return new RRSIGRecord(rrset.getName(), rrset.getDClass(), sig_ttl, rrset
134 .getType(), alg, rrset.getTTL(), expire, start, keyid, signer, null);
138 * Generate from some basic information a prototype SIG RR containing
139 * everything but the actual signature itself.
141 * @param rrset the RRset being signed.
142 * @param key the public KEY RR counterpart to the key being used to sign
144 * @param start the SIG inception time.
145 * @param expire the SIG expiration time.
146 * @param sig_ttl the TTL of the resulting SIG record.
147 * @return a prototype signature based on the RRset and key information.
149 public static RRSIGRecord generatePreRRSIG(RRset rrset, DNSKEYRecord key,
150 Date start, Date expire, long sig_ttl)
152 return generatePreRRSIG(rrset, key.getName(), key.getAlgorithm(), key
153 .getFootprint(), start, expire, sig_ttl);
157 * Generate from some basic information a prototype SIG RR containing
158 * everything but the actual signature itself.
160 * @param rec the DNS record being signed (forming an entire RRset).
161 * @param key the public KEY RR counterpart to the key signing the record.
162 * @param start the SIG inception time.
163 * @param expire the SIG expiration time.
164 * @param sig_ttl the TTL of the result SIG record.
165 * @return a prototype signature based on the Record and key information.
167 public static RRSIGRecord generatePreRRSIG(Record rec, DNSKEYRecord key,
168 Date start, Date expire, long sig_ttl)
170 return new RRSIGRecord(rec.getName(), rec.getDClass(), sig_ttl, rec
171 .getType(), key.getAlgorithm(), rec.getTTL(), expire, start, key
172 .getFootprint(), key.getName(), null);
176 * Generate the binary image of the prototype SIG RR.
178 * @param presig the SIG RR prototype.
179 * @return the RDATA portion of the prototype SIG record. This forms the
180 * first part of the data to be signed.
182 private static byte[] generatePreSigRdata(RRSIGRecord presig)
184 // Generate the binary image;
185 DNSOutput image = new DNSOutput();
187 // precalc some things
188 int start_time = (int) (presig.getTimeSigned().getTime() / 1000);
189 int expire_time = (int) (presig.getExpire().getTime() / 1000);
190 Name signer = presig.getSigner();
192 // first write out the partial SIG record (this is the SIG RDATA
193 // minus the actual signature.
194 image.writeU16(presig.getTypeCovered());
195 image.writeU8(presig.getAlgorithm());
196 image.writeU8(presig.getLabels());
197 image.writeU32((int) presig.getOrigTTL());
198 image.writeU32(expire_time);
199 image.writeU32(start_time);
200 image.writeU16(presig.getFootprint());
201 image.writeByteArray(signer.toWireCanonical());
203 return image.toByteArray();
207 * Calculate the canonical wire line format of the RRset.
209 * @param rrset the RRset to convert.
210 * @param ttl the TTL to use when canonicalizing -- this is generally the
211 * TTL of the signature if there is a pre-existing signature. If
212 * not it is just the ttl of the rrset itself.
213 * @param labels the labels field of the signature, or 0.
214 * @return the canonical wire line format of the rrset. This is the second
215 * part of data to be signed.
217 public static byte[] generateCanonicalRRsetData(RRset rrset, long ttl,
220 DNSOutput image = new DNSOutput();
222 if (ttl == 0) ttl = rrset.getTTL();
223 Name n = rrset.getName();
230 // correct for Name()'s conception of label count.
233 boolean wildcardName = false;
234 if (n.labels() != labels)
236 n = n.wild(n.labels() - labels);
238 // log.trace("Detected wildcard expansion: " + rrset.getName() + " changed to " + n);
241 // now convert load the wire format records in the RRset into a
242 // list of byte arrays.
243 ArrayList canonical_rrs = new ArrayList();
244 for (Iterator i = rrset.rrs(); i.hasNext();)
246 Record r = (Record) i.next();
247 if (r.getTTL() != ttl || wildcardName)
249 // If necessary, we need to create a new record with a new ttl or ownername.
250 // In the TTL case, this avoids changing the ttl in the response.
251 r = Record.newRecord(n, r.getType(), r.getDClass(), ttl, r
252 .rdataToWireCanonical());
254 byte[] wire_fmt = r.toWireCanonical();
255 canonical_rrs.add(wire_fmt);
258 // put the records into the correct ordering.
259 // Caculate the offset where the RDATA begins (we have to skip
260 // past the length byte)
262 int offset = rrset.getName().toWireCanonical().length + 10;
263 ByteArrayComparator bac = new ByteArrayComparator(offset, false);
265 Collections.sort(canonical_rrs, bac);
267 for (Iterator i = canonical_rrs.iterator(); i.hasNext();)
269 byte[] wire_fmt_rec = (byte[]) 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.
280 * @param rrset the RRset to be signed.
281 * @param presig a prototype SIG RR created using the same RRset.
282 * @return a block of data ready to be signed.
284 public static byte[] generateSigData(RRset rrset, RRSIGRecord presig)
287 byte[] rrset_data = generateCanonicalRRsetData(rrset, presig.getOrigTTL(), presig.getLabels());
289 return generateSigData(rrset_data, presig);
293 * Given an RRset and the prototype signature, generate the canonical data
294 * that is to be signed.
296 * @param rrset_data the RRset converted into canonical wire line format (as
297 * per the canonicalization rules in RFC 2535).
298 * @param presig the prototype signature based on the same RRset represented
299 * in <code>rrset_data</code>.
300 * @return a block of data ready to be signed.
302 public static byte[] generateSigData(byte[] rrset_data, RRSIGRecord presig)
305 byte[] sig_rdata = generatePreSigRdata(presig);
307 ByteArrayOutputStream image = new ByteArrayOutputStream(sig_rdata.length
308 + rrset_data.length);
310 image.write(sig_rdata);
311 image.write(rrset_data);
313 return image.toByteArray();
317 * Given the acutal signature an the prototype signature, combine them and
318 * return the fully formed SIGRecord.
320 * @param signature the cryptographic signature, in DNSSEC format.
321 * @param presig the prototype SIG RR to add the signature to.
322 * @return the fully formed SIG RR.
324 public static RRSIGRecord generateRRSIG(byte[] signature, RRSIGRecord presig)
326 return new RRSIGRecord(presig.getName(), presig.getDClass(), presig
327 .getTTL(), presig.getTypeCovered(), presig.getAlgorithm(), presig
328 .getOrigTTL(), presig.getExpire(), presig.getTimeSigned(), presig
329 .getFootprint(), presig.getSigner(), signature);
333 * Converts from a RFC 2536 formatted DSA signature to a JCE (ASN.1)
334 * formatted signature.
337 * ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R . ANS1_INT .
341 * The integers R and S may have a leading null byte to force the integer
344 * @param signature the RFC 2536 formatted DSA signature.
345 * @return The ASN.1 formatted DSA signature.
346 * @throws SignatureException if there was something wrong with the RFC 2536
347 * formatted signature.
349 public static byte[] convertDSASignature(byte[] signature)
350 throws SignatureException
352 if (signature.length != 41)
353 throw new SignatureException("RFC 2536 signature not expected length.");
358 // handle initial null byte padding.
359 if (signature[1] < 0) r_pad++;
360 if (signature[21] < 0) s_pad++;
362 // ASN.1 length = R length + S length + (2 + 2 + 2), where each 2
363 // is for a ASN.1 type-length byte pair of which there are three
365 byte sig_length = (byte) (40 + r_pad + s_pad + 6);
367 byte sig[] = new byte[sig_length];
370 sig[pos++] = ASN1_SEQ;
371 sig[pos++] = (byte) (sig_length - 2); // all but the SEQ type+length.
372 sig[pos++] = ASN1_INT;
373 sig[pos++] = (byte) (20 + r_pad);
375 // copy the value of R, leaving a null byte if necessary
376 if (r_pad == 1) sig[pos++] = 0;
378 System.arraycopy(signature, 1, sig, pos, 20);
381 sig[pos++] = ASN1_INT;
382 sig[pos++] = (byte) (20 + s_pad);
384 // copy the value of S, leaving a null byte if necessary
385 if (s_pad == 1) sig[pos++] = 0;
387 System.arraycopy(signature, 21, sig, pos, 20);
393 * Converts from a JCE (ASN.1) formatted DSA signature to a RFC 2536
394 * compliant signature.
397 * rfc2536 format = T . R . S
400 * where T is a number between 0 and 8, which is based on the DSA key
401 * length, and R & S are formatted to be exactly 20 bytes each (no leading
404 * @param params the DSA parameters associated with the DSA key used to
405 * generate the signature.
406 * @param signature the ASN.1 formatted DSA signature.
407 * @return a RFC 2536 formatted DSA signature.
408 * @throws SignatureException if something is wrong with the ASN.1 format.
410 public static byte[] convertDSASignature(DSAParams params, byte[] signature)
411 throws SignatureException
413 if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT)
415 throw new SignatureException(
416 "Invalid ASN.1 signature format: expected SEQ, INT");
419 byte r_pad = (byte) (signature[3] - 20);
421 if (signature[24 + r_pad] != ASN1_INT)
423 throw new SignatureException(
424 "Invalid ASN.1 signature format: expected SEQ, INT, INT");
427 // log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature));
429 byte s_pad = (byte) (signature[25 + r_pad] - 20);
431 byte[] sig = new byte[41]; // all rfc2536 signatures are 41 bytes.
434 sig[0] = (byte) ((params.getP().bitLength() - 512) / 64);
439 System.arraycopy(signature, 4 + r_pad, sig, 1, 20);
443 // R is shorter than 20 bytes, so right justify the number
444 // (r_pad is negative here, remember?).
445 Arrays.fill(sig, 1, 1 - r_pad, (byte) 0);
446 System.arraycopy(signature, 4, sig, 1 - r_pad, 20 + r_pad);
452 System.arraycopy(signature, 26 + r_pad + s_pad, sig, 21, 20);
456 // S is shorter than 20 bytes, so right justify the number
457 // (s_pad is negative here).
458 Arrays.fill(sig, 21, 21 - s_pad, (byte) 0);
459 System.arraycopy(signature, 26 + r_pad, sig, 21 - s_pad, 20 + s_pad);
462 // if (r_pad < 0 || s_pad < 0)
464 // log.trace("(finish ***) RFC 2536 DSA Sig:\n" + base64.toString(sig));
469 // log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig));