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
.security
.OID
;
43 import java
.io
.BufferedInputStream
;
44 import java
.io
.ByteArrayInputStream
;
45 import java
.io
.ByteArrayOutputStream
;
46 import java
.io
.EOFException
;
47 import java
.io
.IOException
;
48 import java
.io
.InputStream
;
49 import java
.math
.BigInteger
;
50 import java
.util
.Calendar
;
51 import java
.util
.Date
;
52 import java
.util
.TimeZone
;
55 * This class decodes DER sequences into Java objects. The methods of
56 * this class do not have knowledge of higher-levels of structure in the
57 * DER stream -- such as ASN.1 constructions -- and it is therefore up
58 * to the calling application to determine if the data are structured
59 * properly by inspecting the {@link DERValue} that is returned.
61 * @author Casey Marshall (csm@gnu.org)
63 public class DERReader
implements DER
67 // ------------------------------------------------------------------------
69 protected InputStream in
;
71 protected final ByteArrayOutputStream encBuf
;
74 // ------------------------------------------------------------------------
77 * Create a new DER reader from a byte array.
79 * @param in The encoded bytes.
81 public DERReader(byte[] in
)
83 this(new ByteArrayInputStream(in
));
86 public DERReader (byte[] in
, int off
, int len
)
88 this (new ByteArrayInputStream (in
, off
, len
));
92 * Create a new DER readed from an input stream.
94 * @param in The encoded bytes.
96 public DERReader(InputStream in
)
98 if (!in
.markSupported())
99 this.in
= new BufferedInputStream(in
, 16384);
102 encBuf
= new ByteArrayOutputStream(2048);
106 // ------------------------------------------------------------------------
109 * Convenience method for reading a single primitive value from the
112 * @param encoded The encoded bytes.
113 * @throws IOException If the bytes do not represent an encoded
116 public static DERValue
read(byte[] encoded
) throws IOException
118 return new DERReader(encoded
).read();
122 // ------------------------------------------------------------------------
124 public void skip (int bytes
) throws IOException
130 * Decode a single value from the input stream, returning it in a new
131 * {@link DERValue}. By "single value" we mean any single type in its
132 * entirety -- including constructed types such as SEQUENCE and all
133 * the values they contain. Usually it is sufficient to call this
134 * method once to parse and return the top-level structure, then to
135 * inspect the returned value for the proper contents.
137 * @return The parsed DER structure.
138 * @throws IOException If an error occurs reading from the input
140 * @throws DEREncodingException If the input does not represent a
143 public DERValue
read() throws IOException
147 throw new EOFException();
149 int len
= readLength();
150 DERValue value
= null;
151 if ((tag
& CONSTRUCTED
) == CONSTRUCTED
)
154 byte[] encoded
= new byte[len
];
156 encBuf
.write(encoded
);
157 value
= new DERValue(tag
, len
, CONSTRUCTED_VALUE
, encBuf
.toByteArray());
165 value
= new DERValue(tag
, len
, readUniversal(tag
, len
),
166 encBuf
.toByteArray());
170 byte[] encoded
= new byte[len
];
172 encBuf
.write(encoded
);
173 value
= new DERValue(tag
, len
, encoded
, encBuf
.toByteArray());
177 // This should not be reached, since (I think) APPLICATION is
178 // always constructed.
179 throw new DEREncodingException("non-constructed APPLICATION data");
181 throw new DEREncodingException("PRIVATE class not supported");
186 protected int readLength() throws IOException
190 throw new EOFException();
192 if ((i
& ~
0x7F) == 0)
198 byte[] octets
= new byte[i
& 0x7F];
200 encBuf
.write(octets
);
201 return new BigInteger(1, octets
).intValue();
203 throw new DEREncodingException();
207 // ------------------------------------------------------------------------
209 private Object
readUniversal(int tag
, int len
) throws IOException
211 byte[] value
= new byte[len
];
217 if (value
.length
!= 1)
218 throw new DEREncodingException();
219 return Boolean
.valueOf(value
[0] != 0);
222 throw new DEREncodingException();
226 return new BigInteger(value
);
228 byte[] bits
= new byte[len
- 1];
229 System
.arraycopy(value
, 1, bits
, 0, bits
.length
);
230 return new BitString(bits
, value
[0] & 0xFF);
234 case PRINTABLE_STRING
:
236 case VIDEOTEX_STRING
:
241 case UNIVERSAL_STRING
:
244 return makeString(tag
, value
);
246 case GENERALIZED_TIME
:
247 return makeTime(tag
, value
);
248 case OBJECT_IDENTIFIER
:
249 return new OID(value
);
251 return new OID(value
, true);
253 throw new DEREncodingException("unknown tag " + tag
);
257 private static String
makeString(int tag
, byte[] value
)
263 case PRINTABLE_STRING
:
265 case VIDEOTEX_STRING
:
270 return fromIso88591(value
);
272 case UNIVERSAL_STRING
:
273 // XXX The docs say UniversalString is encoded in four bytes
274 // per character, but Java has no support (yet) for UTF-32.
275 //return new String(buf, "UTF-32");
277 return fromUtf16Be(value
);
280 return fromUtf8(value
);
283 throw new DEREncodingException("unknown string tag");
287 private static String
fromIso88591(byte[] bytes
)
289 StringBuffer str
= new StringBuffer(bytes
.length
);
290 for (int i
= 0; i
< bytes
.length
; i
++)
291 str
.append((char) (bytes
[i
] & 0xFF));
292 return str
.toString();
295 private static String
fromUtf16Be(byte[] bytes
) throws IOException
297 if ((bytes
.length
& 0x01) != 0)
298 throw new IOException("UTF-16 bytes are odd in length");
299 StringBuffer str
= new StringBuffer(bytes
.length
/ 2);
300 for (int i
= 0; i
< bytes
.length
; i
+= 2)
302 char c
= (char) ((bytes
[i
] << 8) & 0xFF);
303 c
|= (char) (bytes
[i
+1] & 0xFF);
306 return str
.toString();
309 private static String
fromUtf8(byte[] bytes
) throws IOException
311 StringBuffer str
= new StringBuffer((int)(bytes
.length
/ 1.5));
312 for (int i
= 0; i
< bytes
.length
; )
315 if ((bytes
[i
] & 0xE0) == 0xE0)
317 if ((i
+ 2) >= bytes
.length
)
318 throw new IOException("short UTF-8 input");
319 c
= (char) ((bytes
[i
++] & 0x0F) << 12);
320 if ((bytes
[i
] & 0x80) != 0x80)
321 throw new IOException("malformed UTF-8 input");
322 c
|= (char) ((bytes
[i
++] & 0x3F) << 6);
323 if ((bytes
[i
] & 0x80) != 0x80)
324 throw new IOException("malformed UTF-8 input");
325 c
|= (char) (bytes
[i
++] & 0x3F);
327 else if ((bytes
[i
] & 0xC0) == 0xC0)
329 if ((i
+ 1) >= bytes
.length
)
330 throw new IOException("short input");
331 c
= (char) ((bytes
[i
++] & 0x1F) << 6);
332 if ((bytes
[i
] & 0x80) != 0x80)
333 throw new IOException("malformed UTF-8 input");
334 c
|= (char) (bytes
[i
++] & 0x3F);
336 else if ((bytes
[i
] & 0xFF) < 0x80)
338 c
= (char) (bytes
[i
++] & 0xFF);
341 throw new IOException("badly formed UTF-8 sequence");
344 return str
.toString();
347 private Date
makeTime(int tag
, byte[] value
) throws IOException
349 Calendar calendar
= Calendar
.getInstance();
350 String str
= makeString(PRINTABLE_STRING
, value
);
352 // Classpath's SimpleDateFormat does not work for parsing these
353 // types of times, so we do this by hand.
356 if (str
.indexOf("+") > 0)
358 date
= str
.substring(0, str
.indexOf("+"));
359 tz
= str
.substring(str
.indexOf("+"));
361 else if (str
.indexOf("-") > 0)
363 date
= str
.substring(0, str
.indexOf("-"));
364 tz
= str
.substring(str
.indexOf("-"));
366 else if (str
.endsWith("Z"))
368 date
= str
.substring(0, str
.length()-2);
371 if (!tz
.equals("Z") && tz
.length() > 0)
372 calendar
.setTimeZone(TimeZone
.getTimeZone(tz
));
374 calendar
.setTimeZone(TimeZone
.getTimeZone("UTC"));
375 if ((tag
& 0x1F) == UTC_TIME
)
377 if (date
.length() < 10) // must be at least 10 chars long
378 throw new DEREncodingException("cannot parse date");
379 // UTCTime is of the form "yyMMddHHmm[ss](Z|(+|-)hhmm)"
382 int year
= Integer
.parseInt(str
.substring(0, 2));
388 Integer
.parseInt(str
.substring( 2, 4))-1, // month
389 Integer
.parseInt(str
.substring( 4, 6)), // day
390 Integer
.parseInt(str
.substring( 6, 8)), // hour
391 Integer
.parseInt(str
.substring( 8, 10))); // minute
392 if (date
.length() == 12)
393 calendar
.set(Calendar
.SECOND
,
394 Integer
.parseInt(str
.substring(10, 12)));
396 catch (NumberFormatException nfe
)
398 throw new DEREncodingException("cannot parse date");
403 if (date
.length() < 10) // must be at least 10 chars long
404 throw new DEREncodingException("cannot parse date");
405 // GeneralTime is of the form "yyyyMMddHH[mm[ss[(.|,)SSSS]]]"
406 // followed by "Z" or "(+|-)hh[mm]"
410 Integer
.parseInt(date
.substring(0, 4)), // year
411 Integer
.parseInt(date
.substring(4, 6))-1, // month
412 Integer
.parseInt(date
.substring(6, 8)), // day
413 Integer
.parseInt(date
.substring(8, 10)), 0); // hour, min
414 switch (date
.length())
420 calendar
.set(Calendar
.MILLISECOND
,
421 Integer
.parseInt(date
.substring(15)));
423 calendar
.set(Calendar
.SECOND
,
424 Integer
.parseInt(date
.substring(12, 14)));
426 calendar
.set(Calendar
.MINUTE
,
427 Integer
.parseInt(date
.substring(10, 12)));
430 catch (NumberFormatException nfe
)
432 throw new DEREncodingException("cannot parse date");
435 return calendar
.getTime();