org.bouncycastle.asn1.ASN1InputStream.java Source code

Java tutorial

Introduction

Here is the source code for org.bouncycastle.asn1.ASN1InputStream.java

Source

package org.bouncycastle.asn1;

import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.bouncycastle.util.io.Streams;

/**
 * A general purpose ASN.1 decoder - note: this class differs from the
 * others in that it returns null after it has read the last object in
 * the stream. If an ASN.1 NULL is encountered a DER/BER Null object is
 * returned.
 */
public class ASN1InputStream extends FilterInputStream implements BERTags {
    private final int limit;
    private final boolean lazyEvaluate;

    private final byte[][] tmpBuffers;

    public ASN1InputStream(InputStream is) {
        this(is, StreamUtil.findLimit(is));
    }

    /**
     * Create an ASN1InputStream based on the input byte array. The length of DER objects in
     * the stream is automatically limited to the length of the input array.
     * 
     * @param input array containing ASN.1 encoded data.
     */
    public ASN1InputStream(byte[] input) {
        this(new ByteArrayInputStream(input), input.length);
    }

    /**
     * Create an ASN1InputStream based on the input byte array. The length of DER objects in
     * the stream is automatically limited to the length of the input array.
     *
     * @param input array containing ASN.1 encoded data.
     * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
     */
    public ASN1InputStream(byte[] input, boolean lazyEvaluate) {
        this(new ByteArrayInputStream(input), input.length, lazyEvaluate);
    }

    /**
     * Create an ASN1InputStream where no DER object will be longer than limit.
     * 
     * @param input stream containing ASN.1 encoded data.
     * @param limit maximum size of a DER encoded object.
     */
    public ASN1InputStream(InputStream input, int limit) {
        this(input, limit, false);
    }

    /**
     * Create an ASN1InputStream where no DER object will be longer than limit, and constructed
     * objects such as sequences will be parsed lazily.
     *
     * @param input stream containing ASN.1 encoded data.
     * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
     */
    public ASN1InputStream(InputStream input, boolean lazyEvaluate) {
        this(input, StreamUtil.findLimit(input), lazyEvaluate);
    }

    /**
     * Create an ASN1InputStream where no DER object will be longer than limit, and constructed
     * objects such as sequences will be parsed lazily.
     *
     * @param input stream containing ASN.1 encoded data.
     * @param limit maximum size of a DER encoded object.
     * @param lazyEvaluate true if parsing inside constructed objects can be delayed.
     */
    public ASN1InputStream(InputStream input, int limit, boolean lazyEvaluate) {
        super(input);
        this.limit = limit;
        this.lazyEvaluate = lazyEvaluate;
        this.tmpBuffers = new byte[11][];
    }

    int getLimit() {
        return limit;
    }

    protected int readLength() throws IOException {
        return readLength(this, limit, false);
    }

    protected void readFully(byte[] bytes) throws IOException {
        if (Streams.readFully(this, bytes) != bytes.length) {
            throw new EOFException("EOF encountered in middle of object");
        }
    }

    /**
     * build an object given its tag and the number of bytes to construct it from.
     *
     * @param tag the full tag details.
     * @param tagNo the tagNo defined.
     * @param length the length of the object.
     * @return the resulting primitive.
     * @throws java.io.IOException on processing exception.
     */
    protected ASN1Primitive buildObject(int tag, int tagNo, int length) throws IOException {
        boolean isConstructed = (tag & CONSTRUCTED) != 0;

        DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this, length, limit);

        if ((tag & APPLICATION) != 0) {
            return new DLApplicationSpecific(isConstructed, tagNo, defIn.toByteArray());
        }

        if ((tag & TAGGED) != 0) {
            return new ASN1StreamParser(defIn).readTaggedObject(isConstructed, tagNo);
        }

        if (isConstructed) {
            // TODO There are other tags that may be constructed (e.g. BIT_STRING)
            switch (tagNo) {
            case OCTET_STRING:
                //
                // yes, people actually do this...
                //
                ASN1EncodableVector v = readVector(defIn);
                ASN1OctetString[] strings = new ASN1OctetString[v.size()];

                for (int i = 0; i != strings.length; i++) {
                    ASN1Encodable asn1Obj = v.get(i);
                    if (asn1Obj instanceof ASN1OctetString) {
                        strings[i] = (ASN1OctetString) asn1Obj;
                    } else {
                        throw new ASN1Exception(
                                "unknown object encountered in constructed OCTET STRING: " + asn1Obj.getClass());
                    }
                }

                return new BEROctetString(strings);
            case SEQUENCE:
                if (lazyEvaluate) {
                    return new LazyEncodedSequence(defIn.toByteArray());
                } else {
                    return DLFactory.createSequence(readVector(defIn));
                }
            case SET:
                return DLFactory.createSet(readVector(defIn));
            case EXTERNAL:
                return new DLExternal(readVector(defIn));
            default:
                throw new IOException("unknown tag " + tagNo + " encountered");
            }
        }

        return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
    }

    ASN1EncodableVector readVector(DefiniteLengthInputStream dIn) throws IOException {
        if (dIn.getRemaining() < 1) {
            return new ASN1EncodableVector(0);
        }

        ASN1InputStream subStream = new ASN1InputStream(dIn);
        ASN1EncodableVector v = new ASN1EncodableVector();
        ASN1Primitive p;
        while ((p = subStream.readObject()) != null) {
            v.add(p);
        }
        return v;
    }

    public ASN1Primitive readObject() throws IOException {
        int tag = read();
        if (tag <= 0) {
            if (tag == 0) {
                throw new IOException("unexpected end-of-contents marker");
            }

            return null;
        }

        //
        // calculate tag number
        //
        int tagNo = readTagNumber(this, tag);

        boolean isConstructed = (tag & CONSTRUCTED) != 0;

        //
        // calculate length
        //
        int length = readLength();

        if (length < 0) // indefinite-length method
        {
            if (!isConstructed) {
                throw new IOException("indefinite-length primitive encoding encountered");
            }

            IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this, limit);
            ASN1StreamParser sp = new ASN1StreamParser(indIn, limit);

            if ((tag & APPLICATION) != 0) {
                return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
            }

            if ((tag & TAGGED) != 0) {
                return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
            }

            // TODO There are other tags that may be constructed (e.g. BIT_STRING)
            switch (tagNo) {
            case OCTET_STRING:
                return new BEROctetStringParser(sp).getLoadedObject();
            case SEQUENCE:
                return new BERSequenceParser(sp).getLoadedObject();
            case SET:
                return new BERSetParser(sp).getLoadedObject();
            case EXTERNAL:
                return new DERExternalParser(sp).getLoadedObject();
            default:
                throw new IOException("unknown BER object encountered");
            }
        } else {
            try {
                return buildObject(tag, tagNo, length);
            } catch (IllegalArgumentException e) {
                throw new ASN1Exception("corrupted stream detected", e);
            }
        }
    }

    static int readTagNumber(InputStream s, int tag) throws IOException {
        int tagNo = tag & 0x1f;

        //
        // with tagged object tag number is bottom 5 bits, or stored at the start of the content
        //
        if (tagNo == 0x1f) {
            tagNo = 0;

            int b = s.read();

            // X.690-0207 8.1.2.4.2
            // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
            if ((b & 0x7f) == 0) // Note: -1 will pass
            {
                throw new IOException("corrupted stream - invalid high tag number found");
            }

            while ((b >= 0) && ((b & 0x80) != 0)) {
                tagNo |= (b & 0x7f);
                tagNo <<= 7;
                b = s.read();
            }

            if (b < 0) {
                throw new EOFException("EOF found inside tag value.");
            }

            tagNo |= (b & 0x7f);
        }

        return tagNo;
    }

    static int readLength(InputStream s, int limit, boolean isParsing) throws IOException {
        int length = s.read();
        if (length < 0) {
            throw new EOFException("EOF found when length expected");
        }

        if (length == 0x80) {
            return -1; // indefinite-length encoding
        }

        if (length > 127) {
            int size = length & 0x7f;

            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
            if (size > 4) {
                throw new IOException("DER length more than 4 bytes: " + size);
            }

            length = 0;
            for (int i = 0; i < size; i++) {
                int next = s.read();

                if (next < 0) {
                    throw new EOFException("EOF found reading length");
                }

                length = (length << 8) + next;
            }

            if (length < 0) {
                throw new IOException("corrupted stream - negative length found");
            }

            if (length >= limit && !isParsing) // after all we must have read at least 1 byte
            {
                throw new IOException("corrupted stream - out of bounds length found: " + length + " >= " + limit);
            }
        }

        return length;
    }

    private static byte[] getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers) throws IOException {
        int len = defIn.getRemaining();
        if (defIn.getRemaining() < tmpBuffers.length) {
            byte[] buf = tmpBuffers[len];

            if (buf == null) {
                buf = tmpBuffers[len] = new byte[len];
            }

            Streams.readFully(defIn, buf);

            return buf;
        } else {
            return defIn.toByteArray();
        }
    }

    private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn) throws IOException {
        int remainingBytes = defIn.getRemaining();
        if (0 != (remainingBytes & 1)) {
            throw new IOException("malformed BMPString encoding encountered");
        }

        char[] string = new char[remainingBytes / 2];
        int stringPos = 0;

        byte[] buf = new byte[8];
        while (remainingBytes >= 8) {
            if (Streams.readFully(defIn, buf, 0, 8) != 8) {
                throw new EOFException("EOF encountered in middle of BMPString");
            }

            string[stringPos] = (char) ((buf[0] << 8) | (buf[1] & 0xFF));
            string[stringPos + 1] = (char) ((buf[2] << 8) | (buf[3] & 0xFF));
            string[stringPos + 2] = (char) ((buf[4] << 8) | (buf[5] & 0xFF));
            string[stringPos + 3] = (char) ((buf[6] << 8) | (buf[7] & 0xFF));
            stringPos += 4;
            remainingBytes -= 8;
        }
        if (remainingBytes > 0) {
            if (Streams.readFully(defIn, buf, 0, remainingBytes) != remainingBytes) {
                throw new EOFException("EOF encountered in middle of BMPString");
            }

            int bufPos = 0;
            do {
                int b1 = buf[bufPos++] << 8;
                int b2 = buf[bufPos++] & 0xFF;
                string[stringPos++] = (char) (b1 | b2);
            } while (bufPos < remainingBytes);
        }

        if (0 != defIn.getRemaining() || string.length != stringPos) {
            throw new IllegalStateException();
        }

        return string;
    }

    static ASN1Primitive createPrimitiveDERObject(int tagNo, DefiniteLengthInputStream defIn, byte[][] tmpBuffers)
            throws IOException {
        switch (tagNo) {
        case BIT_STRING:
            return ASN1BitString.fromInputStream(defIn.getRemaining(), defIn);
        case BMP_STRING:
            return new DERBMPString(getBMPCharBuffer(defIn));
        case BOOLEAN:
            return ASN1Boolean.fromOctetString(getBuffer(defIn, tmpBuffers));
        case ENUMERATED:
            return ASN1Enumerated.fromOctetString(getBuffer(defIn, tmpBuffers));
        case GENERALIZED_TIME:
            return new ASN1GeneralizedTime(defIn.toByteArray());
        case GENERAL_STRING:
            return new DERGeneralString(defIn.toByteArray());
        case IA5_STRING:
            return new DERIA5String(defIn.toByteArray());
        case INTEGER:
            return new ASN1Integer(defIn.toByteArray(), false);
        case NULL:
            return DERNull.INSTANCE; // actual content is ignored (enforce 0 length?)
        case NUMERIC_STRING:
            return new DERNumericString(defIn.toByteArray());
        case OBJECT_IDENTIFIER:
            return ASN1ObjectIdentifier.fromOctetString(getBuffer(defIn, tmpBuffers));
        case OCTET_STRING:
            return new DEROctetString(defIn.toByteArray());
        case PRINTABLE_STRING:
            return new DERPrintableString(defIn.toByteArray());
        case T61_STRING:
            return new DERT61String(defIn.toByteArray());
        case UNIVERSAL_STRING:
            return new DERUniversalString(defIn.toByteArray());
        case UTC_TIME:
            return new ASN1UTCTime(defIn.toByteArray());
        case UTF8_STRING:
            return new DERUTF8String(defIn.toByteArray());
        case VISIBLE_STRING:
            return new DERVisibleString(defIn.toByteArray());
        case GRAPHIC_STRING:
            return new DERGraphicString(defIn.toByteArray());
        case VIDEOTEX_STRING:
            return new DERVideotexString(defIn.toByteArray());
        default:
            throw new IOException("unknown tag " + tagNo + " encountered");
        }
    }
}