594 lines · 20.1 KB · Java
RawDownload
/* * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.util; import java.io.InputStream;import java.io.IOException;import java.io.EOFException;import java.util.Date;import java.util.Vector;import java.math.BigInteger;import java.io.DataInputStream; /** * A DER input stream, used for parsing ASN.1 DER-encoded data such as * that found in X.509 certificates.  DER is a subset of BER/1, which has * the advantage that it allows only a single encoding of primitive data. * (High level data such as dates still support many encodings.)  That is, * it uses the "Definite" Encoding Rules (DER) not the "Basic" ones (BER). * * <P>Note that, like BER/1, DER streams are streams of explicitly * tagged data values.  Accordingly, this programming interface does * not expose any variant of the java.io.InputStream interface, since * that kind of input stream holds untagged data values and using that * I/O model could prevent correct parsing of the DER data. * * <P>At this time, this class supports only a subset of the types of DER * data encodings which are defined.  That subset is sufficient for parsing * most X.509 certificates. * * * @author David Brownell * @author Amit Kapoor * @author Hemma Prafullchandra */ public class DerInputStream {     /*     * This version only supports fully buffered DER.  This is easy to     * work with, though if large objects are manipulated DER becomes     * awkward to deal with.  That's where BER is useful, since BER     * handles streaming data relatively well.     */    DerInputBuffer      buffer;     /** The DER tag of the value; one of the tag_ constants. */    public byte         tag;     /**     * Create a DER input stream from a data buffer.  The buffer is not     * copied, it is shared.  Accordingly, the buffer should be treated     * as read-only.     *     * @param data the buffer from which to create the string (CONSUMED)     */    public DerInputStream(byte[] data) throws IOException {        init(data, 0, data.length);    }     /**     * Create a DER input stream from part of a data buffer.     * The buffer is not copied, it is shared.  Accordingly, the     * buffer should be treated as read-only.     *     * @param data the buffer from which to create the string (CONSUMED)     * @param offset the first index of <em>data</em> which will     *          be read as DER input in the new stream     * @param len how long a chunk of the buffer to use,     *          starting at "offset"     */    public DerInputStream(byte[] data, int offset, int len) throws IOException {        init(data, offset, len);    }     /*     * private helper routine     */    private void init(byte[] data, int offset, int len) throws IOException {        if ((offset+2 > data.length) || (offset+len > data.length)) {            throw new IOException("Encoding bytes too short");        }        // check for indefinite length encoding        if (DerIndefLenConverter.isIndefinite(data[offset+1])) {            byte[] inData = new byte[len];            System.arraycopy(data, offset, inData, 0, len);             DerIndefLenConverter derIn = new DerIndefLenConverter();            buffer = new DerInputBuffer(derIn.convert(inData));        } else            buffer = new DerInputBuffer(data, offset, len);        buffer.mark(Integer.MAX_VALUE);    }     DerInputStream(DerInputBuffer buf) {        buffer = buf;        buffer.mark(Integer.MAX_VALUE);    }     /**     * Creates a new DER input stream from part of this input stream.     *     * @param len how long a chunk of the current input stream to use,     *          starting at the current position.     * @param do_skip true if the existing data in the input stream should     *          be skipped.  If this value is false, the next data read     *          on this stream and the newly created stream will be the     *          same.     */    public DerInputStream subStream(int len, boolean do_skip)    throws IOException {        DerInputBuffer  newbuf = buffer.dup();         newbuf.truncate(len);        if (do_skip) {            buffer.skip(len);        }        return new DerInputStream(newbuf);    }     /**     * Return what has been written to this DerInputStream     * as a byte array. Useful for debugging.     */    public byte[] toByteArray() {        return buffer.toByteArray();    }     /*     * PRIMITIVES -- these are "universal" ASN.1 simple types.     *     *  INTEGER, ENUMERATED, BIT STRING, OCTET STRING, NULL     *  OBJECT IDENTIFIER, SEQUENCE (OF), SET (OF)     *  UTF8String, PrintableString, T61String, IA5String, UTCTime,     *  GeneralizedTime, BMPString.     * Note: UniversalString not supported till encoder is available.     */     /**     * Get an integer from the input stream as an integer.     *     * @return the integer held in this DER input stream.     */    public int getInteger() throws IOException {        if (buffer.read() != DerValue.tag_Integer) {            throw new IOException("DER input, Integer tag error");        }        return buffer.getInteger(getLength(buffer));    }     /**     * Get a integer from the input stream as a BigInteger object.     *     * @return the integer held in this DER input stream.     */    public BigInteger getBigInteger() throws IOException {        if (buffer.read() != DerValue.tag_Integer) {            throw new IOException("DER input, Integer tag error");        }        return buffer.getBigInteger(getLength(buffer), false);    }     /**     * Returns an ASN.1 INTEGER value as a positive BigInteger.     * This is just to deal with implementations that incorrectly encode     * some values as negative.     *     * @return the integer held in this DER value as a BigInteger.     */    public BigInteger getPositiveBigInteger() throws IOException {        if (buffer.read() != DerValue.tag_Integer) {            throw new IOException("DER input, Integer tag error");        }        return buffer.getBigInteger(getLength(buffer), true);    }     /**     * Get an enumerated from the input stream.     *     * @return the integer held in this DER input stream.     */    public int getEnumerated() throws IOException {        if (buffer.read() != DerValue.tag_Enumerated) {            throw new IOException("DER input, Enumerated tag error");        }        return buffer.getInteger(getLength(buffer));    }     /**     * Get a bit string from the input stream. Padded bits (if any)     * will be stripped off before the bit string is returned.     */    public byte[] getBitString() throws IOException {        if (buffer.read() != DerValue.tag_BitString)            throw new IOException("DER input not an bit string");         return buffer.getBitString(getLength(buffer));    }     /**     * Get a bit string from the input stream.  The bit string need     * not be byte-aligned.     */    public BitArray getUnalignedBitString() throws IOException {        if (buffer.read() != DerValue.tag_BitString)            throw new IOException("DER input not a bit string");         int length = getLength(buffer) - 1;         /*         * First byte = number of excess bits in the last octet of the         * representation.         */        int validBits = length*8 - buffer.read();         byte[] repn = new byte[length];         if ((length != 0) && (buffer.read(repn) != length))            throw new IOException("short read of DER bit string");        return new BitArray(validBits, repn);    }     /**     * Returns an ASN.1 OCTET STRING from the input stream.     */    public byte[] getOctetString() throws IOException {        if (buffer.read() != DerValue.tag_OctetString)            throw new IOException("DER input not an octet string");         int length = getLength(buffer);        byte[] retval = new byte[length];        if ((length != 0) && (buffer.read(retval) != length))            throw new IOException("short read of DER octet string");         return retval;    }     /**     * Returns the asked number of bytes from the input stream.     */    public void getBytes(byte[] val) throws IOException {        if ((val.length != 0) && (buffer.read(val) != val.length)) {            throw new IOException("short read of DER octet string");        }    }     /**     * Reads an encoded null value from the input stream.     */    public void getNull() throws IOException {        if (buffer.read() != DerValue.tag_Null || buffer.read() != 0)            throw new IOException("getNull, bad data");    }     /**     * Reads an X.200 style Object Identifier from the stream.     */    public ObjectIdentifier getOID() throws IOException {        return new ObjectIdentifier(this);    }     /**     * Return a sequence of encoded entities.  ASN.1 sequences are     * ordered, and they are often used, like a "struct" in C or C++,     * to group data values.  They may have optional or context     * specific values.     *     * @param startLen guess about how long the sequence will be     *          (used to initialize an auto-growing data structure)     * @return array of the values in the sequence     */    public DerValue[] getSequence(int startLen) throws IOException {        tag = (byte)buffer.read();        if (tag != DerValue.tag_Sequence)            throw new IOException("Sequence tag error");        return readVector(startLen);    }     /**     * Return a set of encoded entities.  ASN.1 sets are unordered,     * though DER may specify an order for some kinds of sets (such     * as the attributes in an X.500 relative distinguished name)     * to facilitate binary comparisons of encoded values.     *     * @param startLen guess about how large the set will be     *          (used to initialize an auto-growing data structure)     * @return array of the values in the sequence     */    public DerValue[] getSet(int startLen) throws IOException {        tag = (byte)buffer.read();        if (tag != DerValue.tag_Set)            throw new IOException("Set tag error");        return readVector(startLen);    }     /**     * Return a set of encoded entities.  ASN.1 sets are unordered,     * though DER may specify an order for some kinds of sets (such     * as the attributes in an X.500 relative distinguished name)     * to facilitate binary comparisons of encoded values.     *     * @param startLen guess about how large the set will be     *          (used to initialize an auto-growing data structure)     * @param implicit if true tag is assumed implicit.     * @return array of the values in the sequence     */    public DerValue[] getSet(int startLen, boolean implicit)        throws IOException {        tag = (byte)buffer.read();        if (!implicit) {            if (tag != DerValue.tag_Set) {                throw new IOException("Set tag error");            }        }        return (readVector(startLen));    }     /*     * Read a "vector" of values ... set or sequence have the     * same encoding, except for the initial tag, so both use     * this same helper routine.     */    protected DerValue[] readVector(int startLen) throws IOException {        DerInputStream  newstr;         byte lenByte = (byte)buffer.read();        int len = getLength((lenByte & 0xff), buffer);         if (len == -1) {           // indefinite length encoding found           int readLen = buffer.available();           int offset = 2;     // for tag and length bytes           byte[] indefData = new byte[readLen + offset];           indefData[0] = tag;           indefData[1] = lenByte;           DataInputStream dis = new DataInputStream(buffer);           dis.readFully(indefData, offset, readLen);           dis.close();           DerIndefLenConverter derIn = new DerIndefLenConverter();           buffer = new DerInputBuffer(derIn.convert(indefData));           if (tag != buffer.read())                throw new IOException("Indefinite length encoding" +                        " not supported");           len = DerInputStream.getLength(buffer);        }         if (len == 0)            // return empty array instead of null, which should be            // used only for missing optionals            return new DerValue[0];         /*         * Create a temporary stream from which to read the data,         * unless it's not really needed.         */        if (buffer.available() == len)            newstr = this;        else            newstr = subStream(len, true);         /*         * Pull values out of the stream.         */        Vector<DerValue> vec = new Vector<DerValue>(startLen);        DerValue value;         do {            value = new DerValue(newstr.buffer);            vec.addElement(value);        } while (newstr.available() > 0);         if (newstr.available() != 0)            throw new IOException("extra data at end of vector");         /*         * Now stick them into the array we're returning.         */        int             i, max = vec.size();        DerValue[]      retval = new DerValue[max];         for (i = 0; i < max; i++)            retval[i] = vec.elementAt(i);         return retval;    }     /**     * Get a single DER-encoded value from the input stream.     * It can often be useful to pull a value from the stream     * and defer parsing it.  For example, you can pull a nested     * sequence out with one call, and only examine its elements     * later when you really need to.     */    public DerValue getDerValue() throws IOException {        return new DerValue(buffer);    }     /**     * Read a string that was encoded as a UTF8String DER value.     */    public String getUTF8String() throws IOException {        return readString(DerValue.tag_UTF8String, "UTF-8", "UTF8");    }     /**     * Read a string that was encoded as a PrintableString DER value.     */    public String getPrintableString() throws IOException {        return readString(DerValue.tag_PrintableString, "Printable",                          "ASCII");    }     /**     * Read a string that was encoded as a T61String DER value.     */    public String getT61String() throws IOException {        /*         * Works for common characters between T61 and ASCII.         */        return readString(DerValue.tag_T61String, "T61", "ISO-8859-1");    }     /**     * Read a string that was encoded as a IA5tring DER value.     */    public String getIA5String() throws IOException {        return readString(DerValue.tag_IA5String, "IA5", "ASCII");    }     /**     * Read a string that was encoded as a BMPString DER value.     */    public String getBMPString() throws IOException {        return readString(DerValue.tag_BMPString, "BMP",                          "UnicodeBigUnmarked");    }     /**     * Read a string that was encoded as a GeneralString DER value.     */    public String getGeneralString() throws IOException {        return readString(DerValue.tag_GeneralString, "General",                          "ASCII");    }     /**     * Private helper routine to read an encoded string from the input     * stream.     * @param stringTag the tag for the type of string to read     * @param stringName a name to display in error messages     * @param enc the encoder to use to interpret the data. Should     * correspond to the stringTag above.     */    private String readString(byte stringTag, String stringName,                              String enc) throws IOException {         if (buffer.read() != stringTag)            throw new IOException("DER input not a " +                                  stringName + " string");         int length = getLength(buffer);        byte[] retval = new byte[length];        if ((length != 0) && (buffer.read(retval) != length))            throw new IOException("short read of DER " +                                  stringName + " string");         return new String(retval, enc);    }     /**     * Get a UTC encoded time value from the input stream.     */    public Date getUTCTime() throws IOException {        if (buffer.read() != DerValue.tag_UtcTime)            throw new IOException("DER input, UTCtime tag invalid ");        return buffer.getUTCTime(getLength(buffer));    } 

Lines 1–500 of 594.