using System; using System.IO; using System.Text; using System.Threading; using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Asn1 { public class DerObjectIdentifier : Asn1Object { internal class Meta : Asn1UniversalType { internal static readonly Asn1UniversalType Instance = new Meta(); private Meta() : base(typeof(DerObjectIdentifier), Asn1Tags.ObjectIdentifier) {} internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString) { return CreatePrimitive(octetString.GetOctets(), false); } } /// Implementation limit on the length of the contents octets for an Object Identifier. /// /// We adopt the same value used by OpenJDK. In theory there is no limit on the length of the contents, or the /// number of subidentifiers, or the length of individual subidentifiers. In practice, supporting arbitrary /// lengths can lead to issues, e.g. denial-of-service attacks when attempting to convert a parsed value to its /// (decimal) string form. /// private const int MaxContentsLength = 4096; private const int MaxIdentifierLength = MaxContentsLength * 4 + 1; public static DerObjectIdentifier FromContents(byte[] contents) { if (contents == null) throw new ArgumentNullException(nameof(contents)); return CreatePrimitive(contents, true); } /** * return an OID from the passed in object * * @exception ArgumentException if the object cannot be converted. */ public static DerObjectIdentifier GetInstance(object obj) { if (obj == null) return null; if (obj is DerObjectIdentifier derObjectIdentifier) return derObjectIdentifier; if (obj is IAsn1Convertible asn1Convertible && !(obj is Asn1Object)) { Asn1Object asn1Object = asn1Convertible.ToAsn1Object(); if (asn1Object is DerObjectIdentifier converted) return converted; } else if (obj is byte[] bytes) { try { return (DerObjectIdentifier)Meta.Instance.FromByteArray(bytes); } catch (IOException e) { throw new ArgumentException("failed to construct object identifier from byte[]: " + e.Message); } } throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj"); } public static DerObjectIdentifier GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit) { /* * TODO[api] This block is for backward compatibility, but should be removed. * * - see https://github.com/bcgit/bc-java/issues/1015 */ if (!declaredExplicit && !taggedObject.IsParsed() && taggedObject.HasContextTag()) { Asn1Object baseObject = taggedObject.GetBaseObject().ToAsn1Object(); if (!(baseObject is DerObjectIdentifier)) return FromContents(Asn1OctetString.GetInstance(baseObject).GetOctets()); } return (DerObjectIdentifier)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit); } public static bool TryFromID(string identifier, out DerObjectIdentifier oid) { if (identifier == null) throw new ArgumentNullException(nameof(identifier)); if (identifier.Length <= MaxIdentifierLength && IsValidIdentifier(identifier)) { byte[] contents = ParseIdentifier(identifier); if (contents.Length <= MaxContentsLength) { oid = new DerObjectIdentifier(contents, identifier); return true; } } oid = default; return false; } private const long LongLimit = (long.MaxValue >> 7) - 0x7F; private static readonly DerObjectIdentifier[] Cache = new DerObjectIdentifier[1024]; private readonly byte[] m_contents; private string m_identifier; public DerObjectIdentifier(string identifier) { CheckIdentifier(identifier); byte[] contents = ParseIdentifier(identifier); CheckContentsLength(contents.Length); m_contents = contents; m_identifier = identifier; } private DerObjectIdentifier(byte[] contents, string identifier) { m_contents = contents; m_identifier = identifier; } public virtual DerObjectIdentifier Branch(string branchID) { Asn1RelativeOid.CheckIdentifier(branchID); byte[] branchContents = Asn1RelativeOid.ParseIdentifier(branchID); CheckContentsLength(m_contents.Length + branchContents.Length); return new DerObjectIdentifier( contents: Arrays.Concatenate(m_contents, branchContents), identifier: GetID() + "." + branchID); } public string GetID() { return Objects.EnsureSingletonInitialized(ref m_identifier, m_contents, ParseContents); } // TODO[api] //[Obsolete("Use 'GetID' instead")] public string Id => GetID(); /** * Return true if this oid is an extension of the passed in branch, stem. * @param stem the arc or branch that is a possible parent. * @return true if the branch is on the passed in stem, false otherwise. */ public virtual bool On(DerObjectIdentifier stem) { byte[] contents = m_contents, stemContents = stem.m_contents; int stemLength = stemContents.Length; return contents.Length > stemLength && Arrays.AreEqual(contents, 0, stemLength, stemContents, 0, stemLength); } public override string ToString() => GetID(); protected override bool Asn1Equals(Asn1Object asn1Object) { return asn1Object is DerObjectIdentifier that && Arrays.AreEqual(this.m_contents, that.m_contents); } protected override int Asn1GetHashCode() { return Arrays.GetHashCode(m_contents); } internal override IAsn1Encoding GetEncoding(int encoding) { return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.ObjectIdentifier, m_contents); } internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo) { return new PrimitiveEncoding(tagClass, tagNo, m_contents); } internal sealed override DerEncoding GetEncodingDer() { return new PrimitiveDerEncoding(Asn1Tags.Universal, Asn1Tags.ObjectIdentifier, m_contents); } internal sealed override DerEncoding GetEncodingDerImplicit(int tagClass, int tagNo) { return new PrimitiveDerEncoding(tagClass, tagNo, m_contents); } internal static void CheckContentsLength(int contentsLength) { if (contentsLength > MaxContentsLength) throw new ArgumentException("exceeded OID contents length limit"); } internal static void CheckIdentifier(string identifier) { if (identifier == null) throw new ArgumentNullException(nameof(identifier)); if (identifier.Length > MaxIdentifierLength) throw new ArgumentException("exceeded OID contents length limit"); if (!IsValidIdentifier(identifier)) throw new FormatException("string " + identifier + " not a valid OID"); } internal static DerObjectIdentifier CreatePrimitive(byte[] contents, bool clone) { CheckContentsLength(contents.Length); uint index = (uint)Arrays.GetHashCode(contents); index ^= index >> 20; index ^= index >> 10; index &= 1023; var originalEntry = Volatile.Read(ref Cache[index]); if (originalEntry != null && Arrays.AreEqual(contents, originalEntry.m_contents)) return originalEntry; if (!Asn1RelativeOid.IsValidContents(contents)) throw new ArgumentException("invalid OID contents", nameof(contents)); var newEntry = new DerObjectIdentifier(clone ? Arrays.Clone(contents) : contents, identifier: null); var exchangedEntry = Interlocked.CompareExchange(ref Cache[index], newEntry, originalEntry); if (exchangedEntry != originalEntry) { if (exchangedEntry != null && Arrays.AreEqual(contents, exchangedEntry.m_contents)) return exchangedEntry; } return newEntry; } private static bool IsValidIdentifier(string identifier) { if (identifier.Length < 3 || identifier[1] != '.') return false; char first = identifier[0]; if (first < '0' || first > '2') return false; if (!Asn1RelativeOid.IsValidIdentifier(identifier, from: 2)) return false; if (first == '2') return true; if (identifier.Length == 3 || identifier[3] == '.') return true; if (identifier.Length == 4 || identifier[4] == '.') return identifier[2] < '4'; return false; } private static string ParseContents(byte[] contents) { StringBuilder objId = new StringBuilder(); long value = 0; BigInteger bigValue = null; bool first = true; for (int i = 0; i != contents.Length; i++) { int b = contents[i]; if (value <= LongLimit) { value += b & 0x7F; if ((b & 0x80) == 0) { if (first) { if (value < 40) { objId.Append('0'); } else if (value < 80) { objId.Append('1'); value -= 40; } else { objId.Append('2'); value -= 80; } first = false; } objId.Append('.'); objId.Append(value); value = 0; } else { value <<= 7; } } else { if (bigValue == null) { bigValue = BigInteger.ValueOf(value); } bigValue = bigValue.Or(BigInteger.ValueOf(b & 0x7F)); if ((b & 0x80) == 0) { if (first) { objId.Append('2'); bigValue = bigValue.Subtract(BigInteger.ValueOf(80)); first = false; } objId.Append('.'); objId.Append(bigValue); bigValue = null; value = 0; } else { bigValue = bigValue.ShiftLeft(7); } } } return objId.ToString(); } private static byte[] ParseIdentifier(string identifier) { MemoryStream bOut = new MemoryStream(); OidTokenizer tok = new OidTokenizer(identifier); string token = tok.NextToken(); int first = int.Parse(token) * 40; token = tok.NextToken(); if (token.Length <= 18) { Asn1RelativeOid.WriteField(bOut, first + long.Parse(token)); } else { Asn1RelativeOid.WriteField(bOut, new BigInteger(token).Add(BigInteger.ValueOf(first))); } while (tok.HasMoreTokens) { token = tok.NextToken(); if (token.Length <= 18) { Asn1RelativeOid.WriteField(bOut, long.Parse(token)); } else { Asn1RelativeOid.WriteField(bOut, new BigInteger(token)); } } return bOut.ToArray(); } } }