542 lines
17 KiB
C#
542 lines
17 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Unosquare.Swan.Networking.Ldap {
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Structured" />
|
|
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: { ");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Asn1Choice object represents the choice of any Asn1Object. All
|
|
/// Asn1Object methods are delegated to the object this Asn1Choice contains.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class is used to encapsulate an ASN.1 Identifier.
|
|
/// An Asn1Identifier is composed of three parts:
|
|
/// <li> a class type,</li><li> a form, and</li><li> a tag.</li>
|
|
/// The class type is defined as:
|
|
/// <pre>
|
|
/// bit 8 7 TAG CLASS
|
|
/// ------- -----------
|
|
/// 0 0 UNIVERSAL
|
|
/// 0 1 APPLICATION
|
|
/// 1 0 CONTEXT
|
|
/// 1 1 PRIVATE
|
|
/// </pre>
|
|
/// The form is defined as:
|
|
/// <pre>
|
|
/// bit 6 FORM
|
|
/// ----- --------
|
|
/// 0 PRIMITIVE
|
|
/// 1 CONSTRUCTED
|
|
/// </pre>
|
|
/// Note: CONSTRUCTED types are made up of other CONSTRUCTED or PRIMITIVE
|
|
/// types.
|
|
/// The tag is defined as:.
|
|
/// <pre>
|
|
/// 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
|
|
/// </pre></summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is the base class for all other Asn1 types.
|
|
/// </summary>
|
|
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}]";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class encapsulates the OCTET STRING type.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class serves as the base type for all ASN.1
|
|
/// structured types.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class encapsulates the ASN.1 BOOLEAN type.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class represents the ASN.1 NULL type.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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: \"\"";
|
|
}
|
|
|
|
/// <summary>
|
|
/// This abstract class is the base class
|
|
/// for all Asn1 numeric (integral) types. These include
|
|
/// Asn1Integer and Asn1Enumerated.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Object" />
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class provides a means to manipulate ASN.1 Length's. It will
|
|
/// be used by Asn1Encoder's and Asn1Decoder's by composition.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Structured" />
|
|
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: { ");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Structured" />
|
|
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: { ");
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class encapsulates the ASN.1 INTEGER type.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Numeric" />
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class encapsulates the ASN.1 ENUMERATED type.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Numeric" />
|
|
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) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Asn1Enumerated"/> class.
|
|
/// Constructs an Asn1Enumerated object by decoding data from an
|
|
/// input stream.
|
|
/// </summary>
|
|
/// <param name="stream">The stream.</param>
|
|
/// <param name="len">The length.</param>
|
|
public Asn1Enumerated(Stream stream, Int32 len)
|
|
: base(Id, LberDecoder.DecodeNumeric(stream, len)) {
|
|
}
|
|
|
|
public override String ToString() => base.ToString() + "ENUMERATED: " + this.LongValue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <seealso cref="Asn1Structured" />
|
|
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: { ");
|
|
}
|
|
} |