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.
/** * 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.
Params:
  • input – array containing ASN.1 encoded data.
/** * 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.
Params:
  • input – array containing ASN.1 encoded data.
  • lazyEvaluate – true if parsing inside constructed objects can be delayed.
/** * 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.
Params:
  • input – stream containing ASN.1 encoded data.
  • limit – maximum size of a DER encoded object.
/** * 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.
Params:
  • input – stream containing ASN.1 encoded data.
  • lazyEvaluate – true if parsing inside constructed objects can be delayed.
/** * 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.
Params:
  • input – stream containing ASN.1 encoded data.
  • limit – maximum size of a DER encoded object.
  • lazyEvaluate – true if parsing inside constructed objects can be delayed.
/** * 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); } 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.
Params:
  • tag – the full tag details.
  • tagNo – the tagNo defined.
  • length – the length of the object.
Throws:
Returns:the resulting primitive.
/** * 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); 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 = buildDEREncodableVector(defIn); ASN1OctetString[] strings = new ASN1OctetString[v.size()]; for (int i = 0; i != strings.length; i++) { strings[i] = (ASN1OctetString)v.get(i); } return new BEROctetString(strings); case SEQUENCE: if (lazyEvaluate) { return new LazyEncodedSequence(defIn.toByteArray()); } else { return DERFactory.createSequence(buildDEREncodableVector(defIn)); } case SET: return DERFactory.createSet(buildDEREncodableVector(defIn)); case EXTERNAL: return new DLExternal(buildDEREncodableVector(defIn)); default: throw new IOException("unknown tag " + tagNo + " encountered"); } } return createPrimitiveDERObject(tagNo, defIn, tmpBuffers); } ASN1EncodableVector buildEncodableVector() throws IOException { ASN1EncodableVector v = new ASN1EncodableVector(); ASN1Primitive o; while ((o = readObject()) != null) { v.add(o); } return v; } ASN1EncodableVector buildDEREncodableVector( DefiniteLengthInputStream dIn) throws IOException { return new ASN1InputStream(dIn).buildEncodableVector(); } 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) 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) // after all we must have read at least 1 byte { throw new IOException("corrupted stream - out of bounds length found"); } } 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 len = defIn.getRemaining() / 2; char[] buf = new char[len]; int totalRead = 0; while (totalRead < len) { int ch1 = defIn.read(); if (ch1 < 0) { break; } int ch2 = defIn.read(); if (ch2 < 0) { break; } buf[totalRead++] = (char)((ch1 << 8) | (ch2 & 0xff)); } return buf; } 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"); } } }