308 lines
7.5 KiB
C#
308 lines
7.5 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
|
|
using Org.BouncyCastle.Asn1.Utilities;
|
|
using Org.BouncyCastle.Utilities.IO;
|
|
|
|
namespace Org.BouncyCastle.Asn1
|
|
{
|
|
/**
|
|
* 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
|
|
: FilterStream
|
|
{
|
|
private readonly int limit;
|
|
|
|
public Asn1InputStream(
|
|
Stream inputStream)
|
|
: this(inputStream, int.MaxValue)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* 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(
|
|
Stream inputStream,
|
|
int limit)
|
|
: base(inputStream)
|
|
{
|
|
this.limit = limit;
|
|
}
|
|
|
|
/**
|
|
* 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 MemoryStream(input, false), input.Length)
|
|
{
|
|
}
|
|
|
|
internal Asn1EncodableVector BuildEncodableVector()
|
|
{
|
|
Asn1EncodableVector v = new Asn1EncodableVector();
|
|
|
|
Asn1Object o;
|
|
while ((o = ReadObject()) != null)
|
|
{
|
|
v.Add(o);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
internal virtual Asn1EncodableVector BuildDerEncodableVector(
|
|
DefiniteLengthInputStream dIn)
|
|
{
|
|
return new Asn1InputStream(dIn).BuildEncodableVector();
|
|
}
|
|
|
|
internal virtual DerSequence CreateDerSequence(
|
|
DefiniteLengthInputStream dIn)
|
|
{
|
|
return DerSequence.FromVector(BuildDerEncodableVector(dIn));
|
|
}
|
|
|
|
internal virtual DerSet CreateDerSet(
|
|
DefiniteLengthInputStream dIn)
|
|
{
|
|
return DerSet.FromVector(BuildDerEncodableVector(dIn), false);
|
|
}
|
|
|
|
public Asn1Object ReadObject()
|
|
{
|
|
int tag = ReadByte();
|
|
if (tag <= 0)
|
|
{
|
|
if (tag == 0)
|
|
throw new IOException("unexpected end-of-contents marker");
|
|
|
|
return null;
|
|
}
|
|
|
|
//
|
|
// calculate tag number
|
|
//
|
|
int tagNo = 0;
|
|
if ((tag & Asn1Tags.Tagged) != 0 || (tag & Asn1Tags.Application) != 0)
|
|
{
|
|
tagNo = ReadTagNumber(this, tag);
|
|
}
|
|
|
|
bool isConstructed = (tag & Asn1Tags.Constructed) != 0;
|
|
int baseTagNo = tag & ~Asn1Tags.Constructed;
|
|
|
|
//
|
|
// calculate length
|
|
//
|
|
int length = ReadLength(this, limit);
|
|
|
|
if (length < 0) // indefinite length method
|
|
{
|
|
if (!isConstructed)
|
|
throw new IOException("indefinite length primitive encoding encountered");
|
|
|
|
IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this);
|
|
|
|
if ((tag & Asn1Tags.Tagged) != 0)
|
|
{
|
|
return new BerTaggedObjectParser(tag, tagNo, indIn).ToAsn1Object();
|
|
}
|
|
|
|
Asn1StreamParser sp = new Asn1StreamParser(indIn);
|
|
|
|
// TODO There are other tags that may be constructed (e.g. BitString)
|
|
switch (baseTagNo)
|
|
{
|
|
case Asn1Tags.OctetString:
|
|
return new BerOctetStringParser(sp).ToAsn1Object();
|
|
case Asn1Tags.Sequence:
|
|
return new BerSequenceParser(sp).ToAsn1Object();
|
|
case Asn1Tags.Set:
|
|
return new BerSetParser(sp).ToAsn1Object();
|
|
default:
|
|
throw new IOException("unknown BER object encountered");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this, length);
|
|
|
|
if ((tag & Asn1Tags.Application) != 0)
|
|
{
|
|
return new DerApplicationSpecific(isConstructed, tagNo, defIn.ToArray());
|
|
}
|
|
|
|
if ((tag & Asn1Tags.Tagged) != 0)
|
|
{
|
|
return new BerTaggedObjectParser(tag, tagNo, defIn).ToAsn1Object();
|
|
}
|
|
|
|
if (isConstructed)
|
|
{
|
|
// TODO There are other tags that may be constructed (e.g. BitString)
|
|
switch (baseTagNo)
|
|
{
|
|
case Asn1Tags.OctetString:
|
|
//
|
|
// yes, people actually do this...
|
|
//
|
|
return new BerOctetString(BuildDerEncodableVector(defIn));
|
|
case Asn1Tags.Sequence:
|
|
return CreateDerSequence(defIn);
|
|
case Asn1Tags.Set:
|
|
return CreateDerSet(defIn);
|
|
default:
|
|
return new DerUnknownTag(tag, defIn.ToArray());
|
|
}
|
|
}
|
|
|
|
return CreatePrimitiveDerObject(tag, defIn.ToArray());
|
|
}
|
|
}
|
|
|
|
internal static int ReadTagNumber(
|
|
Stream s,
|
|
int tag)
|
|
{
|
|
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.ReadByte();
|
|
|
|
// 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.ReadByte();
|
|
}
|
|
|
|
if (b < 0)
|
|
throw new EndOfStreamException("EOF found inside tag value.");
|
|
|
|
tagNo |= (b & 0x7f);
|
|
}
|
|
|
|
return tagNo;
|
|
}
|
|
|
|
internal static int ReadLength(
|
|
Stream s,
|
|
int limit)
|
|
{
|
|
int length = s.ReadByte();
|
|
if (length < 0)
|
|
throw new EndOfStreamException("EOF found when length expected");
|
|
|
|
if (length == 0x80)
|
|
return -1; // indefinite-length encoding
|
|
|
|
if (length > 127)
|
|
{
|
|
int size = length & 0x7f;
|
|
|
|
if (size > 4)
|
|
throw new IOException("DER length more than 4 bytes");
|
|
|
|
length = 0;
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
int next = s.ReadByte();
|
|
|
|
if (next < 0)
|
|
throw new EndOfStreamException("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;
|
|
}
|
|
|
|
internal static Asn1Object CreatePrimitiveDerObject(
|
|
int tag,
|
|
byte[] bytes)
|
|
{
|
|
Debug.Assert((tag & (Asn1Tags.Application | Asn1Tags.Constructed | Asn1Tags.Tagged)) == 0);
|
|
|
|
switch (tag)
|
|
{
|
|
case Asn1Tags.BitString:
|
|
{
|
|
int padBits = bytes[0];
|
|
byte[] data = new byte[bytes.Length - 1];
|
|
Array.Copy(bytes, 1, data, 0, bytes.Length - 1);
|
|
return new DerBitString(data, padBits);
|
|
}
|
|
case Asn1Tags.BmpString:
|
|
return new DerBmpString(bytes);
|
|
case Asn1Tags.Boolean:
|
|
return new DerBoolean(bytes);
|
|
case Asn1Tags.Enumerated:
|
|
return new DerEnumerated(bytes);
|
|
case Asn1Tags.GeneralizedTime:
|
|
return new DerGeneralizedTime(bytes);
|
|
case Asn1Tags.GeneralString:
|
|
return new DerGeneralString(bytes);
|
|
case Asn1Tags.IA5String:
|
|
return new DerIA5String(bytes);
|
|
case Asn1Tags.Integer:
|
|
return new DerInteger(bytes);
|
|
case Asn1Tags.Null:
|
|
return DerNull.Instance; // actual content is ignored (enforce 0 length?)
|
|
case Asn1Tags.NumericString:
|
|
return new DerNumericString(bytes);
|
|
case Asn1Tags.ObjectIdentifier:
|
|
return new DerObjectIdentifier(bytes);
|
|
case Asn1Tags.OctetString:
|
|
return new DerOctetString(bytes);
|
|
case Asn1Tags.PrintableString:
|
|
return new DerPrintableString(bytes);
|
|
case Asn1Tags.T61String:
|
|
return new DerT61String(bytes);
|
|
case Asn1Tags.UniversalString:
|
|
return new DerUniversalString(bytes);
|
|
case Asn1Tags.UtcTime:
|
|
return new DerUtcTime(bytes);
|
|
case Asn1Tags.Utf8String:
|
|
return new DerUtf8String(bytes);
|
|
case Asn1Tags.VisibleString:
|
|
return new DerVisibleString(bytes);
|
|
default:
|
|
return new DerUnknownTag(tag, bytes);
|
|
}
|
|
}
|
|
}
|
|
}
|