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 Asn1RelativeOid
: Asn1Object
{
internal class Meta : Asn1UniversalType
{
internal static readonly Asn1UniversalType Instance = new Meta();
private Meta() : base(typeof(Asn1RelativeOid), Asn1Tags.RelativeOid) {}
internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
{
return CreatePrimitive(octetString.GetOctets(), false);
}
}
/// Implementation limit on the length of the contents octets for a Relative OID.
///
/// We adopt the same value used by OpenJDK for Object Identifier. 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 Asn1RelativeOid FromContents(byte[] contents)
{
if (contents == null)
throw new ArgumentNullException(nameof(contents));
return CreatePrimitive(contents, true);
}
public static Asn1RelativeOid GetInstance(object obj)
{
if (obj == null)
return null;
if (obj is Asn1RelativeOid asn1RelativeOid)
return asn1RelativeOid;
if (obj is IAsn1Convertible asn1Convertible)
{
if (!(obj is Asn1Object) && asn1Convertible.ToAsn1Object() is Asn1RelativeOid converted)
return converted;
}
else if (obj is byte[] bytes)
{
try
{
return (Asn1RelativeOid)FromByteArray(bytes);
}
catch (IOException e)
{
throw new ArgumentException("failed to construct relative OID from byte[]: " + e.Message);
}
}
throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj");
}
public static Asn1RelativeOid GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
{
return (Asn1RelativeOid)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
}
public static Asn1RelativeOid GetOptional(Asn1Encodable element)
{
if (element == null)
throw new ArgumentNullException(nameof(element));
if (element is Asn1RelativeOid existing)
return existing;
return null;
}
public static Asn1RelativeOid GetTagged(Asn1TaggedObject taggedObject, bool declaredExplicit)
{
return (Asn1RelativeOid)Meta.Instance.GetTagged(taggedObject, declaredExplicit);
}
public static bool TryFromID(string identifier, out Asn1RelativeOid oid)
{
if (identifier == null)
throw new ArgumentNullException(nameof(identifier));
if (identifier.Length <= MaxIdentifierLength && IsValidIdentifier(identifier, from: 0))
{
byte[] contents = ParseIdentifier(identifier);
if (contents.Length <= MaxContentsLength)
{
oid = new Asn1RelativeOid(contents, identifier);
return true;
}
}
oid = default;
return false;
}
private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
private static readonly Asn1RelativeOid[] Cache = new Asn1RelativeOid[64];
private readonly byte[] m_contents;
private string m_identifier;
public Asn1RelativeOid(string identifier)
{
CheckIdentifier(identifier);
byte[] contents = ParseIdentifier(identifier);
CheckContentsLength(contents.Length);
m_contents = contents;
m_identifier = identifier;
}
private Asn1RelativeOid(byte[] contents, string identifier)
{
m_contents = contents;
m_identifier = identifier;
}
public virtual Asn1RelativeOid Branch(string branchID)
{
CheckIdentifier(branchID);
byte[] branchContents = ParseIdentifier(branchID);
CheckContentsLength(m_contents.Length + branchContents.Length);
return new Asn1RelativeOid(
contents: Arrays.Concatenate(m_contents, branchContents),
identifier: GetID() + "." + branchID);
}
public string GetID()
{
return Objects.EnsureSingletonInitialized(ref m_identifier, m_contents, ParseContents);
}
[Obsolete("Use 'GetID' instead")]
public string Id => GetID();
public override string ToString() => GetID();
protected override bool Asn1Equals(Asn1Object asn1Object)
{
return asn1Object is Asn1RelativeOid 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.RelativeOid, 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.RelativeOid, 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 relative 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 relative OID contents length limit");
if (!IsValidIdentifier(identifier, from: 0))
throw new FormatException("string " + identifier + " not a valid relative OID");
}
internal static Asn1RelativeOid CreatePrimitive(byte[] contents, bool clone)
{
CheckContentsLength(contents.Length);
uint index = (uint)Arrays.GetHashCode(contents);
index ^= index >> 24;
index ^= index >> 12;
index ^= index >> 6;
index &= 63;
var originalEntry = Volatile.Read(ref Cache[index]);
if (originalEntry != null && Arrays.AreEqual(contents, originalEntry.m_contents))
return originalEntry;
if (!IsValidContents(contents))
throw new ArgumentException("invalid relative OID contents", nameof(contents));
var newEntry = new Asn1RelativeOid(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;
}
internal static bool IsValidContents(byte[] contents)
{
if (contents.Length < 1)
return false;
bool subIDStart = true;
for (int i = 0; i < contents.Length; ++i)
{
if (subIDStart && contents[i] == 0x80)
return false;
subIDStart = (contents[i] & 0x80) == 0;
}
return subIDStart;
}
internal static bool IsValidIdentifier(string identifier, int from)
{
int digitCount = 0;
int pos = identifier.Length;
while (--pos >= from)
{
char ch = identifier[pos];
if (ch == '.')
{
if (0 == digitCount || (digitCount > 1 && identifier[pos + 1] == '0'))
return false;
digitCount = 0;
}
else if ('0' <= ch && ch <= '9')
{
++digitCount;
}
else
{
return false;
}
}
if (0 == digitCount || (digitCount > 1 && identifier[pos + 1] == '0'))
return false;
return true;
}
internal 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)
{
first = false;
}
else
{
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)
{
first = false;
}
else
{
objId.Append('.');
}
objId.Append(bigValue);
bigValue = null;
value = 0;
}
else
{
bigValue = bigValue.ShiftLeft(7);
}
}
}
return objId.ToString();
}
internal static byte[] ParseIdentifier(string identifier)
{
MemoryStream bOut = new MemoryStream();
OidTokenizer tok = new OidTokenizer(identifier);
while (tok.HasMoreTokens)
{
string token = tok.NextToken();
if (token.Length <= 18)
{
WriteField(bOut, long.Parse(token));
}
else
{
WriteField(bOut, new BigInteger(token));
}
}
return bOut.ToArray();
}
internal static void WriteField(Stream outputStream, long fieldValue)
{
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
Span result = stackalloc byte[9];
#else
byte[] result = new byte[9];
#endif
int pos = 8;
result[pos] = (byte)((int)fieldValue & 0x7F);
while (fieldValue >= (1L << 7))
{
fieldValue >>= 7;
result[--pos] = (byte)((int)fieldValue | 0x80);
}
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
outputStream.Write(result[pos..]);
#else
outputStream.Write(result, pos, 9 - pos);
#endif
}
internal static void WriteField(Stream outputStream, BigInteger fieldValue)
{
int byteCount = (fieldValue.BitLength + 6) / 7;
if (byteCount == 0)
{
outputStream.WriteByte(0);
}
else
{
BigInteger tmpValue = fieldValue;
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
Span tmp = byteCount <= 16
? stackalloc byte[byteCount]
: new byte[byteCount];
#else
byte[] tmp = new byte[byteCount];
#endif
for (int i = byteCount - 1; i >= 0; i--)
{
tmp[i] = (byte)(tmpValue.IntValue | 0x80);
tmpValue = tmpValue.ShiftRight(7);
}
tmp[byteCount - 1] &= 0x7F;
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
outputStream.Write(tmp);
#else
outputStream.Write(tmp, 0, tmp.Length);
#endif
}
}
}
}