summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2024-01-18 14:41:27 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2024-01-18 14:41:27 +0700
commitf8439e967ac1853291d74262c4fcc8cbd04145a2 (patch)
tree0999d954df26ed4b961bf17ec80aff45ed8fd6d9
parentFix ordering changes in Pkcs12Store (diff)
downloadBouncyCastle.NET-ed25519-f8439e967ac1853291d74262c4fcc8cbd04145a2.tar.xz
Overhaul DerObjectIdentifier and Asn1RelativeOid
- contents is now primary data
- don't generate identifier string during parsing
- improved validation
- add TryFromID methods
-rw-r--r--crypto/src/asn1/Asn1RelativeOid.cs227
-rw-r--r--crypto/src/asn1/DerObjectIdentifier.cs165
-rw-r--r--crypto/test/src/asn1/test/OIDTest.cs47
-rw-r--r--crypto/test/src/asn1/test/RelativeOidTest.cs4
4 files changed, 279 insertions, 164 deletions
diff --git a/crypto/src/asn1/Asn1RelativeOid.cs b/crypto/src/asn1/Asn1RelativeOid.cs
index 1d2ecb3df..f43a85479 100644
--- a/crypto/src/asn1/Asn1RelativeOid.cs
+++ b/crypto/src/asn1/Asn1RelativeOid.cs
@@ -24,6 +24,9 @@ namespace Org.BouncyCastle.Asn1
 
         public static Asn1RelativeOid FromContents(byte[] contents)
         {
+            if (contents == null)
+                throw new ArgumentNullException(nameof(contents));
+
             return CreatePrimitive(contents, true);
         }
 
@@ -61,10 +64,24 @@ namespace Org.BouncyCastle.Asn1
             return (Asn1RelativeOid)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
         }
 
+        public static bool TryFromID(string identifier, out Asn1RelativeOid oid)
+        {
+            if (identifier == null)
+                throw new ArgumentNullException(nameof(identifier));
+            if (!IsValidIdentifier(identifier, 0))
+            {
+                oid = default;
+                return false;
+            }
+
+            oid = new Asn1RelativeOid(ParseIdentifier(identifier), identifier);
+            return true;
+        }
+
         private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
 
-        private readonly string identifier;
-        private byte[] contents;
+        private readonly byte[] m_contents;
+        private string m_identifier;
 
         public Asn1RelativeOid(string identifier)
         {
@@ -73,7 +90,8 @@ namespace Org.BouncyCastle.Asn1
             if (!IsValidIdentifier(identifier, 0))
                 throw new FormatException("string " + identifier + " not a relative OID");
 
-            this.identifier = identifier;
+            m_contents = ParseIdentifier(identifier);
+            m_identifier = identifier;
         }
 
         private Asn1RelativeOid(Asn1RelativeOid oid, string branchID)
@@ -81,86 +99,92 @@ namespace Org.BouncyCastle.Asn1
             if (!IsValidIdentifier(branchID, 0))
                 throw new FormatException("string " + branchID + " not a valid relative OID branch");
 
-            this.identifier = oid.Id + "." + branchID;
+            m_contents = Arrays.Concatenate(oid.m_contents, ParseIdentifier(branchID));
+            m_identifier = oid.GetID() + "." + branchID;
         }
 
         private Asn1RelativeOid(byte[] contents, bool clone)
         {
-            this.identifier = ParseContents(contents);
-            this.contents = clone ? Arrays.Clone(contents) : contents;
+            if (!IsValidContents(contents))
+                throw new ArgumentException("invalid relative OID contents", nameof(contents));
+
+            m_contents = clone ? Arrays.Clone(contents) : contents;
+            m_identifier = null;
         }
 
-        public virtual Asn1RelativeOid Branch(string branchID)
+        private Asn1RelativeOid(byte[] contents, string identifier)
         {
-            return new Asn1RelativeOid(this, branchID);
+            m_contents = contents;
+            m_identifier = identifier;
         }
 
-        public string Id
+        public virtual Asn1RelativeOid Branch(string branchID)
         {
-            get { return identifier; }
+            return new Asn1RelativeOid(this, branchID);
         }
 
-        public override string ToString()
+        public string GetID()
         {
-            return identifier;
+            return Objects.EnsureSingletonInitialized(ref m_identifier, m_contents, ParseContents);
         }
 
+        // TODO[api]
+        //[Obsolete("Use 'GetID' instead")]
+        public string Id => GetID();
+
+        public override string ToString() => GetID();
+
         protected override bool Asn1Equals(Asn1Object asn1Object)
         {
-            Asn1RelativeOid that = asn1Object as Asn1RelativeOid;
-            return null != that
-                && this.identifier == that.identifier;
+            return asn1Object is Asn1RelativeOid that
+                && Arrays.AreEqual(this.m_contents, that.m_contents);
         }
 
         protected override int Asn1GetHashCode()
         {
-            return identifier.GetHashCode();
+            return Arrays.GetHashCode(m_contents);
         }
 
         internal override IAsn1Encoding GetEncoding(int encoding)
         {
-            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.RelativeOid, GetContents());
+            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.RelativeOid, m_contents);
         }
 
         internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
         {
-            return new PrimitiveEncoding(tagClass, tagNo, GetContents());
+            return new PrimitiveEncoding(tagClass, tagNo, m_contents);
         }
 
         internal sealed override DerEncoding GetEncodingDer()
         {
-            return new PrimitiveDerEncoding(Asn1Tags.Universal, Asn1Tags.RelativeOid, GetContents());
+            return new PrimitiveDerEncoding(Asn1Tags.Universal, Asn1Tags.RelativeOid, m_contents);
         }
 
         internal sealed override DerEncoding GetEncodingDerImplicit(int tagClass, int tagNo)
         {
-            return new PrimitiveDerEncoding(tagClass, tagNo, GetContents());
+            return new PrimitiveDerEncoding(tagClass, tagNo, m_contents);
         }
 
-        private byte[] GetContents() => Objects.EnsureSingletonInitialized(ref contents, identifier, CreateContents);
+        internal static Asn1RelativeOid CreatePrimitive(byte[] contents, bool clone)
+        {
+            return new Asn1RelativeOid(contents, clone);
+        }
 
-        private static byte[] CreateContents(string identifier)
+        internal static bool IsValidContents(byte[] contents)
         {
-            MemoryStream bOut = new MemoryStream();
-            OidTokenizer tok = new OidTokenizer(identifier);
-            while (tok.HasMoreTokens)
+            if (contents.Length < 1)
+                return false;
+
+            bool subIDStart = true;
+            for (int i = 0; i < contents.Length; ++i)
             {
-                string token = tok.NextToken();
-                if (token.Length <= 18)
-                {
-                    WriteField(bOut, long.Parse(token));
-                }
-                else
-                {
-                    WriteField(bOut, new BigInteger(token));
-                }
+                if (subIDStart && contents[i] == 0x80)
+                    return false;
+
+                subIDStart = (contents[i] & 0x80) == 0;
             }
-            return bOut.ToArray();
-        }
 
-        internal static Asn1RelativeOid CreatePrimitive(byte[] contents, bool clone)
-        {
-            return new Asn1RelativeOid(contents, clone);
+            return subIDStart;
         }
 
         internal static bool IsValidIdentifier(string identifier, int from)
@@ -195,59 +219,7 @@ namespace Org.BouncyCastle.Asn1
             return true;
         }
 
-        internal static void WriteField(Stream outputStream, long fieldValue)
-        {
-#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            Span<byte> 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<byte> 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
-            }
-        }
-
-        private static string ParseContents(byte[] contents)
+        internal static string ParseContents(byte[] contents)
         {
             StringBuilder objId = new StringBuilder();
             long value = 0;
@@ -311,5 +283,76 @@ namespace Org.BouncyCastle.Asn1
 
             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<byte> 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<byte> 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
+            }
+        }
     }
 }
diff --git a/crypto/src/asn1/DerObjectIdentifier.cs b/crypto/src/asn1/DerObjectIdentifier.cs
index f43724e1f..7d45c9cb8 100644
--- a/crypto/src/asn1/DerObjectIdentifier.cs
+++ b/crypto/src/asn1/DerObjectIdentifier.cs
@@ -25,6 +25,9 @@ namespace Org.BouncyCastle.Asn1
 
         public static DerObjectIdentifier FromContents(byte[] contents)
         {
+            if (contents == null)
+                throw new ArgumentNullException(nameof(contents));
+
             return CreatePrimitive(contents, true);
         }
 
@@ -79,12 +82,26 @@ namespace Org.BouncyCastle.Asn1
             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 (!IsValidIdentifier(identifier))
+            {
+                oid = default;
+                return false;
+            }
+
+            oid = new DerObjectIdentifier(ParseIdentifier(identifier), identifier);
+            return true;
+        }
+
         private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
 
         private static readonly DerObjectIdentifier[] Cache = new DerObjectIdentifier[1024];
 
-        private readonly string identifier;
-        private byte[] contents;
+        private readonly byte[] m_contents;
+        private string m_identifier;
 
         public DerObjectIdentifier(string identifier)
         {
@@ -93,21 +110,32 @@ namespace Org.BouncyCastle.Asn1
             if (!IsValidIdentifier(identifier))
                 throw new FormatException("string " + identifier + " not an OID");
 
-            this.identifier = identifier;
+            m_contents = ParseIdentifier(identifier);
+            m_identifier = identifier;
         }
 
         private DerObjectIdentifier(DerObjectIdentifier oid, string branchID)
         {
             if (!Asn1RelativeOid.IsValidIdentifier(branchID, 0))
-                throw new ArgumentException("string " + branchID + " not a valid OID branch", "branchID");
+                throw new FormatException("string " + branchID + " not a valid OID branch");
 
-            this.identifier = oid.Id + "." + branchID;
+            m_contents = Arrays.Concatenate(oid.m_contents, Asn1RelativeOid.ParseIdentifier(branchID));
+            m_identifier = oid.GetID() + "." + branchID;
         }
 
         private DerObjectIdentifier(byte[] contents, bool clone)
         {
-            this.identifier = ParseContents(contents);
-            this.contents = clone ? Arrays.Clone(contents) : contents;
+            if (!Asn1RelativeOid.IsValidContents(contents))
+                throw new ArgumentException("invalid OID contents", nameof(contents));
+
+            m_contents = clone ? Arrays.Clone(contents) : contents;
+            m_identifier = null;
+        }
+
+        private DerObjectIdentifier(byte[] contents, string identifier)
+        {
+            m_contents = contents;
+            m_identifier = identifier;
         }
 
         public virtual DerObjectIdentifier Branch(string branchID)
@@ -115,11 +143,15 @@ namespace Org.BouncyCastle.Asn1
             return new DerObjectIdentifier(this, branchID);
         }
 
-        public string Id
+        public string GetID()
         {
-            get { return identifier; }
+            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.
@@ -127,81 +159,44 @@ namespace Org.BouncyCastle.Asn1
          */
         public virtual bool On(DerObjectIdentifier stem)
         {
-            string id = Id, stemId = stem.Id;
-            return id.Length > stemId.Length && id[stemId.Length] == '.' && Platform.StartsWith(id, stemId);
-        }
+            byte[] contents = m_contents, stemContents = stem.m_contents;
+            int stemLength = stemContents.Length;
 
-        public override string ToString()
-        {
-            return identifier;
+            return contents.Length > stemLength
+                && Arrays.AreEqual(contents, 0, stemLength, stemContents, 0, stemLength);
         }
 
+        public override string ToString() => GetID();
+
         protected override bool Asn1Equals(Asn1Object asn1Object)
         {
-            DerObjectIdentifier that = asn1Object as DerObjectIdentifier;
-            return null != that
-                && this.identifier == that.identifier;
+            return asn1Object is DerObjectIdentifier that
+                && Arrays.AreEqual(this.m_contents, that.m_contents);
         }
 
         protected override int Asn1GetHashCode()
         {
-            return identifier.GetHashCode();
+            return Arrays.GetHashCode(m_contents);
         }
 
         internal override IAsn1Encoding GetEncoding(int encoding)
         {
-            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.ObjectIdentifier, GetContents());
+            return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.ObjectIdentifier, m_contents);
         }
 
         internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
         {
-            return new PrimitiveEncoding(tagClass, tagNo, GetContents());
+            return new PrimitiveEncoding(tagClass, tagNo, m_contents);
         }
 
         internal sealed override DerEncoding GetEncodingDer()
         {
-            return new PrimitiveDerEncoding(Asn1Tags.Universal, Asn1Tags.ObjectIdentifier, GetContents());
+            return new PrimitiveDerEncoding(Asn1Tags.Universal, Asn1Tags.ObjectIdentifier, m_contents);
         }
 
         internal sealed override DerEncoding GetEncodingDerImplicit(int tagClass, int tagNo)
         {
-            return new PrimitiveDerEncoding(tagClass, tagNo, GetContents());
-        }
-
-        private byte[] GetContents() => Objects.EnsureSingletonInitialized(ref contents, identifier, CreateContents);
-
-        private static byte[] CreateContents(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();
+            return new PrimitiveDerEncoding(tagClass, tagNo, m_contents);
         }
 
         internal static DerObjectIdentifier CreatePrimitive(byte[] contents, bool clone)
@@ -213,7 +208,7 @@ namespace Org.BouncyCastle.Asn1
             index &= 1023;
 
             var originalEntry = Volatile.Read(ref Cache[index]);
-            if (originalEntry != null && Arrays.AreEqual(contents, originalEntry.GetContents()))
+            if (originalEntry != null && Arrays.AreEqual(contents, originalEntry.m_contents))
                 return originalEntry;
 
             var newEntry = new DerObjectIdentifier(contents, clone);
@@ -221,7 +216,7 @@ namespace Org.BouncyCastle.Asn1
             var exchangedEntry = Interlocked.CompareExchange(ref Cache[index], newEntry, originalEntry);
             if (exchangedEntry != originalEntry)
             {
-                if (exchangedEntry != null && Arrays.AreEqual(contents, exchangedEntry.GetContents()))
+                if (exchangedEntry != null && Arrays.AreEqual(contents, exchangedEntry.m_contents))
                     return exchangedEntry;
             }
 
@@ -237,7 +232,19 @@ namespace Org.BouncyCastle.Asn1
             if (first < '0' || first > '2')
                 return false;
 
-            return Asn1RelativeOid.IsValidIdentifier(identifier, 2);
+            if (!Asn1RelativeOid.IsValidIdentifier(identifier, 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)
@@ -314,5 +321,39 @@ namespace Org.BouncyCastle.Asn1
 
             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();
+        }
     }
 }
diff --git a/crypto/test/src/asn1/test/OIDTest.cs b/crypto/test/src/asn1/test/OIDTest.cs
index 6b6993855..5c8142554 100644
--- a/crypto/test/src/asn1/test/OIDTest.cs
+++ b/crypto/test/src/asn1/test/OIDTest.cs
@@ -44,19 +44,23 @@ namespace Org.BouncyCastle.Asn1.Tests
 
 		private void CheckValid(string oid)
 		{
-			DerObjectIdentifier o = new DerObjectIdentifier(oid);
+            Assert.True(DerObjectIdentifier.TryFromID(oid, out var ignore));
+
+            DerObjectIdentifier o = new DerObjectIdentifier(oid);
 			o = (DerObjectIdentifier)Asn1Object.FromByteArray(o.GetEncoded());
 
 			if (!o.Id.Equals(oid))
 			{
 				Fail("failed oid check: " + oid);
 			}
-		}
+        }
 
-		private void CheckInvalid(string oid)
+        private void CheckInvalid(string oid)
 		{
-			try
-			{
+            Assert.False(DerObjectIdentifier.TryFromID(oid, out var ignore));
+
+            try
+            {
 				new DerObjectIdentifier(oid);
 				Fail("failed to catch bad oid: " + oid);
 			}
@@ -95,8 +99,20 @@ namespace Org.BouncyCastle.Asn1.Tests
 			CheckValid("1.1.127.32512.8323072.2130706432.545460846592.139637976727552.35747322042253312.9151314442816847872");
 			CheckValid("1.2.123.12345678901.1.1.1");
 			CheckValid("2.25.196556539987194312349856245628873852187.1");
-
-			CheckInvalid("0");
+            CheckValid("0.0");
+            CheckValid("0.0.1");
+            CheckValid("0.39");
+            CheckValid("0.39.1");
+            CheckValid("1.0");
+            CheckValid("1.0.1");
+            CheckValid("1.39");
+            CheckValid("1.39.1");
+            CheckValid("2.0");
+            CheckValid("2.0.1");
+            CheckValid("2.40");
+            CheckValid("2.40.1");
+
+            CheckInvalid("0");
 			CheckInvalid("1");
 			CheckInvalid("2");
 			CheckInvalid("3.1");
@@ -110,8 +126,16 @@ namespace Org.BouncyCastle.Asn1.Tests
 			CheckInvalid(".12.345.77.234.");
 			CheckInvalid("1.2.3.4.A.5");
 			CheckInvalid("1,2");
+            CheckInvalid("0.40");
+            CheckInvalid("0.40.1");
+            CheckInvalid("0.100");
+            CheckInvalid("0.100.1");
+            CheckInvalid("1.40");
+            CheckInvalid("1.40.1");
+            CheckInvalid("1.100");
+            CheckInvalid("1.100.1");
 
-			BranchCheck("1.1", "2.2");
+            BranchCheck("1.1", "2.2");
 
 			OnCheck("1.1", "1.1", false);
 			OnCheck("1.1", "1.2", false);
@@ -121,9 +145,12 @@ namespace Org.BouncyCastle.Asn1.Tests
 			OnCheck("1.12", "1.1.2", false);
 			OnCheck("1.1", "1.1.1", true);
 			OnCheck("1.1", "1.1.2", true);
-		}
+            OnCheck("1.2.3.4.5.6", "1.2.3.4.5.6", false);
+            OnCheck("1.2.3.4.5.6", "1.2.3.4.5.6.7", true);
+            OnCheck("1.2.3.4.5.6", "1.2.3.4.5.6.7.8", true);
+        }
 
-		[Test]
+        [Test]
 		public void TestFunction()
 		{
 			string resultText = Perform().ToString();
diff --git a/crypto/test/src/asn1/test/RelativeOidTest.cs b/crypto/test/src/asn1/test/RelativeOidTest.cs
index f3d5ba13b..c7d13af68 100644
--- a/crypto/test/src/asn1/test/RelativeOidTest.cs
+++ b/crypto/test/src/asn1/test/RelativeOidTest.cs
@@ -40,6 +40,8 @@ namespace Org.BouncyCastle.Asn1.Tests
 
         private void CheckValid(string oid)
         {
+            Assert.True(Asn1RelativeOid.TryFromID(oid, out var ignore));
+
             Asn1RelativeOid o = new Asn1RelativeOid(oid);
 			o = (Asn1RelativeOid)Asn1Object.FromByteArray(o.GetEncoded());
 
@@ -51,6 +53,8 @@ namespace Org.BouncyCastle.Asn1.Tests
 
         private void CheckInvalid(string oid)
         {
+            Assert.False(Asn1RelativeOid.TryFromID(oid, out var ignore));
+
             try
             {
                 new Asn1RelativeOid(oid);