83164073187b7cffd0cbf0c55da2d78920c28a8d
[captive-validator.git] / src / com / versign / tat / dnssec / SignUtils.java
1 /*
2  * $Id$
3  *
4  * Copyright (c) 2005 VeriSign, Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
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.
17  *
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.
28  *
29  */
30
31 package com.versign.tat.dnssec;
32
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;
43
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
51 /**
52  * This class contains a bunch of utility methods that are generally useful in
53  * signing and verifying rrsets.
54  */
55
56 public class SignUtils {
57
58     /**
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
61      * canonical ordering.
62      */
63     public static class ByteArrayComparator implements Comparator<byte[]> {
64         private int     mOffset = 0;
65         private boolean mDebug  = false;
66
67         public ByteArrayComparator() {
68         }
69
70         public ByteArrayComparator(int offset, boolean debug) {
71             mOffset = offset;
72             mDebug = debug;
73         }
74
75         public int compare(byte[] b1, byte[] b2) throws ClassCastException {
76             for (int i = mOffset; i < b1.length && i < b2.length; i++) {
77                 if (b1[i] != b2[i]) {
78                     if (mDebug) {
79                         System.out.println("offset " + i + " differs (this is "
80                                            + (i - mOffset)
81                                            + " bytes in from our offset.)");
82                     }
83                     return (b1[i] & 0xFF) - (b2[i] & 0xFF);
84                 }
85             }
86
87             return b1.length - b2.length;
88         }
89     }
90
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;
94
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;
99
100     /**
101      * Generate from some basic information a prototype SIG RR containing
102      * everything but the actual signature itself.
103      * 
104      * @param rrset
105      *            the RRset being signed.
106      * @param signer
107      *            the name of the signing key
108      * @param alg
109      *            the algorithm of the signing key
110      * @param keyid
111      *            the keyid (or footprint) of the signing key
112      * @param start
113      *            the SIG inception time.
114      * @param expire
115      *            the SIG expiration time.
116      * @param sig_ttl
117      *            the TTL of the resulting SIG record.
118      * @return a prototype signature based on the RRset and key information.
119      */
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,
125                 signer, null);
126     }
127
128     /**
129      * Generate from some basic information a prototype SIG RR containing
130      * everything but the actual signature itself.
131      * 
132      * @param rrset
133      *            the RRset being signed.
134      * @param key
135      *            the public KEY RR counterpart to the key being used to sign
136      *            the RRset
137      * @param start
138      *            the SIG inception time.
139      * @param expire
140      *            the SIG expiration time.
141      * @param sig_ttl
142      *            the TTL of the resulting SIG record.
143      * @return a prototype signature based on the RRset and key information.
144      */
145     public static RRSIGRecord generatePreRRSIG(RRset rrset, DNSKEYRecord key,
146                                                Date start, Date expire,
147                                                long sig_ttl) {
148         return generatePreRRSIG(rrset, key.getName(), key.getAlgorithm(),
149                                 key.getFootprint(), start, expire, sig_ttl);
150     }
151
152     /**
153      * Generate from some basic information a prototype SIG RR containing
154      * everything but the actual signature itself.
155      * 
156      * @param rec
157      *            the DNS record being signed (forming an entire RRset).
158      * @param key
159      *            the public KEY RR counterpart to the key signing the record.
160      * @param start
161      *            the SIG inception time.
162      * @param expire
163      *            the SIG expiration time.
164      * @param sig_ttl
165      *            the TTL of the result SIG record.
166      * @return a prototype signature based on the Record and key information.
167      */
168     public static RRSIGRecord generatePreRRSIG(Record rec, DNSKEYRecord key,
169                                                Date start, Date expire,
170                                                long sig_ttl) {
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);
174     }
175
176     /**
177      * Generate the binary image of the prototype SIG RR.
178      * 
179      * @param presig
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.
183      */
184     private static byte[] generatePreSigRdata(RRSIGRecord presig) {
185         // Generate the binary image;
186         DNSOutput image = new DNSOutput();
187
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();
192
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());
203
204         return image.toByteArray();
205     }
206
207     /**
208      * Calculate the canonical wire line format of the RRset.
209      * 
210      * @param rrset
211      *            the RRset to convert.
212      * @param ttl
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.
216      * @param labels
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.
220      */
221     @SuppressWarnings("unchecked")
222     public static byte[] generateCanonicalRRsetData(RRset rrset, long ttl,
223                                                     int labels) {
224         DNSOutput image = new DNSOutput();
225
226         if (ttl == 0) ttl = rrset.getTTL();
227         Name n = rrset.getName();
228         if (labels == 0) {
229             labels = n.labels();
230         } else {
231             // correct for Name()'s conception of label count.
232             labels++;
233         }
234         boolean wildcardName = false;
235         if (n.labels() != labels) {
236             n = n.wild(n.labels() - labels);
237             wildcardName = true;
238             // log.trace("Detected wildcard expansion: " + rrset.getName() +
239             // " changed to " + n);
240         }
241
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
249                 // or ownername.
250                 // In the TTL case, this avoids changing the ttl in the
251                 // response.
252                 r = Record.newRecord(n, r.getType(), r.getDClass(), ttl,
253                                      r.rdataToWireCanonical());
254             }
255             byte[] wire_fmt = r.toWireCanonical();
256             canonical_rrs.add(wire_fmt);
257         }
258
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)
262
263         int offset = rrset.getName().toWireCanonical().length + 10;
264         ByteArrayComparator bac = new ByteArrayComparator(offset, false);
265
266         Collections.sort(canonical_rrs, bac);
267
268         for (Iterator<byte[]> i = canonical_rrs.iterator(); i.hasNext();) {
269             byte[] wire_fmt_rec = i.next();
270             image.writeByteArray(wire_fmt_rec);
271         }
272
273         return image.toByteArray();
274     }
275
276     /**
277      * Given an RRset and the prototype signature, generate the canonical data
278      * that is to be signed.
279      * 
280      * @param rrset
281      *            the RRset to be signed.
282      * @param presig
283      *            a prototype SIG RR created using the same RRset.
284      * @return a block of data ready to be signed.
285      */
286     public static byte[] generateSigData(RRset rrset, RRSIGRecord presig)
287             throws IOException {
288         byte[] rrset_data = generateCanonicalRRsetData(rrset,
289                                                        presig.getOrigTTL(),
290                                                        presig.getLabels());
291
292         return generateSigData(rrset_data, presig);
293     }
294
295     /**
296      * Given an RRset and the prototype signature, generate the canonical data
297      * that is to be signed.
298      * 
299      * @param rrset_data
300      *            the RRset converted into canonical wire line format (as per
301      *            the canonicalization rules in RFC 2535).
302      * @param presig
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.
306      */
307     public static byte[] generateSigData(byte[] rrset_data, RRSIGRecord presig)
308             throws IOException {
309         byte[] sig_rdata = generatePreSigRdata(presig);
310
311         ByteArrayOutputStream image = new ByteArrayOutputStream(
312                 sig_rdata.length + rrset_data.length);
313
314         image.write(sig_rdata);
315         image.write(rrset_data);
316
317         return image.toByteArray();
318     }
319
320     /**
321      * Given the actual signature and the prototype signature, combine them and
322      * return the fully formed RRSIGRecord.
323      * 
324      * @param signature
325      *            the cryptographic signature, in DNSSEC format.
326      * @param presig
327      *            the prototype RRSIG RR to add the signature to.
328      * @return the fully formed RRSIG RR.
329      */
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);
336     }
337
338     /**
339      * Converts from a RFC 2536 formatted DSA signature to a JCE (ASN.1)
340      * formatted signature.
341      * 
342      * <p>
343      * ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R . ANS1_INT
344      * . Slength . S
345      * </p>
346      * 
347      * The integers R and S may have a leading null byte to force the integer
348      * positive.
349      * 
350      * @param signature
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
355      *             signature.
356      */
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.");
362
363         byte r_pad = 0;
364         byte s_pad = 0;
365
366         // handle initial null byte padding.
367         if (signature[1] < 0) r_pad++;
368         if (signature[21] < 0) s_pad++;
369
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
372         // (SEQ, INT, INT).
373         byte sig_length = (byte) (40 + r_pad + s_pad + 6);
374
375         byte sig[] = new byte[sig_length];
376         byte pos = 0;
377
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);
382
383         // copy the value of R, leaving a null byte if necessary
384         if (r_pad == 1) sig[pos++] = 0;
385
386         System.arraycopy(signature, 1, sig, pos, 20);
387         pos += 20;
388
389         sig[pos++] = ASN1_INT;
390         sig[pos++] = (byte) (20 + s_pad);
391
392         // copy the value of S, leaving a null byte if necessary
393         if (s_pad == 1) sig[pos++] = 0;
394
395         System.arraycopy(signature, 21, sig, pos, 20);
396
397         return sig;
398     }
399
400     /**
401      * Converts from a JCE (ASN.1) formatted DSA signature to a RFC 2536
402      * compliant signature.
403      * 
404      * <p>
405      * rfc2536 format = T . R . S
406      * </p>
407      * 
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
410      * null bytes).
411      * 
412      * @param params
413      *            the DSA parameters associated with the DSA key used to
414      *            generate the signature.
415      * @param 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.
420      */
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");
426         }
427
428         byte r_pad = (byte) (signature[3] - 20);
429
430         if (signature[24 + r_pad] != ASN1_INT) {
431             throw new SignatureException(
432                     "Invalid ASN.1 signature format: expected SEQ, INT, INT");
433         }
434
435         // log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature));
436
437         byte s_pad = (byte) (signature[25 + r_pad] - 20);
438
439         byte[] sig = new byte[41]; // all rfc2536 signatures are 41 bytes.
440
441         // Calculate T:
442         sig[0] = (byte) ((params.getP().bitLength() - 512) / 64);
443
444         // copy R value
445         if (r_pad >= 0) {
446             System.arraycopy(signature, 4 + r_pad, sig, 1, 20);
447         } else {
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);
452         }
453
454         // copy S value
455         if (s_pad >= 0) {
456             System.arraycopy(signature, 26 + r_pad + s_pad, sig, 21, 20);
457         } else {
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);
462         }
463
464         // if (r_pad < 0 || s_pad < 0)
465         // {
466         // log.trace("(finish ***) RFC 2536 DSA Sig:\n" + base64.toString(sig));
467         //
468         // }
469         // else
470         // {
471         // log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig));
472         // }
473
474         return sig;
475     }
476 }