cleanup
[org.ibex.crypto.git] / src / org / ibex / crypto / DER.java
1 /*
2  * org.ibex.crypto.DER - By Brian Alliet
3  * Copyright (C) 2004 Brian Alliet
4  * 
5  * Based on Bouncy Castle by The Legion Of The Bouncy Castle
6  * Copyright (c) 2000 The Legion Of The Bouncy Castle 
7  * (http://www.bouncycastle.org)
8  * 
9  * Permission is hereby granted, free of charge, to any person obtaining a 
10  * copy of this software and associated documentation files (the "Software"),
11  * to deal in the Software without restriction, including without limitation 
12  * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
13  * and/or sell copies of the Software, and to permit persons to whom the 
14  * Software is furnished to do so, subject to the following conditions:
15  * 
16  * The above copyright notice and this permission notice shall be included in
17  * all copies or substantial portions of the Software.
18  * 
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  * AUTHORS OR COPYRIGHT HOLDER.S BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
25  * DEALINGS IN THE SOFTWARE.
26  */
27 package org.ibex.crypto;
28 import java.io.IOException;
29 import java.io.*;
30 import java.text.ParseException;
31 import java.text.SimpleDateFormat;
32 import java.util.*;
33 import java.math.BigInteger;
34
35 public class DER {
36     private DER() { }
37     
38     public static class Null {
39         final static Null instance = new Null();
40         private Null() { /* noop */ }
41         public boolean equals(Object o) { return o == this; }
42     }
43
44     public static class TaggedObject {
45         public final Object object;
46         public final int tag;
47         public TaggedObject(int tag, Object object) { this.tag = tag; this.object = object; }
48     }
49
50     public static class UnknownObject {
51         public final byte[] data;
52         public final int tag;
53         public UnknownObject(int tag,byte[] data) { this.tag = tag; this.data = data; }
54     }
55
56     public static class BitString {
57         public final int paddingBits;
58         public final byte[] data;
59     
60         public BitString(int paddingBits, byte[] data) {
61             this.paddingBits = paddingBits;
62             this.data = data;
63         }
64     }
65
66     public static class Exception extends java.io.IOException {
67         public Exception(String s) { super(s); }
68     }
69
70     public static class InputStream extends FilterInputStream {
71         private static final int MAX_OBJECT_SIZE = 4*1024*1024;
72     
73         private int limit;
74         public int bytesLeft() { return limit < 0 ? Integer.MAX_VALUE : limit-pos; }
75         private int pos;
76         public int getPos() { return pos; }
77     
78         // hack around gcj bug
79         public static DER.InputStream New(java.io.InputStream is) { return new DER.InputStream(is); }
80         public InputStream(java.io.InputStream is) { this(is,-1); } 
81         public InputStream(java.io.InputStream is, int limit) {
82             super(is);
83             this.limit = limit;
84         }
85     
86         public int read() throws IOException {
87             if(limit >= 0 && pos >= limit) return -1;
88             int n = super.read();
89             if(n != -1) {
90                 pos++;
91             }
92             return n;
93         }
94     
95         public int read(byte[] buf, int start, int len) throws IOException {
96             if(limit >= 0) {
97                 if(pos >= limit) return -1;
98                 len = Math.min(len,limit-pos);
99             }
100             int n = super.read(buf,start,len);
101             if(n != -1) {
102                 pos += n;
103             }
104             return n;
105         }
106     
107         protected void readFully(byte[] buf) throws IOException {
108             int pos = 0;
109             int left = buf.length;
110             while(left > 0) {
111                 int n = read(buf,pos,left);
112                 if(n == -1) throw new EOFException();
113                 pos += n;
114                 left -=n;
115             }
116         }
117     
118         protected int readByte() throws IOException {
119             int n = read();
120             if(n == -1) throw new EOFException();
121             return n;
122         }
123     
124         // From bouncycastle
125         private int readLength() throws IOException {
126             int length = read();
127             if (length < 0) throw new IOException("EOF found when length expected");
128             if (length == 0x80) return -1;      // indefinite-length encoding
129             if (length > 127) {
130                 int size = length & 0x7f;
131                 length = 0;
132                 for (int i = 0; i < size; i++) {
133                     int next = read();
134                     if (next < 0) throw new IOException("EOF found reading length");    
135                     length = (length << 8) + next;
136                 }
137             }
138             return length;
139         }
140         
141         public InputStream getSequenceStream() throws IOException {
142             int tag = readByte();
143             int length = readLength();
144             if(length < 0) throw new Exception("Indefinite length objects not supported");
145             if(tag != (CONSTRUCTED|0x10)) throw new Exception("Constructed Sequence expected");
146             return new InputStream(this,length);
147         }
148     
149         private static final int CONSTRUCTED = 0x20;    
150         public Object readObject() throws IOException {
151             int tag = readByte();        
152             int length = readLength();
153             if(length < 0) throw new Exception("Indefinite length objects not supported");
154             if(length > MAX_OBJECT_SIZE) throw new Exception("Object too large");
155         
156             switch(tag) {
157                 case 0x01: return buildBoolean(length); // Boolean
158                 case 0x02: return buildInteger(length); // Integer
159                 case 0x03: return buildBitString(length); // Bit String
160                 case 0x04: return buildOctetString(length); // Octet String
161                 case 0x05: return Null.instance; // NULL
162                 case 0x06: return buildObjectIdentifier(length); // Object Identifier
163                 
164                 case 0x13: // PrintableString
165                     // It is incorrect to treat this as an IA5String but the T.61 standard is way too old and backwards
166                     // to be worth supporting
167                 case 0x14: // T.61 String 
168                 case 0x16: // IA5String
169                     return buildIA5String(length);
170                 case 0x17: return buildTime(length,false);// UTC Time
171                 case 0x18: return buildTime(length,true); // Generalizd Time
172                 
173                 case CONSTRUCTED | 0x10: // Constructed Sequence
174                 case CONSTRUCTED | 0x11: // Constructed Set
175                     { 
176                         return buildSequence(length);
177                     }
178                 default: {
179                     if((tag & 0x80) != 0) {
180                         if ((tag & 0x1f) == 0x1f) throw new Exception("Unsupported high tag ecountered");
181                         // tagged object - bottom 5 bits are tag
182                         if(length == 0)
183                             return new TaggedObject(tag&0x1,((tag & CONSTRUCTED) == 0) ? (Object)Null.instance : (Object)new Vector());
184                         if((tag & CONSTRUCTED) == 0)
185                             return new TaggedObject(tag&0x1f,buildOctetString(length));
186                  
187                         InputStream dis = new InputStream(this,length);
188                         Object o = dis.readObject();
189                         if(dis.bytesLeft() == 0) return new TaggedObject(tag&0x1f,o);
190                         Vector v = new Vector();
191                         v.addElement(o);
192                         return buildSequence(dis,v);
193                     } else {
194                         return new UnknownObject(tag,readBytes(length));
195                     }
196                 }     
197             }
198         }
199
200         protected Vector buildSequence(int length) throws IOException { return buildSequence(new InputStream(this,length),new Vector()); }
201         protected Vector buildSequence(InputStream dis,Vector v) throws IOException {
202             try {
203                 for(;;) v.addElement(dis.readObject());
204             } catch(EOFException e) { 
205                 return v; 
206             }
207         }    
208     
209         protected byte[] readBytes(int length) throws IOException {
210             byte[] buf = new byte[length];
211             readFully(buf);
212             return buf;
213         }
214     
215         protected BigInteger buildInteger(int length) throws IOException { return new BigInteger(readBytes(length)); }
216     
217         // From bouncycastle
218         protected String buildObjectIdentifier(int length) throws IOException {
219             byte[] bytes = readBytes(length);
220             StringBuffer    objId = new StringBuffer();
221             int             value = 0;
222             boolean         first = true;
223
224             for (int i = 0; i != bytes.length; i++)
225                 {
226                     int b = bytes[i] & 0xff;
227
228                     value = value * 128 + (b & 0x7f);
229                     if ((b & 0x80) == 0)             // end of number reached
230                         {
231                             if (first)
232                                 {
233                                     switch (value / 40)
234                                         {
235                                             case 0:
236                                                 objId.append('0');
237                                                 break;
238                                             case 1:
239                                                 objId.append('1');
240                                                 value -= 40;
241                                                 break;
242                                             default:
243                                                 objId.append('2');
244                                                 value -= 80;
245                                         }
246                                     first = false;
247                                 }
248
249                             objId.append('.');
250                             objId.append(Integer.toString(value));
251                             value = 0;
252                         }
253                 }
254             return objId.toString();
255         }
256     
257         protected String buildIA5String(int length) throws IOException {
258             byte[] buf = readBytes(length);
259             char[] buf2 = new char[buf.length];
260             for(int i=0;i<buf.length;i++) buf2[i] = (char)(buf[i]&0xff);
261             return new String(buf2);
262         }
263     
264         private static final SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
265         protected Date buildTime(int length, boolean generalized) throws IOException {
266             String s = buildIA5String(length);
267             if(!generalized && s.length() > 0) {
268                 if(s.charAt(0) < '5') s = "20" + s;
269                 else s = "19" + s;
270             }
271             switch(s.length()) {
272                 case 13: s = s.substring(0,12) + "00GMT+00:00"; break; //  YYYYMMDDhhmmZ
273                 case 15: s = s.substring(0,14) + "GMT+00:00";   break; // YYYYMMDDhhmmssZ
274                 case 17: s = s.substring(0,12) + "00GMT" + s.substring(12,15) + ":" + s.substring(15,17); break;  // YYYYMMDDhhmm+hh'mm'
275                 case 19: s = s.substring(0,14) + "GMT" + s.substring(14, 17) + ":" + s.substring(17, 19); // YYYYMMDDhhmmss+hh'mm'
276                 default: throw new Exception("Unknown time format " + s);
277             }
278             try {
279                 return dateF.parse(s);
280             } catch(ParseException e) {
281                 throw new Exception("Coudln't parse time: " + e.getMessage());
282             }
283         }
284     
285         protected BitString buildBitString(int length) throws IOException {
286             if(length < 1) throw new Exception("bit string too short");
287             int padding = read();
288             if(padding == -1) throw new IOException("unexpected eof");
289             return new BitString(padding,readBytes(length-1));
290         }
291     
292         protected byte[] buildOctetString(int length) throws IOException { return readBytes(length); }
293     
294         protected Boolean buildBoolean(int length) throws IOException {
295             byte[] bytes = readBytes(length);
296             return bytes[0] != 0 ? Boolean.TRUE : Boolean.FALSE;
297         }
298     
299         /*
300           public static void main(String[] args) throws Exception {
301           InputStream is = new InputStream(new FileInputStream(args[0]));
302           try {
303           for(;;) dump(is.readObject(),"");
304           } catch(EOFException e) {
305           System.err.println("EOF");
306           }
307           }
308           public static void dump(Object o, String indent) {
309           if(o instanceof Vector) {
310           Vector v = (Vector) o;
311           System.out.println(indent + "Sequence/Set");
312           for(int i=0;i<v.size();i++) {
313           dump(v.elementAt(i),indent + i + ":  ");
314           }
315           } else if(o instanceof TaggedObject){
316           dump(((TaggedObject)o).object,indent + "Tagged object: ");
317           } else if(o instanceof byte[]) {
318           System.err.println(indent + "<Byte Array>");
319           } else {
320           System.err.println(indent + o.toString());
321           }
322           }*/
323     }
324 }