ASN1InputStream.java revision e6bf3e8dfa2804891a82075cb469b736321b4827
1package org.bouncycastle.asn1;
2
3import java.io.ByteArrayInputStream;
4import java.io.EOFException;
5import java.io.FilterInputStream;
6import java.io.IOException;
7import java.io.InputStream;
8
9import 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 */
17public 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
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     */
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     */
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     */
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     */
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     */
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
104    int getLimit()
105    {
106        return limit;
107    }
108
109    protected int readLength()
110        throws IOException
111    {
112        return readLength(this, limit);
113    }
114
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    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
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
201    ASN1EncodableVector buildDEREncodableVector(
202        DefiniteLengthInputStream dIn) throws IOException
203    {
204        return new ASN1InputStream(dIn).buildEncodableVector();
205    }
206
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
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
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
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
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
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