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     * @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     */
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 DERApplicationSpecific(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 DERExternal(buildDEREncodableVector(defIn));
185                default:
186                    throw new IOException("unknown tag " + tagNo + " encountered");
187            }
188        }
189
190        return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
191    }
192
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
207    ASN1EncodableVector buildDEREncodableVector(
208        DefiniteLengthInputStream dIn) throws IOException
209    {
210        return new ASN1InputStream(dIn).buildEncodableVector();
211    }
212
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
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
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
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
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
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