using System; using System.IO; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Asn1 { /** * ASN.1 TaggedObject - in ASN.1 notation this is any object preceded by * a [n] where n is some number - these are assumed to follow the construction * rules (as with sequences). */ public abstract class Asn1TaggedObject : Asn1Object, Asn1TaggedObjectParser { private const int DeclaredExplicit = 1; private const int DeclaredImplicit = 2; // TODO It will probably be better to track parsing constructed vs primitive instead private const int ParsedExplicit = 3; private const int ParsedImplicit = 4; public static Asn1TaggedObject GetInstance(object obj) { if (obj == null) return null; if (obj is Asn1TaggedObject asn1TaggedObject) return asn1TaggedObject; if (obj is IAsn1Convertible asn1Convertible) { if (!(obj is Asn1Object) && asn1Convertible.ToAsn1Object() is Asn1TaggedObject converted) return converted; } else if (obj is byte[] bytes) { try { return CheckedCast(FromByteArray(bytes)); } catch (IOException e) { throw new ArgumentException("failed to construct tagged object from byte[]", nameof(obj), e); } } throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), nameof(obj)); } public static Asn1TaggedObject GetInstance(object obj, int tagClass) { return Asn1Utilities.CheckTagClass(CheckInstance(obj), tagClass); } public static Asn1TaggedObject GetInstance(object obj, int tagClass, int tagNo) { return Asn1Utilities.CheckTag(CheckInstance(obj), tagClass, tagNo); } public static Asn1TaggedObject GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit) { return Asn1Utilities.GetExplicitContextBaseTagged(CheckInstance(taggedObject, declaredExplicit)); } public static Asn1TaggedObject GetInstance(Asn1TaggedObject taggedObject, int tagClass, bool declaredExplicit) { return Asn1Utilities.GetExplicitBaseTagged(CheckInstance(taggedObject, declaredExplicit), tagClass); } public static Asn1TaggedObject GetInstance(Asn1TaggedObject taggedObject, int tagClass, int tagNo, bool declaredExplicit) { return Asn1Utilities.GetExplicitBaseTagged(CheckInstance(taggedObject, declaredExplicit), tagClass, tagNo); } private static Asn1TaggedObject CheckInstance(object obj) { return GetInstance(obj ?? throw new ArgumentNullException(nameof(obj))); } private static Asn1TaggedObject CheckInstance(Asn1TaggedObject taggedObject, bool declaredExplicit) { if (!declaredExplicit) throw new ArgumentException("this method not valid for implicitly tagged tagged objects"); return taggedObject ?? throw new ArgumentNullException(nameof(taggedObject)); } internal readonly int m_explicitness; internal readonly int m_tagClass; internal readonly int m_tagNo; internal readonly Asn1Encodable m_object; /** * @param explicitly true if the object is explicitly tagged. * @param tagNo the tag number for this object. * @param obj the tagged object. */ protected Asn1TaggedObject(bool isExplicit, int tagNo, Asn1Encodable obj) : this(isExplicit, Asn1Tags.ContextSpecific, tagNo, obj) { } protected Asn1TaggedObject(bool isExplicit, int tagClass, int tagNo, Asn1Encodable obj) : this(isExplicit ? DeclaredExplicit : DeclaredImplicit, tagClass, tagNo, obj) { } internal Asn1TaggedObject(int explicitness, int tagClass, int tagNo, Asn1Encodable obj) { if (null == obj) throw new ArgumentNullException(nameof(obj)); if (Asn1Tags.Universal == tagClass || (tagClass & Asn1Tags.Private) != tagClass) throw new ArgumentException("invalid tag class: " + tagClass, nameof(tagClass)); m_explicitness = obj is IAsn1Choice ? DeclaredExplicit : explicitness; m_tagClass = tagClass; m_tagNo = tagNo; m_object = obj; } protected override bool Asn1Equals(Asn1Object asn1Object) { Asn1TaggedObject that = asn1Object as Asn1TaggedObject; if (null == that || this.m_tagNo != that.m_tagNo || this.m_tagClass != that.m_tagClass) return false; if (this.m_explicitness != that.m_explicitness) { /* * TODO This seems incorrect for some cases of implicit tags e.g. if one is a * declared-implicit SET and the other a parsed object. */ if (this.IsExplicit() != that.IsExplicit()) return false; } Asn1Object p1 = this.m_object.ToAsn1Object(); Asn1Object p2 = that.m_object.ToAsn1Object(); if (p1 == p2) return true; if (!this.IsExplicit()) { try { byte[] d1 = this.GetEncoded(); byte[] d2 = that.GetEncoded(); return Arrays.AreEqual(d1, d2); } catch (IOException) { return false; } } return p1.CallAsn1Equals(p2); } protected override int Asn1GetHashCode() { return (m_tagClass * 7919) ^ m_tagNo ^ (IsExplicit() ? 0x0F : 0xF0) ^ m_object.ToAsn1Object().CallAsn1GetHashCode(); } public int TagClass => m_tagClass; public int TagNo => m_tagNo; public bool HasContextTag() { return m_tagClass == Asn1Tags.ContextSpecific; } public bool HasContextTag(int tagNo) { return m_tagClass == Asn1Tags.ContextSpecific && m_tagNo == tagNo; } public bool HasTag(int tagClass, int tagNo) { return m_tagClass == tagClass && m_tagNo == tagNo; } public bool HasTagClass(int tagClass) { return m_tagClass == tagClass; } /** * return whether or not the object may be explicitly tagged. *
* Note: if the object has been read from an input stream, the only * time you can be sure if isExplicit is returning the true state of * affairs is if it returns false. An implicitly tagged object may appear * to be explicitly tagged, so you need to understand the context under * which the reading was done as well, see GetObject below.
*/ public bool IsExplicit() { // TODO New methods like 'IsKnownExplicit' etc. to distinguish uncertain cases? switch (m_explicitness) { case DeclaredExplicit: case ParsedExplicit: return true; default: return false; } } internal bool IsParsed() { switch (m_explicitness) { case ParsedExplicit: case ParsedImplicit: return true; default: return false; } } ///