blob: 0c63a1a625ccc9f0efcc96d97e2f7159d88780c9 [file] [log] [blame]
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);
}
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);
if ((tag & APPLICATION) != 0)
{
return new DERApplicationSpecific(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 DERExternal(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");
}
}
}