Skip to content

Commit

Permalink
Limit the size of OID supported by AsnDecoder/AsnReader
Browse files Browse the repository at this point in the history
Assuming we don't run across a case where we need to service in
an AppContext switch for compat, we should look at replacing BigInteger
with Int128 in a subsequent release.
  • Loading branch information
bartonjs committed Jul 10, 2024
1 parent bc7c043 commit f657459
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 50 deletions.
3 changes: 3 additions & 0 deletions src/libraries/System.Formats.Asn1/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@
<data name="ContentException_NamedBitListValueTooBig" xml:space="preserve">
<value>The encoded named bit list value is larger than the value size of the '{0}' enum.</value>
</data>
<data name="ContentException_OidTooBig" xml:space="preserve">
<value>The encoded object identifier (OID) exceeds the limits supported by this library. Supported OIDs are limited to 64 arcs and each subidentifier is limited to a 128-bit value.</value>
</data>
<data name="ContentException_PrimitiveEncodingRequired" xml:space="preserve">
<value>The encoded value uses a constructed encoding, which is invalid for '{0}' values.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,49 @@ private static void ReadSubIdentifier(
throw new AsnContentException();
}

// Set semanticBits to a value such that on the first
// iteration of the loop it becomes the correct value.
// So each entry here is [real semantic bits for this value] - 7.
int semanticBits = source[0] switch
{
>= 0b1100_0000 => 0,
>= 0b1010_0000 => -1,
>= 0b1001_0000 => -2,
>= 0b1000_1000 => -3,
>= 0b1000_0100 => -4,
>= 0b1000_0010 => -5,
>= 0b1000_0001 => -6,
_ => 0,
};

// First, see how long the segment is
int end = -1;
int idx;

// None of T-REC-X.660-201107, T-REC-X.680-201508, or T-REC-X.690-201508
// have any recommendations for a minimum (or maximum) size of a
// sub-identifier.
//
// T-REC-X.667-201210 (and earlier versions) discuss the no-registration-
// required UUID space at 2.25.{UUID}, where UUIDs are defined as 128-bit
// values. This gives us a minimum lower bound of 128-bit.
//
// Windows Crypt32 has historically only supported 64-bit values, and
// the "size limitations" FAQ on oid-info.com says that the largest arc
// value is a 39-digit value that corresponds to a 2.25.UUID value.
//
// So, until something argues for a bigger number, our bit-limit is 128.
const int MaxAllowedBits = 128;

for (idx = 0; idx < source.Length; idx++)
{
semanticBits += 7;

if (semanticBits > MaxAllowedBits)
{
throw new AsnContentException(SR.ContentException_OidTooBig);
}

// If the high bit isn't set this marks the end of the sub-identifier.
bool endOfIdentifier = (source[idx] & 0x80) == 0;

Expand Down Expand Up @@ -265,8 +302,18 @@ private static string ReadObjectIdentifier(ReadOnlySpan<byte> contents)

contents = contents.Slice(bytesRead);

const int MaxArcs = 64;
int remainingArcs = MaxArcs - 2;

while (!contents.IsEmpty)
{
if (remainingArcs <= 0)
{
throw new AsnContentException(SR.ContentException_OidTooBig);
}

remainingArcs--;

ReadSubIdentifier(contents, out bytesRead, out smallValue, out largeValue);
// Exactly one should be non-null.
Debug.Assert((smallValue == null) != (largeValue == null));
Expand Down
122 changes: 72 additions & 50 deletions src/libraries/System.Formats.Asn1/tests/Reader/ReadObjectIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,70 +198,92 @@ public static void ExpectedTag_IgnoresConstructed(
[InlineData(AsnEncodingRules.BER)]
[InlineData(AsnEncodingRules.CER)]
[InlineData(AsnEncodingRules.DER)]
public static void ReadVeryLongOid(AsnEncodingRules ruleSet)
public static void ReadMaximumArcOid(AsnEncodingRules ruleSet)
{
byte[] inputData = new byte[100000];
// 06 83 02 00 00 (OBJECT IDENTIFIER, 65536 bytes).
inputData[0] = 0x06;
inputData[1] = 0x83;
inputData[2] = 0x01;
inputData[3] = 0x00;
inputData[4] = 0x00;
// and the rest are all zero.

// The first byte produces "0.0". Each of the remaining 65535 bytes produce
// another ".0".
const int ExpectedLength = 65536 * 2 + 1;
StringBuilder builder = new StringBuilder(ExpectedLength);
builder.Append('0');

for (int i = 0; i <= ushort.MaxValue; i++)
const int MaxArcs = 64;
// MaxArcs content bytes (all 0x7F) (which includes one for failure), plus one for the tag
// plus one for the encoded length.
byte[] input = new byte[MaxArcs + 2];
input.AsSpan().Fill(0x7F);
input[0] = 0x06;
// The first two arcs are encoded in the first sub-identifier, so MaxArcs - 1.
input[1] = MaxArcs - 1;

string decoded = AsnDecoder.ReadObjectIdentifier(input, ruleSet, out int consumed);
Assert.Equal(input.Length - 1, consumed);

StringBuilder expected = new StringBuilder(4 * MaxArcs);
expected.Append("2.47");

for (int i = 2; i < MaxArcs; i++)
{
builder.Append('.');
builder.Append(0);
expected.Append(".127");
}

AsnReader reader = new AsnReader(inputData, ruleSet);
string oidString = reader.ReadObjectIdentifier();
Assert.Equal(expected.ToString(), decoded);

Assert.Equal(ExpectedLength, oidString.Length);
Assert.Equal(builder.ToString(), oidString);
input[1] = MaxArcs;
AsnContentException ex = Assert.Throws<AsnContentException>(
() => AsnDecoder.ReadObjectIdentifier(input, ruleSet, out _));
Assert.Contains("OID", ex.Message);
}

[Theory]
[InlineData(AsnEncodingRules.BER)]
[InlineData(AsnEncodingRules.CER)]
[InlineData(AsnEncodingRules.DER)]
public static void ReadVeryLongOidArc(AsnEncodingRules ruleSet)
public static void ReadMaximumInitialSubIdentifier(AsnEncodingRules ruleSet)
{
byte[] inputData = new byte[255];
// 06 81 93 (OBJECT IDENTIFIER, 147 bytes).
inputData[0] = 0x06;
inputData[1] = 0x81;
inputData[2] = 0x93;

// With 147 bytes we get 147*7 = 1029 value bits.
// The smallest legal number to encode would have a top byte of 0x81,
// leaving 1022 bits remaining. If they're all zero then we have 2^1022.
//
// Since it's our first sub-identifier it's really encoding "2.(2^1022 - 80)".
inputData[3] = 0x81;
// Leave the last byte as 0.
new Span<byte>(inputData, 4, 145).Fill(0x80);

const string ExpectedOid =
"2." +
"449423283715578976932326297697256183404494244735576643183575" +
"202894331689513752407831771193306018840052800284699678483394" +
"146974422036041556232118576598685310944419733562163713190755" +
"549003115235298632707380212514422095376705856157203684782776" +
"352068092908376276711465745599868114846199290762088390824060" +
"56034224";
// First sub-identifier is 2^128 - 1, second is 1
byte[] valid =
{
0x06, 0x14, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x01,
};

AsnReader reader = new AsnReader(inputData, ruleSet);
// First sub-identifier is 2^128, second is 1
byte[] invalid =
{
0x06, 0x14, 0x84, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x00, 0x01,
};

string oid = AsnDecoder.ReadObjectIdentifier(valid, ruleSet, out int consumed);
Assert.Equal(valid.Length, consumed);
Assert.Equal("2.340282366920938463463374607431768211375.1", oid);

AsnContentException ex = Assert.Throws<AsnContentException>(
() => AsnDecoder.ReadObjectIdentifier(invalid, ruleSet, out _));
Assert.Contains("OID", ex.Message);
}

[Theory]
[InlineData(AsnEncodingRules.BER)]
[InlineData(AsnEncodingRules.CER)]
[InlineData(AsnEncodingRules.DER)]
public static void ReadMaximumNonInitialSubIdentifier(AsnEncodingRules ruleSet)
{
// First sub-identifier is 1, second is 2^128 - 1
byte[] valid =
{
0x06, 0x14, 0x01, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F,
};

// First sub-identifier is 1, second is 2^128
byte[] invalid = new byte[]
{
0x06, 0x14, 0x01, 0x84, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
};

string oid = AsnDecoder.ReadObjectIdentifier(valid, ruleSet, out int consumed);
Assert.Equal(valid.Length, consumed);
Assert.Equal("0.1.340282366920938463463374607431768211455", oid);

string oidString = reader.ReadObjectIdentifier();
Assert.Equal(ExpectedOid, oidString);
AsnContentException ex = Assert.Throws<AsnContentException>(
() => AsnDecoder.ReadObjectIdentifier(invalid, ruleSet, out _));
Assert.Contains("OID", ex.Message);
}
}
}

0 comments on commit f657459

Please sign in to comment.