You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
476 lines
14 KiB
476 lines
14 KiB
package com.fr.third.org.bouncycastle.asn1; |
|
|
|
import java.io.ByteArrayInputStream; |
|
import java.io.EOFException; |
|
import java.io.FilterInputStream; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
|
|
import com.fr.third.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"); |
|
} |
|
} |
|
}
|
|
|