Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit ec6014e

Browse files
authored
Create a Memory/Span-based ASN.1 reader and writer (#25296)
The AsnReader type can read any BER-encoded value for the types that it supports (for tag values up to int.MaxValue). It also understands the CER and DER restrictions, and when reading in those modes it will enforce the restrictions that those encoding rulesets dictate. Callers who want minimal validation can just read in BER mode. The AsnWriter type mostly writes in DER (except where CER demands a different encoding). The only significant "BER-relaxation" it takes is that closing a SET OF value will not sort the contents. Reference materials: * ITU-T-REC-X.680-201508 (ASN.1 language, and some semantics behind the values) * ITU-T-REC-X.690-201508 (the BER encoding family (BER, CER, DER))
1 parent fc0e922 commit ec6014e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+18045
-2
lines changed

src/Common/src/System/Security/Cryptography/Asn1V2.cs

Lines changed: 738 additions & 0 deletions
Large diffs are not rendered by default.

src/Common/src/System/Security/Cryptography/AsnReader.cs

Lines changed: 2910 additions & 0 deletions
Large diffs are not rendered by default.

src/Common/src/System/Security/Cryptography/AsnWriter.cs

Lines changed: 1602 additions & 0 deletions
Large diffs are not rendered by default.

src/Common/tests/System/Security/Cryptography/ByteUtils.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Globalization;
67
using System.Text;
78

@@ -35,12 +36,27 @@ internal static byte[] HexToByteArray(this string hexString)
3536
}
3637

3738
internal static string ByteArrayToHex(this byte[] bytes)
39+
{
40+
return ByteArrayToHex(bytes.AsReadOnlySpan());
41+
}
42+
43+
internal static string ByteArrayToHex(this Span<byte> bytes)
44+
{
45+
return ByteArrayToHex((ReadOnlySpan<byte>)bytes);
46+
}
47+
48+
internal static string ByteArrayToHex(this ReadOnlyMemory<byte> bytes)
49+
{
50+
return ByteArrayToHex(bytes.Span);
51+
}
52+
53+
internal static string ByteArrayToHex(this ReadOnlySpan<byte> bytes)
3854
{
3955
StringBuilder builder = new StringBuilder(bytes.Length * 2);
4056

41-
foreach (byte b in bytes)
57+
for (int i = 0; i < bytes.Length; i++)
4258
{
43-
builder.Append(b.ToString("X2"));
59+
builder.Append(bytes[i].ToString("X2"));
4460
}
4561

4662
return builder.ToString();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Security.Cryptography.Asn1;
6+
7+
namespace System.Security.Cryptography.Tests.Asn1
8+
{
9+
public abstract partial class Asn1ReaderTests
10+
{
11+
public enum PublicTagClass : byte
12+
{
13+
Universal = TagClass.Universal,
14+
Application = TagClass.Application,
15+
ContextSpecific = TagClass.ContextSpecific,
16+
Private = TagClass.Private,
17+
}
18+
19+
public enum PublicEncodingRules
20+
{
21+
BER = AsnEncodingRules.BER,
22+
CER = AsnEncodingRules.CER,
23+
DER = AsnEncodingRules.DER,
24+
}
25+
}
26+
}

src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ComprehensiveReadTests.cs

Lines changed: 265 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Security.Cryptography.Asn1;
6+
using Test.Cryptography;
7+
using Xunit;
8+
9+
namespace System.Security.Cryptography.Tests.Asn1
10+
{
11+
public sealed class ParseTag : Asn1ReaderTests
12+
{
13+
[Theory]
14+
[InlineData(PublicTagClass.Universal, false, 0, "00")]
15+
[InlineData(PublicTagClass.Universal, false, 1, "01")]
16+
[InlineData(PublicTagClass.Application, true, 1, "61")]
17+
[InlineData(PublicTagClass.ContextSpecific, false, 1, "81")]
18+
[InlineData(PublicTagClass.ContextSpecific, true, 1, "A1")]
19+
[InlineData(PublicTagClass.Private, false, 1, "C1")]
20+
[InlineData(PublicTagClass.Universal, false, 30, "1E")]
21+
[InlineData(PublicTagClass.Application, false, 30, "5E")]
22+
[InlineData(PublicTagClass.ContextSpecific, false, 30, "9E")]
23+
[InlineData(PublicTagClass.Private, false, 30, "DE")]
24+
[InlineData(PublicTagClass.Universal, false, 31, "1F1F")]
25+
[InlineData(PublicTagClass.Application, false, 31, "5F1F")]
26+
[InlineData(PublicTagClass.ContextSpecific, false, 31, "9F1F")]
27+
[InlineData(PublicTagClass.Private, false, 31, "DF1F")]
28+
[InlineData(PublicTagClass.Private, false, 127, "DF7F")]
29+
[InlineData(PublicTagClass.Private, false, 128, "DF8100")]
30+
[InlineData(PublicTagClass.Private, false, 253, "DF817D")]
31+
[InlineData(PublicTagClass.Private, false, 255, "DF817F")]
32+
[InlineData(PublicTagClass.Private, false, 256, "DF8200")]
33+
[InlineData(PublicTagClass.Private, false, 1 << 9, "DF8400")]
34+
[InlineData(PublicTagClass.Private, false, 1 << 10, "DF8800")]
35+
[InlineData(PublicTagClass.Private, false, 0b0011_1101_1110_0111, "DFFB67")]
36+
[InlineData(PublicTagClass.Private, false, 1 << 14, "DF818000")]
37+
[InlineData(PublicTagClass.Private, false, 1 << 18, "DF908000")]
38+
[InlineData(PublicTagClass.Private, false, 1 << 18 | 1 << 9, "DF908400")]
39+
[InlineData(PublicTagClass.Private, false, 1 << 20, "DFC08000")]
40+
[InlineData(PublicTagClass.Private, false, 0b0001_1110_1010_0111_0000_0001, "DFFACE01")]
41+
[InlineData(PublicTagClass.Private, false, 1 << 21, "DF81808000")]
42+
[InlineData(PublicTagClass.Private, false, 1 << 27, "DFC0808000")]
43+
[InlineData(PublicTagClass.Private, false, 1 << 28, "DF8180808000")]
44+
[InlineData(PublicTagClass.Private, true, int.MaxValue, "FF87FFFFFF7F")]
45+
[InlineData(PublicTagClass.Universal, false, 119, "1F77")]
46+
public static void ParseValidTag(
47+
PublicTagClass tagClass,
48+
bool isConstructed,
49+
int tagValue,
50+
string inputHex)
51+
{
52+
byte[] inputBytes = inputHex.HexToByteArray();
53+
54+
bool parsed = Asn1Tag.TryParse(inputBytes, out Asn1Tag tag, out int bytesRead);
55+
56+
Assert.True(parsed, "Asn1Tag.TryParse");
57+
Assert.Equal(inputBytes.Length, bytesRead);
58+
Assert.Equal((TagClass)tagClass, tag.TagClass);
59+
Assert.Equal(tagValue, tag.TagValue);
60+
61+
if (isConstructed)
62+
{
63+
Assert.True(tag.IsConstructed, "tag.IsConstructed");
64+
}
65+
else
66+
{
67+
Assert.False(tag.IsConstructed, "tag.IsConstructed");
68+
}
69+
70+
byte[] secondBytes = new byte[inputBytes.Length];
71+
int written;
72+
Assert.False(tag.TryWrite(secondBytes.AsSpan().Slice(0, inputBytes.Length - 1), out written));
73+
Assert.Equal(0, written);
74+
Assert.True(tag.TryWrite(secondBytes, out written));
75+
Assert.Equal(inputBytes.Length, written);
76+
Assert.Equal(inputHex, secondBytes.ByteArrayToHex());
77+
}
78+
79+
[Theory]
80+
[InlineData("Empty", "")]
81+
[InlineData("MultiByte-NoFollow", "1F")]
82+
[InlineData("MultiByte-NoFollow2", "1F81")]
83+
[InlineData("MultiByte-NoFollow3", "1F8180")]
84+
[InlineData("MultiByte-TooLow", "1F01")]
85+
[InlineData("MultiByte-TooLowMax", "1F1E")]
86+
[InlineData("MultiByte-Leading0", "1F807F")]
87+
[InlineData("MultiByte-ValueTooBig", "FF8880808000")]
88+
[InlineData("MultiByte-ValueSubtlyTooBig", "DFC1C0808000")]
89+
public static void ParseCorruptTag(string description, string inputHex)
90+
{
91+
byte[] inputBytes = inputHex.HexToByteArray();
92+
93+
Assert.False(Asn1Tag.TryParse(inputBytes, out Asn1Tag tag, out var bytesRead));
94+
95+
Assert.Equal(default(Asn1Tag), tag);
96+
Assert.Equal(0, bytesRead);
97+
}
98+
99+
[Fact]
100+
public static void TestEquals()
101+
{
102+
Asn1Tag integer = new Asn1Tag(TagClass.Universal, 2);
103+
Asn1Tag integerAgain = new Asn1Tag(TagClass.Universal, 2);
104+
Asn1Tag context2 = new Asn1Tag(TagClass.ContextSpecific, 2);
105+
Asn1Tag constructedContext2 = new Asn1Tag(TagClass.ContextSpecific, 2, true);
106+
Asn1Tag application2 = new Asn1Tag(TagClass.Application, 2);
107+
108+
Assert.False(integer.Equals(null));
109+
Assert.False(integer.Equals(0x02));
110+
Assert.False(integer.Equals(context2));
111+
Assert.False(context2.Equals(constructedContext2));
112+
Assert.False(context2.Equals(application2));
113+
114+
Assert.Equal(integer, integerAgain);
115+
Assert.True(integer == integerAgain);
116+
Assert.True(integer != context2);
117+
Assert.False(integer == context2);
118+
Assert.False(context2 == constructedContext2);
119+
Assert.False(context2 == application2);
120+
121+
Assert.NotEqual(integer.GetHashCode(), context2.GetHashCode());
122+
Assert.NotEqual(context2.GetHashCode(), constructedContext2.GetHashCode());
123+
Assert.NotEqual(context2.GetHashCode(), application2.GetHashCode());
124+
Assert.Equal(integer.GetHashCode(), integerAgain.GetHashCode());
125+
}
126+
127+
[Theory]
128+
[InlineData(PublicTagClass.Universal, false, 0, "00")]
129+
[InlineData(PublicTagClass.ContextSpecific, true, 1, "A1")]
130+
[InlineData(PublicTagClass.Application, false, 31, "5F1F")]
131+
[InlineData(PublicTagClass.Private, false, 128, "DF8100")]
132+
[InlineData(PublicTagClass.Private, false, 0b0001_1110_1010_0111_0000_0001, "DFFACE01")]
133+
[InlineData(PublicTagClass.Private, true, int.MaxValue, "FF87FFFFFF7F")]
134+
public static void ParseTagWithMoreData(
135+
PublicTagClass tagClass,
136+
bool isConstructed,
137+
int tagValue,
138+
string inputHex)
139+
{
140+
byte[] inputBytes = inputHex.HexToByteArray();
141+
Array.Resize(ref inputBytes, inputBytes.Length + 3);
142+
143+
bool parsed = Asn1Tag.TryParse(inputBytes, out Asn1Tag tag, out int bytesRead);
144+
145+
Assert.True(parsed, "Asn1Tag.TryParse");
146+
Assert.Equal(inputHex.Length / 2, bytesRead);
147+
Assert.Equal((TagClass)tagClass, tag.TagClass);
148+
Assert.Equal(tagValue, tag.TagValue);
149+
150+
if (isConstructed)
151+
{
152+
Assert.True(tag.IsConstructed, "tag.IsConstructed");
153+
}
154+
else
155+
{
156+
Assert.False(tag.IsConstructed, "tag.IsConstructed");
157+
}
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)