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)
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., 51 Franklin Street, Fifth Floor, Boston, MA
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
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 gnu.java.lang.CPStringBuilder;
43 import gnu.java.security.OID;
45 import java.io.BufferedInputStream;
46 import java.io.ByteArrayInputStream;
47 import java.io.ByteArrayOutputStream;
48 import java.io.EOFException;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.math.BigInteger;
52 import java.util.Calendar;
53 import java.util.Date;
54 import java.util.TimeZone;
57 * This class decodes DER sequences into Java objects. The methods of
58 * this class do not have knowledge of higher-levels of structure in the
59 * DER stream -- such as ASN.1 constructions -- and it is therefore up
60 * to the calling application to determine if the data are structured
61 * properly by inspecting the {@link DERValue} that is returned.
63 * @author Casey Marshall (csm@gnu.org)
65 public class DERReader implements DER
69 // ------------------------------------------------------------------------
71 protected InputStream in;
73 protected final ByteArrayOutputStream encBuf;
76 // ------------------------------------------------------------------------
79 * Create a new DER reader from a byte array.
81 * @param in The encoded bytes.
83 public DERReader(byte[] in)
85 this(new ByteArrayInputStream(in));
88 public DERReader (byte[] in, int off, int len)
90 this (new ByteArrayInputStream (in, off, len));
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);
104 encBuf = new ByteArrayOutputStream(2048);
108 // ------------------------------------------------------------------------
111 * Convenience method for reading a single primitive value from the
114 * @param encoded The encoded bytes.
115 * @throws IOException If the bytes do not represent an encoded
118 public static DERValue read(byte[] encoded) throws IOException
120 return new DERReader(encoded).read();
124 // ------------------------------------------------------------------------
126 public void skip (int bytes) throws IOException
132 * Decode a single value from the input stream, returning it in a new
133 * {@link DERValue}. By "single value" we mean any single type in its
134 * entirety -- including constructed types such as SEQUENCE and all
135 * the values they contain. Usually it is sufficient to call this
136 * method once to parse and return the top-level structure, then to
137 * inspect the returned value for the proper contents.
139 * @return The parsed DER structure.
140 * @throws IOException If an error occurs reading from the input
142 * @throws DEREncodingException If the input does not represent a
145 public DERValue read() throws IOException
149 throw new EOFException();
151 int len = readLength();
152 DERValue value = null;
153 if ((tag & CONSTRUCTED) == CONSTRUCTED)
156 byte[] encoded = new byte[len];
158 encBuf.write(encoded);
159 value = new DERValue(tag, len, CONSTRUCTED_VALUE, encBuf.toByteArray());
167 value = new DERValue(tag, len, readUniversal(tag, len),
168 encBuf.toByteArray());
172 byte[] encoded = new byte[len];
174 encBuf.write(encoded);
175 value = new DERValue(tag, len, encoded, encBuf.toByteArray());
179 // This should not be reached, since (I think) APPLICATION is
180 // always constructed.
181 throw new DEREncodingException("non-constructed APPLICATION data");
183 throw new DEREncodingException("PRIVATE class not supported");
188 protected int readLength() throws IOException
192 throw new EOFException();
194 if ((i & ~0x7F) == 0)
200 byte[] octets = new byte[i & 0x7F];
202 encBuf.write(octets);
203 return new BigInteger(1, octets).intValue();
205 throw new DEREncodingException();
209 // ------------------------------------------------------------------------
211 private Object readUniversal(int tag, int len) throws IOException
213 byte[] value = new byte[len];
219 if (value.length != 1)
220 throw new DEREncodingException();
221 return Boolean.valueOf(value[0] != 0);
224 throw new DEREncodingException();
228 return new BigInteger(value);
230 byte[] bits = new byte[len - 1];
231 System.arraycopy(value, 1, bits, 0, bits.length);
232 return new BitString(bits, value[0] & 0xFF);
236 case PRINTABLE_STRING:
238 case VIDEOTEX_STRING:
243 case UNIVERSAL_STRING:
246 return makeString(tag, value);
248 case GENERALIZED_TIME:
249 return makeTime(tag, value);
250 case OBJECT_IDENTIFIER:
251 return new OID(value);
253 return new OID(value, true);
255 throw new DEREncodingException("unknown tag " + tag);
259 private static String makeString(int tag, byte[] value)
265 case PRINTABLE_STRING:
267 case VIDEOTEX_STRING:
272 return fromIso88591(value);
274 case UNIVERSAL_STRING:
275 // XXX The docs say UniversalString is encoded in four bytes
276 // per character, but Java has no support (yet) for UTF-32.
277 //return new String(buf, "UTF-32");
279 return fromUtf16Be(value);
282 return fromUtf8(value);
285 throw new DEREncodingException("unknown string tag");
289 private static String fromIso88591(byte[] bytes)
291 CPStringBuilder str = new CPStringBuilder(bytes.length);
292 for (int i = 0; i < bytes.length; i++)
293 str.append((char) (bytes[i] & 0xFF));
294 return str.toString();
297 private static String fromUtf16Be(byte[] bytes) throws IOException
299 if ((bytes.length & 0x01) != 0)
300 throw new IOException("UTF-16 bytes are odd in length");
301 CPStringBuilder str = new CPStringBuilder(bytes.length / 2);
302 for (int i = 0; i < bytes.length; i += 2)
304 char c = (char) ((bytes[i] << 8) & 0xFF);
305 c |= (char) (bytes[i+1] & 0xFF);
308 return str.toString();
311 private static String fromUtf8(byte[] bytes) throws IOException
313 CPStringBuilder str = new CPStringBuilder((int)(bytes.length / 1.5));
314 for (int i = 0; i < bytes.length; )
317 if ((bytes[i] & 0xE0) == 0xE0)
319 if ((i + 2) >= bytes.length)
320 throw new IOException("short UTF-8 input");
321 c = (char) ((bytes[i++] & 0x0F) << 12);
322 if ((bytes[i] & 0x80) != 0x80)
323 throw new IOException("malformed UTF-8 input");
324 c |= (char) ((bytes[i++] & 0x3F) << 6);
325 if ((bytes[i] & 0x80) != 0x80)
326 throw new IOException("malformed UTF-8 input");
327 c |= (char) (bytes[i++] & 0x3F);
329 else if ((bytes[i] & 0xC0) == 0xC0)
331 if ((i + 1) >= bytes.length)
332 throw new IOException("short input");
333 c = (char) ((bytes[i++] & 0x1F) << 6);
334 if ((bytes[i] & 0x80) != 0x80)
335 throw new IOException("malformed UTF-8 input");
336 c |= (char) (bytes[i++] & 0x3F);
338 else if ((bytes[i] & 0xFF) < 0x80)
340 c = (char) (bytes[i++] & 0xFF);
343 throw new IOException("badly formed UTF-8 sequence");
346 return str.toString();
349 private Date makeTime(int tag, byte[] value) throws IOException
351 Calendar calendar = Calendar.getInstance();
352 String str = makeString(PRINTABLE_STRING, value);
354 // Classpath's SimpleDateFormat does not work for parsing these
355 // types of times, so we do this by hand.
358 if (str.indexOf("+") > 0)
360 date = str.substring(0, str.indexOf("+"));
361 tz = str.substring(str.indexOf("+"));
363 else if (str.indexOf("-") > 0)
365 date = str.substring(0, str.indexOf("-"));
366 tz = str.substring(str.indexOf("-"));
368 else if (str.endsWith("Z"))
370 date = str.substring(0, str.length()-2);
373 if (!tz.equals("Z") && tz.length() > 0)
374 calendar.setTimeZone(TimeZone.getTimeZone(tz));
376 calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
377 if ((tag & 0x1F) == UTC_TIME)
379 if (date.length() < 10) // must be at least 10 chars long
380 throw new DEREncodingException("cannot parse date");
381 // UTCTime is of the form "yyMMddHHmm[ss](Z|(+|-)hhmm)"
384 int year = Integer.parseInt(str.substring(0, 2));
390 Integer.parseInt(str.substring( 2, 4))-1, // month
391 Integer.parseInt(str.substring( 4, 6)), // day
392 Integer.parseInt(str.substring( 6, 8)), // hour
393 Integer.parseInt(str.substring( 8, 10))); // minute
394 if (date.length() == 12)
395 calendar.set(Calendar.SECOND,
396 Integer.parseInt(str.substring(10, 12)));
398 catch (NumberFormatException nfe)
400 throw new DEREncodingException("cannot parse date");
405 if (date.length() < 10) // must be at least 10 chars long
406 throw new DEREncodingException("cannot parse date");
407 // GeneralTime is of the form "yyyyMMddHH[mm[ss[(.|,)SSSS]]]"
408 // followed by "Z" or "(+|-)hh[mm]"
412 Integer.parseInt(date.substring(0, 4)), // year
413 Integer.parseInt(date.substring(4, 6))-1, // month
414 Integer.parseInt(date.substring(6, 8)), // day
415 Integer.parseInt(date.substring(8, 10)), 0); // hour, min
416 switch (date.length())
422 calendar.set(Calendar.MILLISECOND,
423 Integer.parseInt(date.substring(15)));
425 calendar.set(Calendar.SECOND,
426 Integer.parseInt(date.substring(12, 14)));
428 calendar.set(Calendar.MINUTE,
429 Integer.parseInt(date.substring(10, 12)));
432 catch (NumberFormatException nfe)
434 throw new DEREncodingException("cannot parse date");
437 return calendar.getTime();