• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.bouncycastle.asn1;
2 
3 import java.io.ByteArrayInputStream;
4 import java.io.EOFException;
5 import java.io.FilterInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 
9 import org.bouncycastle.util.io.Streams;
10 
11 /**
12  * a general purpose ASN.1 decoder - note: this class differs from the
13  * others in that it returns null after it has read the last object in
14  * the stream. If an ASN.1 NULL is encountered a DER/BER Null object is
15  * returned.
16  */
17 public class ASN1InputStream
18     extends FilterInputStream
19     implements BERTags
20 {
21     private final int limit;
22     private final boolean lazyEvaluate;
23 
24     private final byte[][] tmpBuffers;
25 
ASN1InputStream( InputStream is)26     public ASN1InputStream(
27         InputStream is)
28     {
29         this(is, StreamUtil.findLimit(is));
30     }
31 
32     /**
33      * Create an ASN1InputStream based on the input byte array. The length of DER objects in
34      * the stream is automatically limited to the length of the input array.
35      *
36      * @param input array containing ASN.1 encoded data.
37      */
ASN1InputStream( byte[] input)38     public ASN1InputStream(
39         byte[] input)
40     {
41         this(new ByteArrayInputStream(input), input.length);
42     }
43 
44     /**
45      * Create an ASN1InputStream based on the input byte array. The length of DER objects in
46      * the stream is automatically limited to the length of the input array.
47      *
48      * @param input array containing ASN.1 encoded data.
49      * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
50      */
ASN1InputStream( byte[] input, boolean lazyEvaluate)51     public ASN1InputStream(
52         byte[] input,
53         boolean lazyEvaluate)
54     {
55         this(new ByteArrayInputStream(input), input.length, lazyEvaluate);
56     }
57 
58     /**
59      * Create an ASN1InputStream where no DER object will be longer than limit.
60      *
61      * @param input stream containing ASN.1 encoded data.
62      * @param limit maximum size of a DER encoded object.
63      */
ASN1InputStream( InputStream input, int limit)64     public ASN1InputStream(
65         InputStream input,
66         int         limit)
67     {
68         this(input, limit, false);
69     }
70 
71     /**
72      * Create an ASN1InputStream where no DER object will be longer than limit, and constructed
73      * objects such as sequences will be parsed lazily.
74      *
75      * @param input stream containing ASN.1 encoded data.
76      * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
77      */
ASN1InputStream( InputStream input, boolean lazyEvaluate)78     public ASN1InputStream(
79         InputStream input,
80         boolean     lazyEvaluate)
81     {
82         this(input, StreamUtil.findLimit(input), lazyEvaluate);
83     }
84 
85     /**
86      * Create an ASN1InputStream where no DER object will be longer than limit, and constructed
87      * objects such as sequences will be parsed lazily.
88      *
89      * @param input stream containing ASN.1 encoded data.
90      * @param limit maximum size of a DER encoded object.
91      * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
92      */
ASN1InputStream( InputStream input, int limit, boolean lazyEvaluate)93     public ASN1InputStream(
94         InputStream input,
95         int         limit,
96         boolean     lazyEvaluate)
97     {
98         super(input);
99         this.limit = limit;
100         this.lazyEvaluate = lazyEvaluate;
101         this.tmpBuffers = new byte[11][];
102     }
103 
getLimit()104     int getLimit()
105     {
106         return limit;
107     }
108 
readLength()109     protected int readLength()
110         throws IOException
111     {
112         return readLength(this, limit);
113     }
114 
readFully( byte[] bytes)115     protected void readFully(
116         byte[]  bytes)
117         throws IOException
118     {
119         if (Streams.readFully(this, bytes) != bytes.length)
120         {
121             throw new EOFException("EOF encountered in middle of object");
122         }
123     }
124 
125     /**
126      * build an object given its tag and the number of bytes to construct it from.
127      */
buildObject( int tag, int tagNo, int length)128     protected ASN1Primitive buildObject(
129         int       tag,
130         int       tagNo,
131         int       length)
132         throws IOException
133     {
134         boolean isConstructed = (tag & CONSTRUCTED) != 0;
135 
136         DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this, length);
137 
138         if ((tag & APPLICATION) != 0)
139         {
140             return new DERApplicationSpecific(isConstructed, tagNo, defIn.toByteArray());
141         }
142 
143         if ((tag & TAGGED) != 0)
144         {
145             return new ASN1StreamParser(defIn).readTaggedObject(isConstructed, tagNo);
146         }
147 
148         if (isConstructed)
149         {
150             // TODO There are other tags that may be constructed (e.g. BIT_STRING)
151             switch (tagNo)
152             {
153                 case OCTET_STRING:
154                     //
155                     // yes, people actually do this...
156                     //
157                     ASN1EncodableVector v = buildDEREncodableVector(defIn);
158                     ASN1OctetString[] strings = new ASN1OctetString[v.size()];
159 
160                     for (int i = 0; i != strings.length; i++)
161                     {
162                         strings[i] = (ASN1OctetString)v.get(i);
163                     }
164 
165                     return new BEROctetString(strings);
166                 case SEQUENCE:
167                     if (lazyEvaluate)
168                     {
169                         return new LazyEncodedSequence(defIn.toByteArray());
170                     }
171                     else
172                     {
173                         return DERFactory.createSequence(buildDEREncodableVector(defIn));
174                     }
175                 case SET:
176                     return DERFactory.createSet(buildDEREncodableVector(defIn));
177                 case EXTERNAL:
178                     return new DERExternal(buildDEREncodableVector(defIn));
179                 default:
180                     throw new IOException("unknown tag " + tagNo + " encountered");
181             }
182         }
183 
184         return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
185     }
186 
buildEncodableVector()187     ASN1EncodableVector buildEncodableVector()
188         throws IOException
189     {
190         ASN1EncodableVector v = new ASN1EncodableVector();
191         ASN1Primitive o;
192 
193         while ((o = readObject()) != null)
194         {
195             v.add(o);
196         }
197 
198         return v;
199     }
200 
buildDEREncodableVector( DefiniteLengthInputStream dIn)201     ASN1EncodableVector buildDEREncodableVector(
202         DefiniteLengthInputStream dIn) throws IOException
203     {
204         return new ASN1InputStream(dIn).buildEncodableVector();
205     }
206 
readObject()207     public ASN1Primitive readObject()
208         throws IOException
209     {
210         int tag = read();
211         if (tag <= 0)
212         {
213             if (tag == 0)
214             {
215                 throw new IOException("unexpected end-of-contents marker");
216             }
217 
218             return null;
219         }
220 
221         //
222         // calculate tag number
223         //
224         int tagNo = readTagNumber(this, tag);
225 
226         boolean isConstructed = (tag & CONSTRUCTED) != 0;
227 
228         //
229         // calculate length
230         //
231         int length = readLength();
232 
233         if (length < 0) // indefinite length method
234         {
235             if (!isConstructed)
236             {
237                 throw new IOException("indefinite length primitive encoding encountered");
238             }
239 
240             IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this, limit);
241             ASN1StreamParser sp = new ASN1StreamParser(indIn, limit);
242 
243             if ((tag & APPLICATION) != 0)
244             {
245                 return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
246             }
247 
248             if ((tag & TAGGED) != 0)
249             {
250                 return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
251             }
252 
253             // TODO There are other tags that may be constructed (e.g. BIT_STRING)
254             switch (tagNo)
255             {
256                 case OCTET_STRING:
257                     return new BEROctetStringParser(sp).getLoadedObject();
258                 case SEQUENCE:
259                     return new BERSequenceParser(sp).getLoadedObject();
260                 case SET:
261                     return new BERSetParser(sp).getLoadedObject();
262                 case EXTERNAL:
263                     return new DERExternalParser(sp).getLoadedObject();
264                 default:
265                     throw new IOException("unknown BER object encountered");
266             }
267         }
268         else
269         {
270             try
271             {
272                 return buildObject(tag, tagNo, length);
273             }
274             catch (IllegalArgumentException e)
275             {
276                 throw new ASN1Exception("corrupted stream detected", e);
277             }
278         }
279     }
280 
readTagNumber(InputStream s, int tag)281     static int readTagNumber(InputStream s, int tag)
282         throws IOException
283     {
284         int tagNo = tag & 0x1f;
285 
286         //
287         // with tagged object tag number is bottom 5 bits, or stored at the start of the content
288         //
289         if (tagNo == 0x1f)
290         {
291             tagNo = 0;
292 
293             int b = s.read();
294 
295             // X.690-0207 8.1.2.4.2
296             // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
297             if ((b & 0x7f) == 0) // Note: -1 will pass
298             {
299                 throw new IOException("corrupted stream - invalid high tag number found");
300             }
301 
302             while ((b >= 0) && ((b & 0x80) != 0))
303             {
304                 tagNo |= (b & 0x7f);
305                 tagNo <<= 7;
306                 b = s.read();
307             }
308 
309             if (b < 0)
310             {
311                 throw new EOFException("EOF found inside tag value.");
312             }
313 
314             tagNo |= (b & 0x7f);
315         }
316 
317         return tagNo;
318     }
319 
readLength(InputStream s, int limit)320     static int readLength(InputStream s, int limit)
321         throws IOException
322     {
323         int length = s.read();
324         if (length < 0)
325         {
326             throw new EOFException("EOF found when length expected");
327         }
328 
329         if (length == 0x80)
330         {
331             return -1;      // indefinite-length encoding
332         }
333 
334         if (length > 127)
335         {
336             int size = length & 0x7f;
337 
338             // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
339             if (size > 4)
340             {
341                 throw new IOException("DER length more than 4 bytes: " + size);
342             }
343 
344             length = 0;
345             for (int i = 0; i < size; i++)
346             {
347                 int next = s.read();
348 
349                 if (next < 0)
350                 {
351                     throw new EOFException("EOF found reading length");
352                 }
353 
354                 length = (length << 8) + next;
355             }
356 
357             if (length < 0)
358             {
359                 throw new IOException("corrupted stream - negative length found");
360             }
361 
362             if (length >= limit)   // after all we must have read at least 1 byte
363             {
364                 throw new IOException("corrupted stream - out of bounds length found");
365             }
366         }
367 
368         return length;
369     }
370 
getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)371     private static byte[] getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)
372         throws IOException
373     {
374         int len = defIn.getRemaining();
375         if (defIn.getRemaining() < tmpBuffers.length)
376         {
377             byte[] buf = tmpBuffers[len];
378 
379             if (buf == null)
380             {
381                 buf = tmpBuffers[len] = new byte[len];
382             }
383 
384             Streams.readFully(defIn, buf);
385 
386             return buf;
387         }
388         else
389         {
390             return defIn.toByteArray();
391         }
392     }
393 
getBMPCharBuffer(DefiniteLengthInputStream defIn)394     private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn)
395         throws IOException
396     {
397         int len = defIn.getRemaining() / 2;
398         char[] buf = new char[len];
399         int totalRead = 0;
400         while (totalRead < len)
401         {
402             int ch1 = defIn.read();
403             if (ch1 < 0)
404             {
405                 break;
406             }
407             int ch2 = defIn.read();
408             if (ch2 < 0)
409             {
410                 break;
411             }
412             buf[totalRead++] = (char)((ch1 << 8) | (ch2 & 0xff));
413         }
414 
415         return buf;
416     }
417 
createPrimitiveDERObject( int tagNo, DefiniteLengthInputStream defIn, byte[][] tmpBuffers)418     static ASN1Primitive createPrimitiveDERObject(
419         int     tagNo,
420         DefiniteLengthInputStream defIn,
421         byte[][] tmpBuffers)
422         throws IOException
423     {
424         switch (tagNo)
425         {
426             case BIT_STRING:
427                 return DERBitString.fromInputStream(defIn.getRemaining(), defIn);
428             case BMP_STRING:
429                 return new DERBMPString(getBMPCharBuffer(defIn));
430             case BOOLEAN:
431                 return ASN1Boolean.fromOctetString(getBuffer(defIn, tmpBuffers));
432             case ENUMERATED:
433                 return ASN1Enumerated.fromOctetString(getBuffer(defIn, tmpBuffers));
434             case GENERALIZED_TIME:
435                 return new ASN1GeneralizedTime(defIn.toByteArray());
436             case GENERAL_STRING:
437                 return new DERGeneralString(defIn.toByteArray());
438             case IA5_STRING:
439                 return new DERIA5String(defIn.toByteArray());
440             case INTEGER:
441                 return new ASN1Integer(defIn.toByteArray());
442             case NULL:
443                 return DERNull.INSTANCE;   // actual content is ignored (enforce 0 length?)
444             case NUMERIC_STRING:
445                 return new DERNumericString(defIn.toByteArray());
446             case OBJECT_IDENTIFIER:
447                 return ASN1ObjectIdentifier.fromOctetString(getBuffer(defIn, tmpBuffers));
448             case OCTET_STRING:
449                 return new DEROctetString(defIn.toByteArray());
450             case PRINTABLE_STRING:
451                 return new DERPrintableString(defIn.toByteArray());
452             case T61_STRING:
453                 return new DERT61String(defIn.toByteArray());
454             case UNIVERSAL_STRING:
455                 return new DERUniversalString(defIn.toByteArray());
456             case UTC_TIME:
457                 return new ASN1UTCTime(defIn.toByteArray());
458             case UTF8_STRING:
459                 return new DERUTF8String(defIn.toByteArray());
460             case VISIBLE_STRING:
461                 return new DERVisibleString(defIn.toByteArray());
462             default:
463                 throw new IOException("unknown tag " + tagNo + " encountered");
464         }
465     }
466 }
467