diff --git a/crypto/src/asn1/Asn1InputStream.cs b/crypto/src/asn1/Asn1InputStream.cs
index 96b0a1c66..3b5eaaa95 100644
--- a/crypto/src/asn1/Asn1InputStream.cs
+++ b/crypto/src/asn1/Asn1InputStream.cs
@@ -377,7 +377,9 @@ namespace Org.BouncyCastle.Asn1
switch (tagNo)
{
case Asn1Tags.BmpString:
+ {
return CreateDerBmpString(defIn);
+ }
case Asn1Tags.Boolean:
{
GetBuffer(defIn, tmpBuffers, out var contents);
@@ -390,9 +392,16 @@ namespace Org.BouncyCastle.Asn1
}
case Asn1Tags.ObjectIdentifier:
{
+ DerObjectIdentifier.CheckContentsLength(defIn.Remaining);
bool usedBuffer = GetBuffer(defIn, tmpBuffers, out var contents);
return DerObjectIdentifier.CreatePrimitive(contents, clone: usedBuffer);
}
+ case Asn1Tags.RelativeOid:
+ {
+ Asn1RelativeOid.CheckContentsLength(defIn.Remaining);
+ bool usedBuffer = GetBuffer(defIn, tmpBuffers, out var contents);
+ return Asn1RelativeOid.CreatePrimitive(contents, clone: usedBuffer);
+ }
}
byte[] bytes = defIn.ToArray();
@@ -421,8 +430,6 @@ namespace Org.BouncyCastle.Asn1
return Asn1OctetString.CreatePrimitive(bytes);
case Asn1Tags.PrintableString:
return DerPrintableString.CreatePrimitive(bytes);
- case Asn1Tags.RelativeOid:
- return Asn1RelativeOid.CreatePrimitive(bytes, false);
case Asn1Tags.T61String:
return DerT61String.CreatePrimitive(bytes);
case Asn1Tags.UniversalString:
diff --git a/crypto/src/asn1/Asn1RelativeOid.cs b/crypto/src/asn1/Asn1RelativeOid.cs
index f43a85479..1fbf83d5c 100644
--- a/crypto/src/asn1/Asn1RelativeOid.cs
+++ b/crypto/src/asn1/Asn1RelativeOid.cs
@@ -22,6 +22,16 @@ namespace Org.BouncyCastle.Asn1
}
}
+ /// <summary>Implementation limit on the length of the contents octets for a Relative OID.</summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ private const int MaxContentsLength = 4096;
+ private const int MaxIdentifierLength = MaxContentsLength * 4 - 1;
+
public static Asn1RelativeOid FromContents(byte[] contents)
{
if (contents == null)
@@ -68,14 +78,18 @@ namespace Org.BouncyCastle.Asn1
{
if (identifier == null)
throw new ArgumentNullException(nameof(identifier));
- if (!IsValidIdentifier(identifier, 0))
+ if (identifier.Length <= MaxIdentifierLength && IsValidIdentifier(identifier, from: 0))
{
- oid = default;
- return false;
+ byte[] contents = ParseIdentifier(identifier);
+ if (contents.Length <= MaxContentsLength)
+ {
+ oid = new Asn1RelativeOid(contents, identifier);
+ return true;
+ }
}
- oid = new Asn1RelativeOid(ParseIdentifier(identifier), identifier);
- return true;
+ oid = default;
+ return false;
}
private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
@@ -85,31 +99,13 @@ namespace Org.BouncyCastle.Asn1
public Asn1RelativeOid(string identifier)
{
- if (identifier == null)
- throw new ArgumentNullException("identifier");
- if (!IsValidIdentifier(identifier, 0))
- throw new FormatException("string " + identifier + " not a relative OID");
-
- m_contents = ParseIdentifier(identifier);
- m_identifier = identifier;
- }
-
- private Asn1RelativeOid(Asn1RelativeOid oid, string branchID)
- {
- if (!IsValidIdentifier(branchID, 0))
- throw new FormatException("string " + branchID + " not a valid relative OID branch");
-
- m_contents = Arrays.Concatenate(oid.m_contents, ParseIdentifier(branchID));
- m_identifier = oid.GetID() + "." + branchID;
- }
+ CheckIdentifier(identifier);
- private Asn1RelativeOid(byte[] contents, bool clone)
- {
- if (!IsValidContents(contents))
- throw new ArgumentException("invalid relative OID contents", nameof(contents));
+ byte[] contents = ParseIdentifier(identifier);
+ CheckContentsLength(contents.Length);
- m_contents = clone ? Arrays.Clone(contents) : contents;
- m_identifier = null;
+ m_contents = contents;
+ m_identifier = identifier;
}
private Asn1RelativeOid(byte[] contents, string identifier)
@@ -120,7 +116,14 @@ namespace Org.BouncyCastle.Asn1
public virtual Asn1RelativeOid Branch(string branchID)
{
- return new Asn1RelativeOid(this, 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()
@@ -165,9 +168,30 @@ namespace Org.BouncyCastle.Asn1
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)
{
- return new Asn1RelativeOid(contents, clone);
+ CheckContentsLength(contents.Length);
+
+ if (!IsValidContents(contents))
+ throw new ArgumentException("invalid relative OID contents", nameof(contents));
+
+ return new Asn1RelativeOid(clone ? Arrays.Clone(contents) : contents, identifier: null);
}
internal static bool IsValidContents(byte[] contents)
diff --git a/crypto/src/asn1/DerObjectIdentifier.cs b/crypto/src/asn1/DerObjectIdentifier.cs
index 04792cbdd..7e1d5c2ff 100644
--- a/crypto/src/asn1/DerObjectIdentifier.cs
+++ b/crypto/src/asn1/DerObjectIdentifier.cs
@@ -23,6 +23,16 @@ namespace Org.BouncyCastle.Asn1
}
}
+ /// <summary>Implementation limit on the length of the contents octets for an Object Identifier.</summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ private const int MaxContentsLength = 4096;
+ private const int MaxIdentifierLength = MaxContentsLength * 4 + 1;
+
public static DerObjectIdentifier FromContents(byte[] contents)
{
if (contents == null)
@@ -86,14 +96,18 @@ namespace Org.BouncyCastle.Asn1
{
if (identifier == null)
throw new ArgumentNullException(nameof(identifier));
- if (!IsValidIdentifier(identifier))
+ if (identifier.Length <= MaxIdentifierLength && IsValidIdentifier(identifier))
{
- oid = default;
- return false;
+ byte[] contents = ParseIdentifier(identifier);
+ if (contents.Length <= MaxContentsLength)
+ {
+ oid = new DerObjectIdentifier(contents, identifier);
+ return true;
+ }
}
- oid = new DerObjectIdentifier(ParseIdentifier(identifier), identifier);
- return true;
+ oid = default;
+ return false;
}
private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
@@ -105,22 +119,13 @@ namespace Org.BouncyCastle.Asn1
public DerObjectIdentifier(string identifier)
{
- if (identifier == null)
- throw new ArgumentNullException("identifier");
- if (!IsValidIdentifier(identifier))
- throw new FormatException("string " + identifier + " not an OID");
+ CheckIdentifier(identifier);
- m_contents = ParseIdentifier(identifier);
- m_identifier = identifier;
- }
+ byte[] contents = ParseIdentifier(identifier);
+ CheckContentsLength(contents.Length);
- private DerObjectIdentifier(byte[] contents, bool clone)
- {
- if (!Asn1RelativeOid.IsValidContents(contents))
- throw new ArgumentException("invalid OID contents", nameof(contents));
-
- m_contents = clone ? Arrays.Clone(contents) : contents;
- m_identifier = null;
+ m_contents = contents;
+ m_identifier = identifier;
}
private DerObjectIdentifier(byte[] contents, string identifier)
@@ -131,11 +136,13 @@ namespace Org.BouncyCastle.Asn1
public virtual DerObjectIdentifier Branch(string branchID)
{
- if (!Asn1RelativeOid.IsValidIdentifier(branchID, 0))
- throw new FormatException("string " + branchID + " not a valid OID branch");
+ Asn1RelativeOid.CheckIdentifier(branchID);
+
+ byte[] branchContents = Asn1RelativeOid.ParseIdentifier(branchID);
+ CheckContentsLength(m_contents.Length + branchContents.Length);
return new DerObjectIdentifier(
- contents: Arrays.Concatenate(m_contents, Asn1RelativeOid.ParseIdentifier(branchID)),
+ contents: Arrays.Concatenate(m_contents, branchContents),
identifier: GetID() + "." + branchID);
}
@@ -195,9 +202,27 @@ namespace Org.BouncyCastle.Asn1
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)
{
- int index = Arrays.GetHashCode(contents);
+ CheckContentsLength(contents.Length);
+
+ uint index = (uint)Arrays.GetHashCode(contents);
index ^= index >> 20;
index ^= index >> 10;
@@ -207,7 +232,10 @@ namespace Org.BouncyCastle.Asn1
if (originalEntry != null && Arrays.AreEqual(contents, originalEntry.m_contents))
return originalEntry;
- var newEntry = new DerObjectIdentifier(contents, clone);
+ 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)
@@ -228,7 +256,7 @@ namespace Org.BouncyCastle.Asn1
if (first < '0' || first > '2')
return false;
- if (!Asn1RelativeOid.IsValidIdentifier(identifier, 2))
+ if (!Asn1RelativeOid.IsValidIdentifier(identifier, from: 2))
return false;
if (first == '2')
|