summary refs log tree commit diff
path: root/crypto/src/asn1/Asn1InputStream.cs
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/src/asn1/Asn1InputStream.cs')
-rw-r--r--crypto/src/asn1/Asn1InputStream.cs368
1 files changed, 368 insertions, 0 deletions
diff --git a/crypto/src/asn1/Asn1InputStream.cs b/crypto/src/asn1/Asn1InputStream.cs
new file mode 100644
index 000000000..a72049b56
--- /dev/null
+++ b/crypto/src/asn1/Asn1InputStream.cs
@@ -0,0 +1,368 @@
+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;
+
+        private readonly byte[][] tmpBuffers;
+
+        internal static int FindLimit(Stream input)
+		{
+			if (input is LimitedInputStream)
+			{
+				return ((LimitedInputStream)input).GetRemaining();
+			}
+			else if (input is MemoryStream)
+			{
+				MemoryStream mem = (MemoryStream)input;
+				return (int)(mem.Length - mem.Position);
+			}
+
+			return int.MaxValue;
+		}
+
+		public Asn1InputStream(
+			Stream inputStream)
+			: this(inputStream, FindLimit(inputStream))
+		{
+		}
+
+		/**
+		 * 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;
+            this.tmpBuffers = new byte[16][];
+        }
+
+		/**
+		 * 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)
+		{
+		}
+
+		/**
+		* build an object given its tag and the number of bytes to construct it from.
+		*/
+		private Asn1Object BuildObject(
+			int	tag,
+			int	tagNo,
+			int	length)
+		{
+			bool isConstructed = (tag & Asn1Tags.Constructed) != 0;
+
+			DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this.s, length);
+
+			if ((tag & Asn1Tags.Application) != 0)
+			{
+				return new DerApplicationSpecific(isConstructed, tagNo, defIn.ToArray());
+			}
+
+			if ((tag & Asn1Tags.Tagged) != 0)
+			{
+				return new Asn1StreamParser(defIn).ReadTaggedObject(isConstructed, tagNo);
+			}
+
+			if (isConstructed)
+			{
+				// TODO There are other tags that may be constructed (e.g. BitString)
+				switch (tagNo)
+				{
+					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);
+					case Asn1Tags.External:
+						return new DerExternal(BuildDerEncodableVector(defIn));                
+					default:
+						return new DerUnknownTag(true, tagNo, defIn.ToArray());
+				}
+			}
+
+            return CreatePrimitiveDerObject(tagNo, defIn, tmpBuffers);
+		}
+
+        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 = ReadTagNumber(this.s, tag);
+
+			bool isConstructed = (tag & Asn1Tags.Constructed) != 0;
+
+			//
+			// calculate length
+			//
+			int length = ReadLength(this.s, limit);
+
+			if (length < 0) // indefinite length method
+			{
+				if (!isConstructed)
+					throw new IOException("indefinite length primitive encoding encountered");
+
+				IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this.s, limit);
+            	Asn1StreamParser sp = new Asn1StreamParser(indIn, limit);
+
+				if ((tag & Asn1Tags.Application) != 0)
+				{
+					return new BerApplicationSpecificParser(tagNo, sp).ToAsn1Object();
+				}
+
+				if ((tag & Asn1Tags.Tagged) != 0)
+				{
+					return new BerTaggedObjectParser(true, tagNo, sp).ToAsn1Object();
+				}
+
+				// TODO There are other tags that may be constructed (e.g. BitString)
+				switch (tagNo)
+				{
+					case Asn1Tags.OctetString:
+						return new BerOctetStringParser(sp).ToAsn1Object();
+					case Asn1Tags.Sequence:
+						return new BerSequenceParser(sp).ToAsn1Object();
+					case Asn1Tags.Set:
+						return new BerSetParser(sp).ToAsn1Object();
+					case Asn1Tags.External:
+						return new DerExternalParser(sp).ToAsn1Object();
+					default:
+						throw new IOException("unknown BER object encountered");
+				}
+			}
+			else
+			{
+				try
+				{
+					return BuildObject(tag, tagNo, length);
+				}
+				catch (ArgumentException e)
+				{
+					throw new Asn1Exception("corrupted stream detected", e);
+				}
+			}
+		}
+
+		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;
+
+				// 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.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 byte[] GetBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)
+        {
+            int len = defIn.GetRemaining();
+            if (len >= tmpBuffers.Length)
+            {
+                return defIn.ToArray();
+            }
+
+            byte[] buf = tmpBuffers[len];
+            if (buf == null)
+            {
+                buf = tmpBuffers[len] = new byte[len];
+            }
+
+            defIn.ReadAllIntoByteArray(buf);
+
+            return buf;
+        }
+
+		internal static Asn1Object CreatePrimitiveDerObject(
+			int                         tagNo,
+			DefiniteLengthInputStream   defIn,
+            byte[][]                    tmpBuffers)
+		{
+            switch (tagNo)
+            {
+                case Asn1Tags.Boolean:
+                    return new DerBoolean(GetBuffer(defIn, tmpBuffers));
+                case Asn1Tags.Enumerated:
+                    return new DerEnumerated(GetBuffer(defIn, tmpBuffers));
+                case Asn1Tags.ObjectIdentifier:
+                    return DerObjectIdentifier.FromOctetString(GetBuffer(defIn, tmpBuffers));
+            }
+
+            byte[] bytes = defIn.ToArray();
+
+            switch (tagNo)
+			{
+				case Asn1Tags.BitString:
+					return DerBitString.FromAsn1Octets(bytes);
+				case Asn1Tags.BmpString:
+					return new DerBmpString(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.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(false, tagNo, bytes);
+			}
+		}
+	}
+}