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., 59 Temple Place, Suite 330, 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 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
;
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
74 // ------------------------------------------------------------------------
76 protected InputStream in
;
78 protected final ByteArrayOutputStream encBuf
;
81 // ------------------------------------------------------------------------
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
));
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 // ------------------------------------------------------------------------
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
137 * @throws DEREncodingException If the input does not represent a
140 public DERValue
read() throws IOException
144 throw new EOFException();
146 int len
= readLength();
147 DERValue value
= null;
148 if ((tag
& CONSTRUCTED
) == CONSTRUCTED
)
151 byte[] encoded
= new byte[len
];
153 encBuf
.write(encoded
);
154 value
= new DERValue(tag
, len
, CONSTRUCTED_VALUE
, encBuf
.toByteArray());
162 value
= new DERValue(tag
, len
, readUniversal(tag
, len
),
163 encBuf
.toByteArray());
167 byte[] encoded
= new byte[len
];
169 encBuf
.write(encoded
);
170 value
= new DERValue(tag
, len
, encoded
, encBuf
.toByteArray());
174 // This should not be reached, since (I think) APPLICATION is
175 // always constructed.
176 throw new DEREncodingException("non-constructed APPLICATION data");
178 throw new DEREncodingException("PRIVATE class not supported");
184 // ------------------------------------------------------------------------
186 private Object
readUniversal(int tag
, int len
) throws IOException
188 byte[] value
= new byte[len
];
194 if (value
.length
!= 1)
195 throw new DEREncodingException();
196 return Boolean
.valueOf(value
[0] != 0);
199 throw new DEREncodingException();
203 return new BigInteger(value
);
205 byte[] bits
= new byte[len
- 1];
206 System
.arraycopy(value
, 1, bits
, 0, bits
.length
);
207 return new BitString(bits
, value
[0] & 0xFF);
211 case PRINTABLE_STRING
:
213 case VIDEOTEX_STRING
:
218 case UNIVERSAL_STRING
:
221 return makeString(tag
, value
);
223 case GENERALIZED_TIME
:
224 return makeTime(tag
, value
);
225 case OBJECT_IDENTIFIER
:
226 return new OID(value
);
228 return new OID(value
, true);
230 throw new DEREncodingException("unknown tag " + tag
);
234 private int readLength() throws IOException
238 throw new EOFException();
240 if ((i
& ~
0x7F) == 0)
246 byte[] octets
= new byte[i
& 0x7F];
248 encBuf
.write(octets
);
249 return new BigInteger(1, octets
).intValue();
251 throw new DEREncodingException();
254 private String
makeString(int tag
, byte[] value
)
257 Charset charset
= null;
261 case PRINTABLE_STRING
:
263 case VIDEOTEX_STRING
:
268 charset
= Charset
.forName("ISO-8859-1");
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");
275 charset
= Charset
.forName("UTF-16BE");
278 charset
= Charset
.forName("UTF-8");
281 throw new DEREncodingException("unknown string tag");
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()];
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.
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);
316 if (!tz
.equals("Z") && tz
.length() > 0)
317 calendar
.setTimeZone(TimeZone
.getTimeZone(tz
));
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));
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");
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]"
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())
365 calendar
.set(calendar
.MILLISECOND
,
366 Integer
.parseInt(date
.substring(15)));
368 calendar
.set(calendar
.SECOND
,
369 Integer
.parseInt(date
.substring(12, 14)));
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();