• 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      *
128      * @param tag the full tag details.
129      * @param tagNo the tagNo defined.
130      * @param length the length of the object.
131      * @return the resulting primitive.
132      * @throws java.io.IOException on processing exception.
133      */
buildObject( int tag, int tagNo, int length)134     protected ASN1Primitive buildObject(
135         int       tag,
136         int       tagNo,
137         int       length)
138         throws IOException
139     {
140         boolean isConstructed = (tag & CONSTRUCTED) != 0;
141 
142         DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this, length);
143 
144         if ((tag & APPLICATION) != 0)
145         {
146             return new DLApplicationSpecific(isConstructed, tagNo, defIn.toByteArray());
147         }
148 
149         if ((tag & TAGGED) != 0)
150         {
151             return new ASN1StreamParser(defIn).readTaggedObject(isConstructed, tagNo);
152         }
153 
154         if (isConstructed)
155         {
156             // TODO There are other tags that may be constructed (e.g. BIT_STRING)
157             switch (tagNo)
158             {
159                 case OCTET_STRING:
160                     //
161                     // yes, people actually do this...
162                     //
163                     ASN1EncodableVector v = buildDEREncodableVector(defIn);
164                     ASN1OctetString[] strings = new ASN1OctetString[v.size()];
165 
166                     for (int i = 0; i != strings.length; i++)
167                     {
168                         strings[i] = (ASN1OctetString)v.get(i);
169                     }
170 
171                     return new BEROctetString(strings);
172                 case SEQUENCE:
173                     if (lazyEvaluate)
174                     {
175                         return new LazyEncodedSequence(defIn.toByteArray());
176                     }
177                     else
178                     {
179                         return DERFactory.createSequence(buildDEREncodableVector(defIn));
180                     }
181                 case SET:
182                     return DERFactory.createSet(buildDEREncodableVector(defIn));
183                 case EXTERNAL:
184                     return new DLExternal(buildDEREncodableVector(defIn));
185                 default:
186                     throw new IOException("unknown tag " + tagNo + " encountered");
187             }
188         }
189 
190         return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
191     }
192 
buildEncodableVector()193     ASN1EncodableVector buildEncodableVector()
194         throws IOException
195     {
196         ASN1EncodableVector v = new ASN1EncodableVector();
197         ASN1Primitive o;
198 
199         while ((o = readObject()) != null)
200         {
201             v.add(o);
202         }
203 
204         return v;
205     }
206 
buildDEREncodableVector( DefiniteLengthInputStream dIn)207     ASN1EncodableVector buildDEREncodableVector(
208         DefiniteLengthInputStream dIn) throws IOException
209     {
210         return new ASN1InputStream(dIn).buildEncodableVector();
211     }
212 
readObject()213     public ASN1Primitive readObject()
214         throws IOException
215     {
216         int tag = read();
217         if (tag <= 0)
218         {
219             if (tag == 0)
220             {
221                 throw new IOException("unexpected end-of-contents marker");
222             }
223 
224             return null;
225         }
226 
227         //
228         // calculate tag number
229         //
230         int tagNo = readTagNumber(this, tag);
231 
232         boolean isConstructed = (tag & CONSTRUCTED) != 0;
233 
234         //
235         // calculate length
236         //
237         int length = readLength();
238 
239         if (length < 0) // indefinite-length method
240         {
241             if (!isConstructed)
242             {
243                 throw new IOException("indefinite-length primitive encoding encountered");
244             }
245 
246             IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this, limit);
247             ASN1StreamParser sp = new ASN1StreamParser(indIn, limit);
248 
249             if ((tag & APPLICATION) != 0)
250             {
251                 return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
252             }
253 
254             if ((tag & TAGGED) != 0)
255             {
256                 return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
257             }
258 
259             // TODO There are other tags that may be constructed (e.g. BIT_STRING)
260             switch (tagNo)
261             {
262                 case OCTET_STRING:
263                     return new BEROctetStringParser(sp).getLoadedObject();
264                 case SEQUENCE:
265                     return new BERSequenceParser(sp).getLoadedObject();
266                 case SET:
267                     return new BERSetParser(sp).getLoadedObject();
268                 case EXTERNAL:
269                     return new DERExternalParser(sp).getLoadedObject();
270                 default:
271                     throw new IOException("unknown BER object encountered");
272             }
273         }
274         else
275         {
276             try
277             {
278                 return buildObject(tag, tagNo, length);
279             }
280             catch (IllegalArgumentException e)
281             {
282                 throw new ASN1Exception("corrupted stream detected", e);
283             }
284         }
285     }
286 
readTagNumber(InputStream s, int tag)287     static int readTagNumber(InputStream s, int tag)
288         throws IOException
289     {
290         int tagNo = tag & 0x1f;
291 
292         //
293         // with tagged object tag number is bottom 5 bits, or stored at the start of the content
294         //
295         if (tagNo == 0x1f)
296         {
297             tagNo = 0;
298 
299             int b = s.read();
300 
301             // X.690-0207 8.1.2.4.2
302             // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
303             if ((b & 0x7f) == 0) // Note: -1 will pass
304             {
305                 throw new IOException("corrupted stream - invalid high tag number found");
306             }
307 
308             while ((b >= 0) && ((b & 0x80) != 0))
309             {
310                 tagNo |= (b & 0x7f);
311                 tagNo <<= 7;
312                 b = s.read();
313             }
314 
315             if (b < 0)
316             {
317                 throw new EOFException("EOF found inside tag value.");
318             }
319 
320             tagNo |= (b & 0x7f);
321         }
322 
323         return tagNo;
324     }
325 
readLength(InputStream s, int limit)326     static int readLength(InputStream s, int limit)
327         throws IOException
328     {
329         int length = s.read();
330         if (length < 0)
331         {
332             throw new EOFException("EOF found when length expected");
333         }
334 
335         if (length == 0x80)
336         {
337             return -1;      // indefinite-length encoding
338         }
339 
340         if (length > 127)
341         {
342             int size = length & 0x7f;
343 
344             // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
345             if (size > 4)
346             {
347                 throw new IOException("DER length more than 4 bytes: " + size);
348             }
349 
350             length = 0;
351             for (int i = 0; i < size; i++)
352             {
353                 int next = s.read();
354 
355                 if (next < 0)
356                 {
357                     throw new EOFException("EOF found reading length");
358                 }
359 
360                 length = (length << 8) + next;
361             }
362 
363             if (length < 0)
364             {
365                 throw new IOException("corrupted stream - negative length found");
366             }
367 
368             if (length >= limit)   // after all we must have read at least 1 byte
369             {
370                 throw new IOException("corrupted stream - out of bounds length found");
371             }
372         }
373 
374         return length;
375     }
376 
getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)377     private static byte[] getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)
378         throws IOException
379     {
380         int len = defIn.getRemaining();
381         if (defIn.getRemaining() < tmpBuffers.length)
382         {
383             byte[] buf = tmpBuffers[len];
384 
385             if (buf == null)
386             {
387                 buf = tmpBuffers[len] = new byte[len];
388             }
389 
390             Streams.readFully(defIn, buf);
391 
392             return buf;
393         }
394         else
395         {
396             return defIn.toByteArray();
397         }
398     }
399 
getBMPCharBuffer(DefiniteLengthInputStream defIn)400     private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn)
401         throws IOException
402     {
403         int len = defIn.getRemaining() / 2;
404         char[] buf = new char[len];
405         int totalRead = 0;
406         while (totalRead < len)
407         {
408             int ch1 = defIn.read();
409             if (ch1 < 0)
410             {
411                 break;
412             }
413             int ch2 = defIn.read();
414             if (ch2 < 0)
415             {
416                 break;
417             }
418             buf[totalRead++] = (char)((ch1 << 8) | (ch2 & 0xff));
419         }
420 
421         return buf;
422     }
423 
createPrimitiveDERObject( int tagNo, DefiniteLengthInputStream defIn, byte[][] tmpBuffers)424     static ASN1Primitive createPrimitiveDERObject(
425         int     tagNo,
426         DefiniteLengthInputStream defIn,
427         byte[][] tmpBuffers)
428         throws IOException
429     {
430         switch (tagNo)
431         {
432             case BIT_STRING:
433                 return ASN1BitString.fromInputStream(defIn.getRemaining(), defIn);
434             case BMP_STRING:
435                 return new DERBMPString(getBMPCharBuffer(defIn));
436             case BOOLEAN:
437                 return ASN1Boolean.fromOctetString(getBuffer(defIn, tmpBuffers));
438             case ENUMERATED:
439                 return ASN1Enumerated.fromOctetString(getBuffer(defIn, tmpBuffers));
440             case GENERALIZED_TIME:
441                 return new ASN1GeneralizedTime(defIn.toByteArray());
442             case GENERAL_STRING:
443                 return new DERGeneralString(defIn.toByteArray());
444             case IA5_STRING:
445                 return new DERIA5String(defIn.toByteArray());
446             case INTEGER:
447                 return new ASN1Integer(defIn.toByteArray(), false);
448             case NULL:
449                 return DERNull.INSTANCE;   // actual content is ignored (enforce 0 length?)
450             case NUMERIC_STRING:
451                 return new DERNumericString(defIn.toByteArray());
452             case OBJECT_IDENTIFIER:
453                 return ASN1ObjectIdentifier.fromOctetString(getBuffer(defIn, tmpBuffers));
454             case OCTET_STRING:
455                 return new DEROctetString(defIn.toByteArray());
456             case PRINTABLE_STRING:
457                 return new DERPrintableString(defIn.toByteArray());
458             case T61_STRING:
459                 return new DERT61String(defIn.toByteArray());
460             case UNIVERSAL_STRING:
461                 return new DERUniversalString(defIn.toByteArray());
462             case UTC_TIME:
463                 return new ASN1UTCTime(defIn.toByteArray());
464             case UTF8_STRING:
465                 return new DERUTF8String(defIn.toByteArray());
466             case VISIBLE_STRING:
467                 return new DERVisibleString(defIn.toByteArray());
468             case GRAPHIC_STRING:
469                 return new DERGraphicString(defIn.toByteArray());
470             case VIDEOTEX_STRING:
471                 return new DERVideotexString(defIn.toByteArray());
472             default:
473                 throw new IOException("unknown tag " + tagNo + " encountered");
474         }
475     }
476 }
477