FileDocCategorySizeDatePackage
BerInputStream.javaAPI DocAndroid 1.5 API30097Wed May 06 22:41:06 BST 2009org.apache.harmony.security.asn1

BerInputStream

public class BerInputStream extends Object
Decodes ASN.1 types encoded with BER (X.690)
see
ASN.1

Fields Summary
protected InputStream
in
Associated InputStream
protected byte[]
buffer
Internal buffer for storing encoded array
protected int
offset
The position in the buffer. Next read must place data into the buffer from this offset
private static final int
BUF_INCREASE_SIZE
protected static final int
INDEFINIT_LENGTH
Indicates indefinite length of the current type
public int
tag
Current decoded tag
protected int
length
Current decoded length
public Object
content
Current decoded content
protected int
tagOffset
Current decoded tag offset
protected int
contentOffset
Current decoded content offset
public int
choiceIndex
The last choice index
public int[]
times
Keeps last decoded: year, month, day, hour, minute, second, millisecond
public int
oidElement
protected boolean
isVerify
Indicates verify or store mode. In store mode a decoded content is stored in a newly allocated appropriate object. The content variable holds a reference to the last created object. In verify mode a decoded content is not stored.
protected boolean
isIndefinedLength
Indicates defined or indefined reading mode for associated InputStream. This mode is defined by reading a length for a first ASN.1 type from InputStream.
private Object[]
pool
Constructors Summary
public BerInputStream(byte[] encoded)
Creates stream for decoding.

param
encoded - bytes array to be decoded
throws
IOException - if an error occurs


                             
         
        this(encoded, 0, encoded.length);
    
public BerInputStream(byte[] encoded, int offset, int expectedLength)
Creates stream for decoding.

param
encoded - bytes array to be decoded
param
offset - the encoding offset
param
expectedLength - expected length of full encoding, this includes identifier, length an content octets
throws
IOException - if an error occurs


        this.buffer = encoded;
        this.offset = offset;

        next();

        // compare expected and decoded length
        if (length != INDEFINIT_LENGTH
                && (offset + expectedLength) != (this.offset + this.length)) {
            throw new ASN1Exception(Messages.getString("security.111")); //$NON-NLS-1$
        }
    
public BerInputStream(InputStream in)
Creates stream for decoding. Allocates initial buffer of default size

param
is associated InputStream

        this(in, BUF_INCREASE_SIZE);
    
public BerInputStream(InputStream in, int initialSize)
Creates stream for decoding. Allocates initial buffer of initialSize size

param
initialSize the internal buffer initial size
param
is associated InputStream


        this.in = in;
        buffer = new byte[initialSize];

        next();

        if (length != INDEFINIT_LENGTH) {
            // input stream has definite length encoding
            // check allocated length to avoid further reallocations
            if (buffer.length < length) {
                byte[] newBuffer = new byte[length];
                System.arraycopy(buffer, 0, newBuffer, 0, offset);
                buffer = newBuffer;
            }
        } else {
            isIndefinedLength = true;
            throw new ASN1Exception(Messages.getString("security.112")); //$NON-NLS-1$
        }
    
Methods Summary
public voidcompactBuffer()
Reallocates the buffer in order to make it exactly the size of data it contains

        if (offset != buffer.length) {
            byte[] newBuffer = new byte[offset];
            // restore buffer content
            System.arraycopy(buffer, 0, newBuffer, 0, offset);
            // set new buffer
            buffer = newBuffer;
        }
    
private final voiddecodeValueCollection(ASN1ValueCollection collection)


        int begOffset = offset;
        int endOffset = begOffset + length;

        ASN1Type type = collection.type;

        if (isVerify) {
            while (endOffset > offset) {
                next();
                type.decode(this);
            }
        } else {

            int seqTagOffset = tagOffset; //store tag offset

            ArrayList values = new ArrayList();
            while (endOffset > offset) {
                next();
                values.add(type.decode(this));
            }

            content = values;

            tagOffset = seqTagOffset; //retrieve tag offset
        }

        if (offset != endOffset) {
            throw new ASN1Exception(Messages.getString("security.134", begOffset)); //$NON-NLS-1$
        }
    
public java.lang.Objectget(java.lang.Object key)


        if (pool == null) {
            return null;
        }

        for (int i = 0; i < pool[0].length; i++) {
            if (pool[0][i] == key) {
                return pool[1][i];
            }
        }
        return null;
    
public final byte[]getBuffer()
Returns internal buffer used for decoding

return
- buffer

        return buffer;
    
public final intgetContentOffset()

        return contentOffset;
    
public byte[]getEncoded()
Returns encoded array. MUST be invoked after decoding corresponding ASN.1 notation

        byte[] encoded = new byte[offset - tagOffset];
        System.arraycopy(buffer, tagOffset, encoded, 0, encoded.length);
        return encoded;
    
public final intgetEndOffset()
Returns end offset for the current encoded type

return
- offset

        return offset + length;
    
public final intgetLength()
Returns length of the current content for decoding

return
- length of content

        return length;
    
public static intgetLength(byte[] encoding)
Returns the length of the encoding

        int length = encoding[1] & 0xFF;
        int numOctets = 0;
        if ((length & 0x80) != 0) { // long form
            numOctets = length & 0x7F;

            // collect this value length
            length = encoding[2] & 0xFF;
            for (int i = 3; i < numOctets + 2; i++) {
                length = (length << 8) + (encoding[i] & 0xFF);
            }
        }
        //    tag length long_form content
        return 1 + 1 + numOctets + length;
    
public final intgetOffset()
Returns the current offset

return
- offset

        return offset;
    
public final intgetTagOffset()
Returns start offset for the current encoded type

return
- offset

        return tagOffset;
    
public intnext()
Decodes next encoded type. Initializes tag, length, tagOffset and contentOffset variables

return
next decoded tag
throws
IOException - if error occured


        tagOffset = offset;

        // read tag
        tag = read();

        // read length
        length = read();
        if (length != 0x80) { // definite form
            // long or short length form
            if ((length & 0x80) != 0) { // long form
                int numOctets = length & 0x7F;

                if (numOctets > 5) {
                    throw new ASN1Exception(Messages.getString("security.113", //$NON-NLS-1$
                            tagOffset)); //FIXME message
                }

                // collect this value length
                length = read();
                for (int i = 1; i < numOctets; i++) {
                    int ch = read();
                    length = (length << 8) + ch;//read();
                }

                if (length > 0xFFFFFF) {
                    throw new ASN1Exception(Messages.getString("security.113", //$NON-NLS-1$
                            tagOffset)); //FIXME message
                }
            }
        } else { //indefinite form
            length = INDEFINIT_LENGTH;
        }
        contentOffset = offset;

        return tag;
    
public voidput(java.lang.Object key, java.lang.Object entry)


        if (pool == null) {
            pool = new Object[2][10];
        }

        int i = 0;
        for (; i < pool[0].length && pool[0][i] != null; i++) {
            if (pool[0][i] == key) {
                pool[1][i] = entry;
                return;
            }
        }

        if (i == pool[0].length) {
            Object[][] newPool = new Object[pool[0].length * 2][2];
            System.arraycopy(pool[0], 0, newPool[0], 0, pool[0].length);
            System.arraycopy(pool[1], 0, newPool[1], 0, pool[0].length);
            pool = newPool;
        } else {
            pool[0][i] = key;
            pool[1][i] = entry;
        }
    
protected intread()
Reads the next encoded byte from the encoded input stream.

return
the next encoded byte
throws
IOException - if error occured


        if (offset == buffer.length) {
            throw new ASN1Exception(Messages.getString("security.13B")); //$NON-NLS-1$
        }

        if (in == null) {
            return buffer[offset++] & 0xFF;
        } else {
            int octet = in.read();
            if (octet == -1) {
                throw new ASN1Exception(Messages.getString("security.13B")); //$NON-NLS-1$
            }
            
            buffer[offset++] = (byte) octet;
            
            return octet;
        }
    
public voidreadBitString()
Decodes ASN.1 bitstring type

throws
IOException - if error occured


        if (tag == ASN1Constants.TAG_BITSTRING) {

            if (length == 0) {
                throw new ASN1Exception(
                        Messages.getString("security.114", tagOffset)); //$NON-NLS-1$
            }

            readContent();

            // content: check unused bits
            if (buffer[contentOffset] > 7) {
                throw new ASN1Exception(Messages.getString("security.115", //$NON-NLS-1$
                        contentOffset));
            }

            if (length == 1 && buffer[contentOffset] != 0) {
                throw new ASN1Exception(Messages.getString("security.116", //$NON-NLS-1$
                        contentOffset));
            }

        } else if (tag == ASN1Constants.TAG_C_BITSTRING) {
            throw new ASN1Exception(Messages.getString("security.117")); //$NON-NLS-1$
        } else {
            throw new ASN1Exception(
                    Messages.getString("security.118", tagOffset, //$NON-NLS-1$
                            Integer.toHexString(tag)));
        }
    
public voidreadBoolean()
Decodes ASN.1 boolean type

throws
IOException - if error occured


        if (tag != ASN1Constants.TAG_BOOLEAN) {
            throw new ASN1Exception(Messages.getString("security.11C", //$NON-NLS-1$
                    tagOffset, Integer.toHexString(tag)));
        }

        // check encoded length
        if (length != 1) {
            throw new ASN1Exception(Messages.getString("security.11D", tagOffset));//$NON-NLS-1$
        }

        readContent();
    
public voidreadContent()
Reads the next encoded content from the encoded input stream. The method MUST be used for reading a primitive encoded content.

throws
IOException - if error occured

        if (offset + length > buffer.length) {
            throw new ASN1Exception(Messages.getString("security.13B")); //$NON-NLS-1$
        }

        if (in == null) {
            offset += length;
        } else {
            if (in.read(buffer, offset, length) != length) {
                throw new ASN1Exception(Messages.getString("security.13C")); //$NON-NLS-1$
            }
            offset += length;
        }
    
public voidreadEnumerated()
Decodes ASN.1 Enumerated type

throws
IOException - if error occured


        if (tag != ASN1Constants.TAG_ENUM) {
            throw new ASN1Exception(
                    Messages.getString("security.119", tagOffset, //$NON-NLS-1$
                            Integer.toHexString(tag)));
        }

        //
        // all checks are the same as for ASN.1 integer type
        //

        // check encoded length
        if (length == 0) {
            throw new ASN1Exception(Messages.getString("security.11A", tagOffset));//$NON-NLS-1$
        }

        readContent();

        // check encoded content
        if (length > 1) {

            int bits = buffer[contentOffset] & 0xFF;
            if (buffer[contentOffset + 1] < 0) {
                bits += 0x100;
            }

            if (bits == 0 || bits == 0x1FF) {
                throw new ASN1Exception(Messages.getString("security.11B", contentOffset)); //$NON-NLS-1$
            }
        }
    
public voidreadGeneralizedTime()
Decodes ASN.1 GeneralizedTime type

throws
IOException - if error occured

        
        if (tag == ASN1Constants.TAG_GENERALIZEDTIME) {

            // FIXME: any other optimizations?
            readContent();
            // FIXME store string somewhere to allow a custom time type perform
            // additional checks

            // check syntax: the last char MUST be Z
            if (buffer[offset - 1] != 'Z") {
                // FIXME support only format that is acceptable for DER
                throw new ASN1Exception(Messages.getString("security.11E")); //$NON-NLS-1$
            }

            // check syntax: MUST be YYYYMMDDHHMMSS[(./,)DDD]'Z'
            if (length != 15 && (length < 17 || length > 19)) // invalid
                                                                // length
            {
                throw new ASN1Exception(Messages.getString("security.11F", //$NON-NLS-1$
                                contentOffset));
            }

            // check content: milliseconds
            if (length > 16) {
                byte char14 = buffer[contentOffset + 14];
                if (char14 != '." && char14 != ',") {
                    throw new ASN1Exception(
                            Messages.getString("security.11F", //$NON-NLS-1$
                                    contentOffset));
                }
            }

            if (times == null) {
                times = new int[7];
            }
            times[0] = strToInt(contentOffset, 4); // year
            times[1] = strToInt(contentOffset + 4, 2); // month
            times[2] = strToInt(contentOffset + 6, 2); // day
            times[3] = strToInt(contentOffset + 8, 2); // hour
            times[4] = strToInt(contentOffset + 10, 2); // minute
            times[5] = strToInt(contentOffset + 12, 2); // second

            if (length > 16) {
                // FIXME optimize me
                times[6] = strToInt(contentOffset + 15, length - 16);

                if (length == 17) {
                    times[6] = times[6] * 100;
                } else if (length == 18) {
                    times[6] = times[6] * 10;
                }
            }

            // FIXME check all values for valid numbers!!!
        } else if (tag == ASN1Constants.TAG_C_GENERALIZEDTIME) {
            throw new ASN1Exception(Messages.getString("security.120")); //$NON-NLS-1$

        } else {
            throw new ASN1Exception(Messages.getString("security.121", //$NON-NLS-1$
                            tagOffset, Integer.toHexString(tag)));
        }
    
public voidreadInteger()
Decodes ASN.1 Integer type

throws
IOException - if error occured


        if (tag != ASN1Constants.TAG_INTEGER) {
            throw new ASN1Exception(Messages.getString("security.127", //$NON-NLS-1$
                    tagOffset, Integer.toHexString(tag)));
        }

        // check encoded length
        if (length < 1) {
            throw new ASN1Exception(Messages.getString("security.128", //$NON-NLS-1$
                    tagOffset)); //$NON-NLS-1$
        }

        readContent();

        // check encoded content
        if (length > 1) {

            byte firstByte = buffer[offset - length];
            byte secondByte = (byte) (buffer[offset - length + 1] & 0x80);

            if (firstByte == 0 && secondByte == 0 || firstByte == (byte) 0xFF
                    && secondByte == (byte) 0x80) {
                throw new ASN1Exception(Messages.getString("security.129", //$NON-NLS-1$
                                (offset - length)));
            }
        }
    
public voidreadOID()
Decodes ASN.1 ObjectIdentifier type

throws
IOException - if error occured


        if (tag != ASN1Constants.TAG_OID) {
            throw new ASN1Exception(Messages.getString("security.12C", //$NON-NLS-1$
                    tagOffset, Integer.toHexString(tag)));
        }

        // check encoded length
        if (length < 1) {
            throw new ASN1Exception(Messages.getString("security.12D", tagOffset)); //$NON-NLS-1$
        }

        readContent();

        // check content: last encoded byte (8th bit MUST be zero)
        if ((buffer[offset - 1] & 0x80) != 0) {
            throw new ASN1Exception(Messages.getString("security.12E", (offset - 1))); //$NON-NLS-1$
        }

        oidElement = 1;
        for (int i = 0; i < length; i++, ++oidElement) {
        
            // According to ASN.1 BER spec:
            //    leading octet of subidentifier MUST not be 0x80
            // This assertion is not verified
            //
            //if (buffer[contentOffset + i] == (byte)0x80) {
            //    throw new ASN1Exception(
            //            "Wrong content for ASN.1 object identifier at ["
            //                    + contentOffset
            //                    + "]. Subidentifier MUST be encoded in minimum number of octets");
            //}
        
            while ((buffer[contentOffset + i] & 0x80) == 0x80) {
                i++;
            }
        }
    
public voidreadOctetString()
Decodes ASN.1 Octetstring type

throws
IOException - if error occured


        if (tag == ASN1Constants.TAG_OCTETSTRING) {
            readContent();
        } else if (tag == ASN1Constants.TAG_C_OCTETSTRING) {
            throw new ASN1Exception(Messages.getString("security.12A")); //$NON-NLS-1$
        } else {
            throw new ASN1Exception(
                    Messages.getString("security.12B", tagOffset, //$NON-NLS-1$
                            Integer.toHexString(tag)));
        }
    
public voidreadSequence(ASN1Sequence sequence)
Decodes ASN.1 Sequence type

param
sequence - ASN.1 sequence to be decoded
throws
IOException - if error occured


        if (tag != ASN1Constants.TAG_C_SEQUENCE) {
            throw new ASN1Exception(
                    Messages.getString("security.12F", tagOffset, //$NON-NLS-1$
                            Integer.toHexString(tag)));
        }

        int begOffset = offset;
        int endOffset = begOffset + length;

        ASN1Type[] type = sequence.type;

        int i = 0;

        if (isVerify) {

            for (; (offset < endOffset) && (i < type.length); i++) {

                next();
                while (!type[i].checkTag(tag)) {
                    // check whether it is optional component or not 
                    if (!sequence.OPTIONAL[i] || (i == type.length - 1)) {
                        throw new ASN1Exception(Messages.getString("security.130", //$NON-NLS-1$
                                        tagOffset));
                    }
                    i++;
                }

                type[i].decode(this);
            }

            // check the rest of components
            for (; i < type.length; i++) {
                if (!sequence.OPTIONAL[i]) {
                    throw new ASN1Exception(Messages.getString("security.131", //$NON-NLS-1$
                            tagOffset));
                }
            }

        } else {

            int seqTagOffset = tagOffset; //store tag offset

            Object[] values = new Object[type.length];
            for (; (offset < endOffset) && (i < type.length); i++) {

                next();
                while (!type[i].checkTag(tag)) {
                    // check whether it is optional component or not 
                    if (!sequence.OPTIONAL[i] || (i == type.length - 1)) {
                        throw new ASN1Exception(Messages.getString("security.132", //$NON-NLS-1$
                                        tagOffset));
                    }

                    // sets default value
                    if (sequence.DEFAULT[i] != null) {
                        values[i] = sequence.DEFAULT[i];
                    }
                    i++;
                }
                values[i] = type[i].decode(this);
            }

            // check the rest of components
            for (; i < type.length; i++) {
                if (!sequence.OPTIONAL[i]) {
                    throw new ASN1Exception(Messages.getString("security.133", //$NON-NLS-1$
                            tagOffset));
                }
                if (sequence.DEFAULT[i] != null) {
                    values[i] = sequence.DEFAULT[i];
                }
            }
            content = values;

            tagOffset = seqTagOffset; //retrieve tag offset
        }

        if (offset != endOffset) {
            throw new ASN1Exception(Messages.getString("security.134", begOffset)); //$NON-NLS-1$
        }
    
public voidreadSequenceOf(ASN1SequenceOf sequenceOf)
Decodes ASN.1 SequenceOf type

param
sequenceOf - ASN.1 sequence to be decoded
throws
IOException - if error occured

        
        if (tag != ASN1Constants.TAG_C_SEQUENCEOF) {
            throw new ASN1Exception(Messages.getString("security.135", tagOffset, //$NON-NLS-1$
                            Integer.toHexString(tag)));
        }

        decodeValueCollection(sequenceOf);
    
public voidreadSet(ASN1Set set)
Decodes ASN.1 Set type

param
set - ASN.1 set to be decoded
throws
IOException - if error occured

        
        if (tag != ASN1Constants.TAG_C_SET) {
            throw new ASN1Exception(Messages.getString("security.136", //$NON-NLS-1$
                    tagOffset, Integer.toHexString(tag)));
        }

        throw new ASN1Exception(Messages.getString("security.137")); //$NON-NLS-1$
    
public voidreadSetOf(ASN1SetOf setOf)
Decodes ASN.1 SetOf type

param
set - ASN.1 set to be decoded
throws
IOException - if error occured

        
        if (tag != ASN1Constants.TAG_C_SETOF) {
            throw new ASN1Exception(Messages.getString("security.138", //$NON-NLS-1$
                    tagOffset, Integer.toHexString(tag)));
        }

        decodeValueCollection(setOf);
    
public voidreadString(ASN1StringType type)
Decodes ASN.1 String type

throws
IOException - if an I/O error occurs or the end of the stream is reached


        //FIXME check string content
        if (tag == type.id) {
            readContent();
        } else if (tag == type.constrId) {
            throw new ASN1Exception(Messages.getString("security.139")); //$NON-NLS-1$
        } else {
            throw new ASN1Exception(
                    Messages.getString("security.13A", tagOffset, //$NON-NLS-1$
                            Integer.toHexString(tag)));
        }
    
public voidreadUTCTime()
Decodes ASN.1 UTCTime type

throws
IOException - if an I/O error occurs or the end of the stream is reached


        if (tag == ASN1Constants.TAG_UTCTIME) {

            switch (length) {
            case ASN1UTCTime.UTC_HM:
            case ASN1UTCTime.UTC_HMS:
                break;
            case ASN1UTCTime.UTC_LOCAL_HM:
            case ASN1UTCTime.UTC_LOCAL_HMS:
                // FIXME only coordinated universal time formats are supported
                throw new ASN1Exception(Messages.getString("security.122")); //$NON-NLS-1$
            default:
                throw new ASN1Exception(Messages.getString("security.123", //$NON-NLS-1$
                                tagOffset));
            }

            // FIXME: any other optimizations?
            readContent();

            // FIXME store string somewhere to allow a custom time type perform
            // additional checks

            // check syntax: the last char MUST be Z
            if (buffer[offset - 1] != 'Z") {
                throw new ASN1Exception("ASN.1 UTCTime wrongly encoded at [" //$NON-NLS-1$
                        + contentOffset + ']");
            }

            if (times == null) {
                times = new int[7];
            }

            times[0] = strToInt(contentOffset, 2); // year
            if (times[0] > 49) {
                times[0] += 1900;
            } else {
                times[0] += 2000;
            }

            times[1] = strToInt(contentOffset + 2, 2); // month
            times[2] = strToInt(contentOffset + 4, 2); // day
            times[3] = strToInt(contentOffset + 6, 2); // hour
            times[4] = strToInt(contentOffset + 8, 2); // minute

            if (length == ASN1UTCTime.UTC_HMS) {
                times[5] = strToInt(contentOffset + 10, 2); // second
            }

            // FIXME check all time values for valid numbers!!!
        } else if (tag == ASN1Constants.TAG_C_UTCTIME) {
            throw new ASN1Exception(Messages.getString("security.124")); //$NON-NLS-1$
        } else {
            throw new ASN1Exception(Messages.getString("security.125", //$NON-NLS-1$
                    tagOffset, Integer.toHexString(tag)));
        }
    
public final voidreset(byte[] encoded)
Resets this stream to initial state.

param
encoded - a new bytes array to be decoded
throws
IOException - if an error occurs

        buffer = encoded;

        next();
    
public final voidsetVerify()
Sets verify mode.

        isVerify = true;
    
private intstrToInt(int off, int count)


        //FIXME works only with buffer

        int c;
        int result = 0;
        for (int i = off, end = off + count; i < end; i++) {
            c = buffer[i] - 48;
            if (c < 0 || c > 9) {
                throw new ASN1Exception(Messages.getString("security.126")); //$NON-NLS-1$
            }
            result = result * 10 + c;
        }
        return result;