Skip to content

Commit f657459

Browse files
authored
Limit the size of OID supported by AsnDecoder/AsnReader
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.
1 parent bc7c043 commit f657459

File tree

3 files changed

+122
-50
lines changed

3 files changed

+122
-50
lines changed

src/libraries/System.Formats.Asn1/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@
144144
<data name="ContentException_NamedBitListValueTooBig" xml:space="preserve">
145145
<value>The encoded named bit list value is larger than the value size of the '{0}' enum.</value>
146146
</data>
147+
<data name="ContentException_OidTooBig" xml:space="preserve">
148+
<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>
149+
</data>
147150
<data name="ContentException_PrimitiveEncodingRequired" xml:space="preserve">
148151
<value>The encoded value uses a constructed encoding, which is invalid for '{0}' values.</value>
149152
</data>

src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,49 @@ private static void ReadSubIdentifier(
9090
throw new AsnContentException();
9191
}
9292

93+
// Set semanticBits to a value such that on the first
94+
// iteration of the loop it becomes the correct value.
95+
// So each entry here is [real semantic bits for this value] - 7.
96+
int semanticBits = source[0] switch
97+
{
98+
>= 0b1100_0000 => 0,
99+
>= 0b1010_0000 => -1,
100+
>= 0b1001_0000 => -2,
101+
>= 0b1000_1000 => -3,
102+
>= 0b1000_0100 => -4,
103+
>= 0b1000_0010 => -5,
104+
>= 0b1000_0001 => -6,
105+
_ => 0,
106+
};
107+
93108
// First, see how long the segment is
94109
int end = -1;
95110
int idx;
96111

112+
// None of T-REC-X.660-201107, T-REC-X.680-201508, or T-REC-X.690-201508
113+
// have any recommendations for a minimum (or maximum) size of a
114+
// sub-identifier.
115+
//
116+
// T-REC-X.667-201210 (and earlier versions) discuss the no-registration-
117+
// required UUID space at 2.25.{UUID}, where UUIDs are defined as 128-bit
118+
// values. This gives us a minimum lower bound of 128-bit.
119+
//
120+
// Windows Crypt32 has historically only supported 64-bit values, and
121+
// the "size limitations" FAQ on oid-info.com says that the largest arc
122+
// value is a 39-digit value that corresponds to a 2.25.UUID value.
123+
//
124+
// So, until something argues for a bigger number, our bit-limit is 128.
125+
const int MaxAllowedBits = 128;
126+
97127
for (idx = 0; idx < source.Length; idx++)
98128
{
129+
semanticBits += 7;
130+
131+
if (semanticBits > MaxAllowedBits)
132+
{
133+
throw new AsnContentException(SR.ContentException_OidTooBig);
134+
}
135+
99136
// If the high bit isn't set this marks the end of the sub-identifier.
100137
bool endOfIdentifier = (source[idx] & 0x80) == 0;
101138

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

266303
contents = contents.Slice(bytesRead);
267304

305+
const int MaxArcs = 64;
306+
int remainingArcs = MaxArcs - 2;
307+
268308
while (!contents.IsEmpty)
269309
{
310+
if (remainingArcs <= 0)
311+
{
312+
throw new AsnContentException(SR.ContentException_OidTooBig);
313+
}
314+
315+
remainingArcs--;
316+
270317
ReadSubIdentifier(contents, out bytesRead, out smallValue, out largeValue);
271318
// Exactly one should be non-null.
272319
Debug.Assert((smallValue == null) != (largeValue == null));

src/libraries/System.Formats.Asn1/tests/Reader/ReadObjectIdentifier.cs

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -198,70 +198,92 @@ public static void ExpectedTag_IgnoresConstructed(
198198
[InlineData(AsnEncodingRules.BER)]
199199
[InlineData(AsnEncodingRules.CER)]
200200
[InlineData(AsnEncodingRules.DER)]
201-
public static void ReadVeryLongOid(AsnEncodingRules ruleSet)
201+
public static void ReadMaximumArcOid(AsnEncodingRules ruleSet)
202202
{
203-
byte[] inputData = new byte[100000];
204-
// 06 83 02 00 00 (OBJECT IDENTIFIER, 65536 bytes).
205-
inputData[0] = 0x06;
206-
inputData[1] = 0x83;
207-
inputData[2] = 0x01;
208-
inputData[3] = 0x00;
209-
inputData[4] = 0x00;
210-
// and the rest are all zero.
211-
212-
// The first byte produces "0.0". Each of the remaining 65535 bytes produce
213-
// another ".0".
214-
const int ExpectedLength = 65536 * 2 + 1;
215-
StringBuilder builder = new StringBuilder(ExpectedLength);
216-
builder.Append('0');
217-
218-
for (int i = 0; i <= ushort.MaxValue; i++)
203+
const int MaxArcs = 64;
204+
// MaxArcs content bytes (all 0x7F) (which includes one for failure), plus one for the tag
205+
// plus one for the encoded length.
206+
byte[] input = new byte[MaxArcs + 2];
207+
input.AsSpan().Fill(0x7F);
208+
input[0] = 0x06;
209+
// The first two arcs are encoded in the first sub-identifier, so MaxArcs - 1.
210+
input[1] = MaxArcs - 1;
211+
212+
string decoded = AsnDecoder.ReadObjectIdentifier(input, ruleSet, out int consumed);
213+
Assert.Equal(input.Length - 1, consumed);
214+
215+
StringBuilder expected = new StringBuilder(4 * MaxArcs);
216+
expected.Append("2.47");
217+
218+
for (int i = 2; i < MaxArcs; i++)
219219
{
220-
builder.Append('.');
221-
builder.Append(0);
220+
expected.Append(".127");
222221
}
223222

224-
AsnReader reader = new AsnReader(inputData, ruleSet);
225-
string oidString = reader.ReadObjectIdentifier();
223+
Assert.Equal(expected.ToString(), decoded);
226224

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

231231
[Theory]
232232
[InlineData(AsnEncodingRules.BER)]
233233
[InlineData(AsnEncodingRules.CER)]
234234
[InlineData(AsnEncodingRules.DER)]
235-
public static void ReadVeryLongOidArc(AsnEncodingRules ruleSet)
235+
public static void ReadMaximumInitialSubIdentifier(AsnEncodingRules ruleSet)
236236
{
237-
byte[] inputData = new byte[255];
238-
// 06 81 93 (OBJECT IDENTIFIER, 147 bytes).
239-
inputData[0] = 0x06;
240-
inputData[1] = 0x81;
241-
inputData[2] = 0x93;
242-
243-
// With 147 bytes we get 147*7 = 1029 value bits.
244-
// The smallest legal number to encode would have a top byte of 0x81,
245-
// leaving 1022 bits remaining. If they're all zero then we have 2^1022.
246-
//
247-
// Since it's our first sub-identifier it's really encoding "2.(2^1022 - 80)".
248-
inputData[3] = 0x81;
249-
// Leave the last byte as 0.
250-
new Span<byte>(inputData, 4, 145).Fill(0x80);
251-
252-
const string ExpectedOid =
253-
"2." +
254-
"449423283715578976932326297697256183404494244735576643183575" +
255-
"202894331689513752407831771193306018840052800284699678483394" +
256-
"146974422036041556232118576598685310944419733562163713190755" +
257-
"549003115235298632707380212514422095376705856157203684782776" +
258-
"352068092908376276711465745599868114846199290762088390824060" +
259-
"56034224";
237+
// First sub-identifier is 2^128 - 1, second is 1
238+
byte[] valid =
239+
{
240+
0x06, 0x14, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
241+
0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x01,
242+
};
260243

261-
AsnReader reader = new AsnReader(inputData, ruleSet);
244+
// First sub-identifier is 2^128, second is 1
245+
byte[] invalid =
246+
{
247+
0x06, 0x14, 0x84, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
248+
0x80, 0x80, 0x80, 0x80, 0x00, 0x01,
249+
};
250+
251+
string oid = AsnDecoder.ReadObjectIdentifier(valid, ruleSet, out int consumed);
252+
Assert.Equal(valid.Length, consumed);
253+
Assert.Equal("2.340282366920938463463374607431768211375.1", oid);
254+
255+
AsnContentException ex = Assert.Throws<AsnContentException>(
256+
() => AsnDecoder.ReadObjectIdentifier(invalid, ruleSet, out _));
257+
Assert.Contains("OID", ex.Message);
258+
}
259+
260+
[Theory]
261+
[InlineData(AsnEncodingRules.BER)]
262+
[InlineData(AsnEncodingRules.CER)]
263+
[InlineData(AsnEncodingRules.DER)]
264+
public static void ReadMaximumNonInitialSubIdentifier(AsnEncodingRules ruleSet)
265+
{
266+
// First sub-identifier is 1, second is 2^128 - 1
267+
byte[] valid =
268+
{
269+
0x06, 0x14, 0x01, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
270+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F,
271+
};
272+
273+
// First sub-identifier is 1, second is 2^128
274+
byte[] invalid = new byte[]
275+
{
276+
0x06, 0x14, 0x01, 0x84, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
277+
0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
278+
};
279+
280+
string oid = AsnDecoder.ReadObjectIdentifier(valid, ruleSet, out int consumed);
281+
Assert.Equal(valid.Length, consumed);
282+
Assert.Equal("0.1.340282366920938463463374607431768211455", oid);
262283

263-
string oidString = reader.ReadObjectIdentifier();
264-
Assert.Equal(ExpectedOid, oidString);
284+
AsnContentException ex = Assert.Throws<AsnContentException>(
285+
() => AsnDecoder.ReadObjectIdentifier(invalid, ruleSet, out _));
286+
Assert.Contains("OID", ex.Message);
265287
}
266288
}
267289
}

0 commit comments

Comments
 (0)