2003-12-26 Guilhem Lavaux <guilhem@kaffe.org>
[official-gcc.git] / libjava / gnu / java / security / der / DERReader.java
blob3020b8b2d1aeb0fb706aea5ad3453e0976890cfd
1 /* DERReader.java -- parses ASN.1 DER sequences
2 Copyright (C) 2003 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.java.security.der;
41 import java.io.BufferedInputStream;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
44 import java.io.EOFException;
45 import java.io.InputStream;
46 import java.io.IOException;
48 import java.math.BigInteger;
50 import java.nio.ByteBuffer;
51 import java.nio.CharBuffer;
52 import java.nio.charset.Charset;
53 import java.nio.charset.CharsetDecoder;
55 import java.util.Calendar;
56 import java.util.Date;
57 import java.util.TimeZone;
59 import gnu.java.security.OID;
61 /**
62 * This class decodes DER sequences into Java objects. The methods of
63 * this class do not have knowledge of higher-levels of structure in the
64 * DER stream -- such as ASN.1 constructions -- and it is therefore up
65 * to the calling application to determine if the data are structured
66 * properly by inspecting the {@link DERValue} that is returned.
68 * @author Casey Marshall (rsdio@metastatic.org)
70 public class DERReader implements DER
73 // Fields.
74 // ------------------------------------------------------------------------
76 protected InputStream in;
78 protected final ByteArrayOutputStream encBuf;
80 // Constructor.
81 // ------------------------------------------------------------------------
83 /**
84 * Create a new DER reader from a byte array.
86 * @param in The encoded bytes.
88 public DERReader(byte[] in)
90 this(new ByteArrayInputStream(in));
93 /**
94 * Create a new DER readed from an input stream.
96 * @param in The encoded bytes.
98 public DERReader(InputStream in)
100 if (!in.markSupported())
101 this.in = new BufferedInputStream(in, 16384);
102 else
103 this.in = in;
104 encBuf = new ByteArrayOutputStream(2048);
107 // Class methods.
108 // ------------------------------------------------------------------------
111 * Convenience method for reading a single primitive value from the
112 * given byte array.
114 * @param encoded The encoded bytes.
115 * @throws IOException If the bytes do not represent an encoded
116 * object.
118 public static DERValue read(byte[] encoded) throws IOException
120 return new DERReader(encoded).read();
123 // Instance methods.
124 // ------------------------------------------------------------------------
127 * Decode a single value from the input stream, returning it in a new
128 * {@link DERValue}. By "single value" we mean any single type in its
129 * entirety -- including constructed types such as SEQUENCE and all
130 * the values they contain. Usually it is sufficient to call this
131 * method once to parse and return the top-level structure, then to
132 * inspect the returned value for the proper contents.
134 * @return The parsed DER structure.
135 * @throws IOException If an error occurs reading from the input
136 * stream.
137 * @throws DEREncodingException If the input does not represent a
138 * valid DER stream.
140 public DERValue read() throws IOException
142 int tag = in.read();
143 if (tag == -1)
144 throw new EOFException();
145 encBuf.write(tag);
146 int len = readLength();
147 DERValue value = null;
148 if ((tag & CONSTRUCTED) == CONSTRUCTED)
150 in.mark(2048);
151 byte[] encoded = new byte[len];
152 in.read(encoded);
153 encBuf.write(encoded);
154 value = new DERValue(tag, len, CONSTRUCTED_VALUE, encBuf.toByteArray());
155 in.reset();
156 encBuf.reset();
157 return value;
159 switch (tag & 0xC0)
161 case UNIVERSAL:
162 value = new DERValue(tag, len, readUniversal(tag, len),
163 encBuf.toByteArray());
164 encBuf.reset();
165 break;
166 case CONTEXT:
167 byte[] encoded = new byte[len];
168 in.read(encoded);
169 encBuf.write(encoded);
170 value = new DERValue(tag, len, encoded, encBuf.toByteArray());
171 encBuf.reset();
172 break;
173 case APPLICATION:
174 // This should not be reached, since (I think) APPLICATION is
175 // always constructed.
176 throw new DEREncodingException("non-constructed APPLICATION data");
177 default:
178 throw new DEREncodingException("PRIVATE class not supported");
180 return value;
183 // Own methods.
184 // ------------------------------------------------------------------------
186 private Object readUniversal(int tag, int len) throws IOException
188 byte[] value = new byte[len];
189 in.read(value);
190 encBuf.write(value);
191 switch (tag & 0x1F)
193 case BOOLEAN:
194 if (value.length != 1)
195 throw new DEREncodingException();
196 return Boolean.valueOf(value[0] != 0);
197 case NULL:
198 if (len != 0)
199 throw new DEREncodingException();
200 return null;
201 case INTEGER:
202 case ENUMERATED:
203 return new BigInteger(value);
204 case BIT_STRING:
205 byte[] bits = new byte[len - 1];
206 System.arraycopy(value, 1, bits, 0, bits.length);
207 return new BitString(bits, value[0] & 0xFF);
208 case OCTET_STRING:
209 return value;
210 case NUMERIC_STRING:
211 case PRINTABLE_STRING:
212 case T61_STRING:
213 case VIDEOTEX_STRING:
214 case IA5_STRING:
215 case GRAPHIC_STRING:
216 case ISO646_STRING:
217 case GENERAL_STRING:
218 case UNIVERSAL_STRING:
219 case BMP_STRING:
220 case UTF8_STRING:
221 return makeString(tag, value);
222 case UTC_TIME:
223 case GENERALIZED_TIME:
224 return makeTime(tag, value);
225 case OBJECT_IDENTIFIER:
226 return new OID(value);
227 case RELATIVE_OID:
228 return new OID(value, true);
229 default:
230 throw new DEREncodingException("unknown tag " + tag);
234 private int readLength() throws IOException
236 int i = in.read();
237 if (i == -1)
238 throw new EOFException();
239 encBuf.write(i);
240 if ((i & ~0x7F) == 0)
242 return i;
244 else if (i < 0xFF)
246 byte[] octets = new byte[i & 0x7F];
247 in.read(octets);
248 encBuf.write(octets);
249 return new BigInteger(1, octets).intValue();
251 throw new DEREncodingException();
254 private String makeString(int tag, byte[] value)
255 throws IOException
257 Charset charset = null;
258 switch (tag & 0x1F)
260 case NUMERIC_STRING:
261 case PRINTABLE_STRING:
262 case T61_STRING:
263 case VIDEOTEX_STRING:
264 case IA5_STRING:
265 case GRAPHIC_STRING:
266 case ISO646_STRING:
267 case GENERAL_STRING:
268 charset = Charset.forName("ISO-8859-1");
269 break;
270 case UNIVERSAL_STRING:
271 // XXX The docs say UniversalString is encoded in four bytes
272 // per character, but Java has no support (yet) for UTF-32.
273 //return new String(buf, "UTF-32");
274 case BMP_STRING:
275 charset = Charset.forName("UTF-16BE");
276 break;
277 case UTF8_STRING:
278 charset = Charset.forName("UTF-8");
279 break;
280 default:
281 throw new DEREncodingException("unknown string tag");
283 if (charset == null)
284 throw new DEREncodingException("no decoder");
285 CharsetDecoder decoder = charset.newDecoder();
286 CharBuffer result = decoder.decode(ByteBuffer.wrap(value));
287 char[] buf = new char[result.remaining()];
288 result.get(buf);
289 return new String(buf);
292 private Date makeTime(int tag, byte[] value) throws IOException
294 Calendar calendar = Calendar.getInstance();
295 String str = makeString(PRINTABLE_STRING, value);
297 // Classpath's SimpleDateFormat does not work for parsing these
298 // types of times, so we do this by hand.
299 String date = str;
300 String tz = "";
301 if (str.indexOf("+") > 0)
303 date = str.substring(0, str.indexOf("+"));
304 tz = str.substring(str.indexOf("+"));
306 else if (str.indexOf("-") > 0)
308 date = str.substring(0, str.indexOf("-"));
309 tz = str.substring(str.indexOf("-"));
311 else if (str.endsWith("Z"))
313 date = str.substring(0, str.length()-2);
314 tz = "Z";
316 if (!tz.equals("Z") && tz.length() > 0)
317 calendar.setTimeZone(TimeZone.getTimeZone(tz));
318 else
319 calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
320 if ((tag & 0x1F) == UTC_TIME)
322 if (date.length() < 10) // must be at least 10 chars long
323 throw new DEREncodingException("cannot parse date");
324 // UTCTime is of the form "yyMMddHHmm[ss](Z|(+|-)hhmm)"
327 int year = Integer.parseInt(str.substring(0, 2));
328 if (year < 50)
329 year += 2000;
330 else
331 year += 1900;
332 calendar.set(year,
333 Integer.parseInt(str.substring( 2, 4))-1, // month
334 Integer.parseInt(str.substring( 4, 6)), // day
335 Integer.parseInt(str.substring( 6, 8)), // hour
336 Integer.parseInt(str.substring( 8, 10))); // minute
337 if (date.length() == 12);
338 calendar.set(calendar.SECOND,
339 Integer.parseInt(str.substring(10, 12)));
341 catch (NumberFormatException nfe)
343 throw new DEREncodingException("cannot parse date");
346 else
348 if (date.length() < 10) // must be at least 10 chars long
349 throw new DEREncodingException("cannot parse date");
350 // GeneralTime is of the form "yyyyMMddHH[mm[ss[(.|,)SSSS]]]"
351 // followed by "Z" or "(+|-)hh[mm]"
354 calendar.set(
355 Integer.parseInt(date.substring(0, 4)), // year
356 Integer.parseInt(date.substring(4, 6))-1, // month
357 Integer.parseInt(date.substring(6, 8)), // day
358 Integer.parseInt(date.substring(8, 10)), 0); // hour, min
359 switch (date.length())
361 case 19:
362 case 18:
363 case 17:
364 case 16:
365 calendar.set(calendar.MILLISECOND,
366 Integer.parseInt(date.substring(15)));
367 case 14:
368 calendar.set(calendar.SECOND,
369 Integer.parseInt(date.substring(12, 14)));
370 case 12:
371 calendar.set(calendar.MINUTE,
372 Integer.parseInt(date.substring(10, 12)));
375 catch (NumberFormatException nfe)
377 throw new DEREncodingException("cannot parse date");
380 return calendar.getTime();