using System; using System.IO; using System.Text; namespace Unosquare.Swan.Networking.Ldap { /// /// The Asn1Set class can hold an unordered collection of components with /// identical type. This class inherits from the Asn1Structured class /// which already provides functionality to hold multiple Asn1 components. /// /// internal class Asn1SetOf : Asn1Structured { public const Int32 Tag = 0x11; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public Asn1SetOf(Int32 size = 10) : base(Id, size) { } public override String ToString() => this.ToString("SET OF: { "); } /// /// The Asn1Choice object represents the choice of any Asn1Object. All /// Asn1Object methods are delegated to the object this Asn1Choice contains. /// /// internal class Asn1Choice : Asn1Object { private Asn1Object _content; public Asn1Choice(Asn1Object content = null) => this._content = content; protected internal virtual Asn1Object ChoiceValue { get => this._content; set => this._content = value; } public override Asn1Identifier GetIdentifier() => this._content.GetIdentifier(); public override void SetIdentifier(Asn1Identifier id) => this._content.SetIdentifier(id); public override String ToString() => this._content.ToString(); } /// /// This class is used to encapsulate an ASN.1 Identifier. /// An Asn1Identifier is composed of three parts: ///
  • a class type,
  • a form, and
  • a tag.
  • /// The class type is defined as: ///
      /// bit 8 7 TAG CLASS
      /// ------- -----------
      /// 0 0 UNIVERSAL
      /// 0 1 APPLICATION
      /// 1 0 CONTEXT
      /// 1 1 PRIVATE
      /// 
    /// The form is defined as: ///
      /// bit 6 FORM
      /// ----- --------
      /// 0 PRIMITIVE
      /// 1 CONSTRUCTED
      /// 
    /// Note: CONSTRUCTED types are made up of other CONSTRUCTED or PRIMITIVE /// types. /// The tag is defined as:. ///
      /// bit 5 4 3 2 1 TAG
      /// ------------- ---------------------------------------------
      /// 0 0 0 0 0
      /// . . . . .
      /// 1 1 1 1 0 (0-30) single octet tag
      /// 1 1 1 1 1 (> 30) multiple octet tag, more octets follow
      /// 
    internal sealed class Asn1Identifier { public Asn1Identifier(Asn1IdentifierTag tagClass, Boolean constructed, Int32 tag) { this.Asn1Class = tagClass; this.Constructed = constructed; this.Tag = tag; } public Asn1Identifier(LdapOperation tag) : this(Asn1IdentifierTag.Application, true, (Int32)tag) { } public Asn1Identifier(Int32 contextTag, Boolean constructed = false) : this(Asn1IdentifierTag.Context, constructed, contextTag) { } public Asn1Identifier(Stream stream) { Int32 r = stream.ReadByte(); this.EncodedLength++; if(r < 0) { throw new EndOfStreamException("BERDecoder: decode: EOF in Identifier"); } this.Asn1Class = (Asn1IdentifierTag)(r >> 6); this.Constructed = (r & 0x20) != 0; this.Tag = r & 0x1F; // if tag < 30 then its a single octet identifier. if(this.Tag == 0x1F) { // if true, its a multiple octet identifier. this.Tag = this.DecodeTagNumber(stream); } } public Asn1IdentifierTag Asn1Class { get; } public Boolean Constructed { get; } public Int32 Tag { get; } public Int32 EncodedLength { get; private set; } public Boolean Universal => this.Asn1Class == Asn1IdentifierTag.Universal; public Object Clone() => this.MemberwiseClone(); private Int32 DecodeTagNumber(Stream stream) { Int32 n = 0; while(true) { Int32 r = stream.ReadByte(); this.EncodedLength++; if(r < 0) { throw new EndOfStreamException("BERDecoder: decode: EOF in tag number"); } n = (n << 7) + (r & 0x7F); if((r & 0x80) == 0) { break; } } return n; } } /// /// This is the base class for all other Asn1 types. /// internal abstract class Asn1Object { private static readonly String[] ClassTypes = { "[UNIVERSAL ", "[APPLICATION ", "[", "[PRIVATE " }; private Asn1Identifier _id; protected Asn1Object(Asn1Identifier id = null) => this._id = id; public virtual Asn1Identifier GetIdentifier() => this._id; public virtual void SetIdentifier(Asn1Identifier identifier) => this._id = identifier; public override String ToString() { Asn1Identifier identifier = this.GetIdentifier(); return $"{ClassTypes[(Int32)identifier.Asn1Class]}{identifier.Tag}]"; } } /// /// This class encapsulates the OCTET STRING type. /// /// internal sealed class Asn1OctetString : Asn1Object { public const Int32 Tag = 0x04; private static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); private readonly SByte[] _content; public Asn1OctetString(SByte[] content) : base(Id) => this._content = content; public Asn1OctetString(String content) : base(Id) => this._content = Encoding.UTF8.GetSBytes(content); public Asn1OctetString(Stream stream, Int32 len) : base(Id) => this._content = len > 0 ? (SByte[])LberDecoder.DecodeOctetString(stream, len) : new SByte[0]; public SByte[] ByteValue() => this._content; public String StringValue() => Encoding.UTF8.GetString(this._content); public override String ToString() => base.ToString() + "OCTET STRING: " + this.StringValue(); } /// /// The Asn1Tagged class can hold a base Asn1Object with a distinctive tag /// describing the type of that base object. It also maintains a boolean value /// indicating whether the value should be encoded by EXPLICIT or IMPLICIT /// means. (Explicit is true by default.) /// If the type is encoded IMPLICITLY, the base types form, length and content /// will be encoded as usual along with the class type and tag specified in /// the constructor of this Asn1Tagged class. /// If the type is to be encoded EXPLICITLY, the base type will be encoded as /// usual after the Asn1Tagged identifier has been encoded. /// /// internal class Asn1Tagged : Asn1Object { private Asn1Object _content; public Asn1Tagged(Asn1Identifier identifier, Asn1Object obj = null, Boolean isExplicit = true) : base(identifier) { this._content = obj; this.Explicit = isExplicit; if(!isExplicit) { // replace object's id with new tag. this._content?.SetIdentifier(identifier); } } public Asn1Tagged(Asn1Identifier identifier, SByte[] vals) : base(identifier) { this._content = new Asn1OctetString(vals); this.Explicit = false; } public Asn1Tagged(Stream stream, Int32 len, Asn1Identifier identifier) : base(identifier) => // If we are decoding an implicit tag, there is no way to know at this // low level what the base type really is. We can place the content // into an Asn1OctetString type and pass it back to the application who // will be able to create the appropriate ASN.1 type for this tag. this._content = new Asn1OctetString(stream, len); public Asn1Object TaggedValue { get => this._content; set { this._content = value; if(!this.Explicit) { // replace object's id with new tag. value?.SetIdentifier(this.GetIdentifier()); } } } public Boolean Explicit { get; } public override String ToString() => this.Explicit ? base.ToString() + this._content : this._content.ToString(); } /// /// This class serves as the base type for all ASN.1 /// structured types. /// /// internal abstract class Asn1Structured : Asn1Object { private Asn1Object[] _content; private Int32 _contentIndex; protected internal Asn1Structured(Asn1Identifier id, Int32 size = 10) : base(id) => this._content = new Asn1Object[size]; public Asn1Object[] ToArray() { Asn1Object[] cloneArray = new Asn1Object[this._contentIndex]; Array.Copy(this._content, 0, cloneArray, 0, this._contentIndex); return cloneArray; } public void Add(String s) => this.Add(new Asn1OctetString(s)); public void Add(Asn1Object obj) { if(this._contentIndex == this._content.Length) { // Array too small, need to expand it, double length Asn1Object[] newArray = new Asn1Object[this._contentIndex + this._contentIndex]; Array.Copy(this._content, 0, newArray, 0, this._contentIndex); this._content = newArray; } this._content[this._contentIndex++] = obj; } public void Set(Int32 index, Asn1Object value) { if(index >= this._contentIndex || index < 0) { throw new IndexOutOfRangeException($"Asn1Structured: get: index {index}, size {this._contentIndex}"); } this._content[index] = value; } public Asn1Object Get(Int32 index) { if(index >= this._contentIndex || index < 0) { throw new IndexOutOfRangeException($"Asn1Structured: set: index {index}, size {this._contentIndex}"); } return this._content[index]; } public Int32 Size() => this._contentIndex; public String ToString(String type) { StringBuilder sb = new StringBuilder().Append(type); for(Int32 i = 0; i < this._contentIndex; i++) { _ = sb.Append(this._content[i]); if(i != this._contentIndex - 1) { _ = sb.Append(", "); } } _ = sb.Append(" }"); return base.ToString() + sb; } protected internal void DecodeStructured(Stream stream, Int32 len) { Int32[] componentLen = new Int32[1]; // collects length of component while(len > 0) { this.Add(LberDecoder.Decode(stream, componentLen)); len -= componentLen[0]; } } } /// /// This class encapsulates the ASN.1 BOOLEAN type. /// /// internal class Asn1Boolean : Asn1Object { public const Int32 Tag = 0x01; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); private readonly Boolean _content; public Asn1Boolean(Boolean content) : base(Id) => this._content = content; public Asn1Boolean(Stream stream, Int32 len) : base(Id) => this._content = LberDecoder.DecodeBoolean(stream, len); public Boolean BooleanValue() => this._content; public override String ToString() => $"{base.ToString()}BOOLEAN: {this._content}"; } /// /// This class represents the ASN.1 NULL type. /// /// internal sealed class Asn1Null : Asn1Object { public const Int32 Tag = 0x05; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public Asn1Null() : base(Id) { } public override String ToString() => $"{base.ToString()}NULL: \"\""; } /// /// This abstract class is the base class /// for all Asn1 numeric (integral) types. These include /// Asn1Integer and Asn1Enumerated. /// /// internal abstract class Asn1Numeric : Asn1Object { private readonly Int64 _content; internal Asn1Numeric(Asn1Identifier id, Int32 numericValue) : base(id) => this._content = numericValue; internal Asn1Numeric(Asn1Identifier id, Int64 numericValue) : base(id) => this._content = numericValue; public Int32 IntValue() => (Int32)this._content; public Int64 LongValue() => this._content; } /// /// This class provides a means to manipulate ASN.1 Length's. It will /// be used by Asn1Encoder's and Asn1Decoder's by composition. /// internal sealed class Asn1Length { public Asn1Length(Stream stream) { Int32 r = stream.ReadByte(); this.EncodedLength++; if(r == 0x80) { this.Length = -1; } else if(r < 0x80) { this.Length = r; } else { this.Length = 0; for(r &= 0x7F; r > 0; r--) { Int32 part = stream.ReadByte(); this.EncodedLength++; if(part < 0) { throw new EndOfStreamException("BERDecoder: decode: EOF in Asn1Length"); } this.Length = (this.Length << 8) + part; } } } public Int32 Length { get; } public Int32 EncodedLength { get; } } /// /// The Asn1Sequence class can hold an ordered collection of components with /// distinct type. /// This class inherits from the Asn1Structured class which /// provides functionality to hold multiple Asn1 components. /// /// internal class Asn1Sequence : Asn1Structured { public const Int32 Tag = 0x10; private static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public Asn1Sequence(Int32 size) : base(Id, size) { } public Asn1Sequence(Stream stream, Int32 len) : base(Id) => this.DecodeStructured(stream, len); public override String ToString() => this.ToString("SEQUENCE: { "); } /// /// The Asn1Set class can hold an unordered collection of components with /// distinct type. This class inherits from the Asn1Structured class /// which already provides functionality to hold multiple Asn1 components. /// /// internal sealed class Asn1Set : Asn1Structured { public const Int32 Tag = 0x11; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public Asn1Set(Stream stream, Int32 len) : base(Id) => this.DecodeStructured(stream, len); public override String ToString() => this.ToString("SET: { "); } /// /// This class encapsulates the ASN.1 INTEGER type. /// /// internal class Asn1Integer : Asn1Numeric { public const Int32 Tag = 0x02; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public Asn1Integer(Int32 content) : base(Id, content) { } public Asn1Integer(Stream stream, Int32 len) : base(Id, LberDecoder.DecodeNumeric(stream, len)) { } public override String ToString() => base.ToString() + "INTEGER: " + this.LongValue(); } /// /// This class encapsulates the ASN.1 ENUMERATED type. /// /// internal sealed class Asn1Enumerated : Asn1Numeric { public const Int32 Tag = 0x0a; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public Asn1Enumerated(LdapScope content) : base(Id, (Int32)content) { } public Asn1Enumerated(Int32 content) : base(Id, content) { } /// /// Initializes a new instance of the class. /// Constructs an Asn1Enumerated object by decoding data from an /// input stream. /// /// The stream. /// The length. public Asn1Enumerated(Stream stream, Int32 len) : base(Id, LberDecoder.DecodeNumeric(stream, len)) { } public override String ToString() => base.ToString() + "ENUMERATED: " + this.LongValue(); } /// /// The Asn1SequenceOf class is used to hold an ordered collection /// of components with identical type. This class inherits /// from the Asn1Structured class which already provides /// functionality to hold multiple Asn1 components. /// /// internal class Asn1SequenceOf : Asn1Structured { public const Int32 Tag = 0x10; public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public Asn1SequenceOf(Int32 size) : base(Id, size) { } public Asn1SequenceOf(Stream stream, Int32 len) : base(Id) => this.DecodeStructured(stream, len); public override String ToString() => this.ToString("SEQUENCE OF: { "); } }