Skip to content

Commit 7e2e3c9

Browse files
authored
[release/6.0] Ignore non-X509 certificates in SignedCms (#67462)
* Ignore non-X509 certificates in SignedCms This allows for AttributeCertificateV1/AttributeCertificateV2/OtherCertificate entries in the SignedCms without causing a decode error. That data is not presented to callers via the SignedCms object, but the behavior is consistent with SignedCms on .NET Framework and its underlying WinCryptMsg counterpart. * Add packaging changes for servicing
1 parent 1978e2f commit 7e2e3c9

File tree

8 files changed

+412
-8
lines changed

8 files changed

+412
-8
lines changed

src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<Nullable>enable</Nullable>
77
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netcoreapp3.1-windows;netcoreapp3.1;netstandard2.1-windows;netstandard2.1;netstandard2.0-windows;netstandard2.0;net461-windows</TargetFrameworks>
88
<IsPackable>true</IsPackable>
9+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
10+
<ServicingVersion>1</ServicingVersion>
911
<PackageDescription>Provides support for PKCS and CMS algorithms.
1012

1113
Commonly Used Types:
@@ -490,6 +492,10 @@ System.Security.Cryptography.Pkcs.EnvelopedCms</PackageDescription>
490492
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\MessageImprint.xml.cs">
491493
<DependentUpon>System\Security\Cryptography\Pkcs\Asn1\MessageImprint.xml</DependentUpon>
492494
</Compile>
495+
<AsnXml Include="System\Security\Cryptography\Pkcs\Asn1\OtherCertificateFormat.xml" />
496+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\OtherCertificateFormat.xml.cs">
497+
<DependentUpon>System\Security\Cryptography\Pkcs\Asn1\OtherCertificateFormat.xml</DependentUpon>
498+
</Compile>
493499
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\PkiFailureInfo.cs" />
494500
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\PkiStatus.cs" />
495501
<AsnXml Include="System\Security\Cryptography\Pkcs\Asn1\PkiStatusInfo.xml" />

src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CertificateChoiceAsn.xml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@
1515
other[3] IMPLICIT OtherCertificateFormat
1616
}
1717
18-
OtherCertificateFormat ::= SEQUENCE {
19-
otherCertFormat OBJECT IDENTIFIER,
20-
otherCert ANY DEFINED BY otherCertFormat
21-
}
22-
23-
Except we only support public key certificates, so just trim the choice here.
2418
-->
2519
<asn:AnyValue name="Certificate" universalTagNumber="16" />
20+
<asn:AnyValue name="ExtendedCertificate" implicitTag="0" />
21+
<asn:AnyValue name="AttributeCertificateV1" implicitTag="1" />
22+
<asn:AnyValue name="AttributeCertificateV2" implicitTag="2" />
23+
<asn:AsnType name="OtherCertificateFormat" typeName="System.Security.Cryptography.Pkcs.Asn1.OtherCertificateFormat" implicitTag="3" />
2624
</asn:Choice>

src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CertificateChoiceAsn.xml.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ namespace System.Security.Cryptography.Pkcs.Asn1
1212
internal partial struct CertificateChoiceAsn
1313
{
1414
internal ReadOnlyMemory<byte>? Certificate;
15+
internal ReadOnlyMemory<byte>? ExtendedCertificate;
16+
internal ReadOnlyMemory<byte>? AttributeCertificateV1;
17+
internal ReadOnlyMemory<byte>? AttributeCertificateV2;
18+
internal System.Security.Cryptography.Pkcs.Asn1.OtherCertificateFormat? OtherCertificateFormat;
1519

1620
#if DEBUG
1721
static CertificateChoiceAsn()
@@ -28,6 +32,10 @@ static CertificateChoiceAsn()
2832
};
2933

3034
ensureUniqueTag(new Asn1Tag((UniversalTagNumber)16), "Certificate");
35+
ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 0), "ExtendedCertificate");
36+
ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 1), "AttributeCertificateV1");
37+
ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 2), "AttributeCertificateV2");
38+
ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 3), "OtherCertificateFormat");
3139
}
3240
#endif
3341

@@ -60,6 +68,90 @@ internal void Encode(AsnWriter writer)
6068
wroteValue = true;
6169
}
6270

71+
if (ExtendedCertificate.HasValue)
72+
{
73+
if (wroteValue)
74+
throw new CryptographicException();
75+
76+
// Validator for tag constraint for ExtendedCertificate
77+
{
78+
if (!Asn1Tag.TryDecode(ExtendedCertificate.Value.Span, out Asn1Tag validateTag, out _) ||
79+
!validateTag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0)))
80+
{
81+
throw new CryptographicException();
82+
}
83+
}
84+
85+
try
86+
{
87+
writer.WriteEncodedValue(ExtendedCertificate.Value.Span);
88+
}
89+
catch (ArgumentException e)
90+
{
91+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
92+
}
93+
wroteValue = true;
94+
}
95+
96+
if (AttributeCertificateV1.HasValue)
97+
{
98+
if (wroteValue)
99+
throw new CryptographicException();
100+
101+
// Validator for tag constraint for AttributeCertificateV1
102+
{
103+
if (!Asn1Tag.TryDecode(AttributeCertificateV1.Value.Span, out Asn1Tag validateTag, out _) ||
104+
!validateTag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 1)))
105+
{
106+
throw new CryptographicException();
107+
}
108+
}
109+
110+
try
111+
{
112+
writer.WriteEncodedValue(AttributeCertificateV1.Value.Span);
113+
}
114+
catch (ArgumentException e)
115+
{
116+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
117+
}
118+
wroteValue = true;
119+
}
120+
121+
if (AttributeCertificateV2.HasValue)
122+
{
123+
if (wroteValue)
124+
throw new CryptographicException();
125+
126+
// Validator for tag constraint for AttributeCertificateV2
127+
{
128+
if (!Asn1Tag.TryDecode(AttributeCertificateV2.Value.Span, out Asn1Tag validateTag, out _) ||
129+
!validateTag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 2)))
130+
{
131+
throw new CryptographicException();
132+
}
133+
}
134+
135+
try
136+
{
137+
writer.WriteEncodedValue(AttributeCertificateV2.Value.Span);
138+
}
139+
catch (ArgumentException e)
140+
{
141+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
142+
}
143+
wroteValue = true;
144+
}
145+
146+
if (OtherCertificateFormat.HasValue)
147+
{
148+
if (wroteValue)
149+
throw new CryptographicException();
150+
151+
OtherCertificateFormat.Value.Encode(writer, new Asn1Tag(TagClass.ContextSpecific, 3));
152+
wroteValue = true;
153+
}
154+
63155
if (!wroteValue)
64156
{
65157
throw new CryptographicException();
@@ -107,6 +199,28 @@ private static void DecodeCore(ref AsnValueReader reader, ReadOnlyMemory<byte> r
107199
tmpSpan = reader.ReadEncodedValue();
108200
decoded.Certificate = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray();
109201
}
202+
else if (tag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0)))
203+
{
204+
tmpSpan = reader.ReadEncodedValue();
205+
decoded.ExtendedCertificate = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray();
206+
}
207+
else if (tag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 1)))
208+
{
209+
tmpSpan = reader.ReadEncodedValue();
210+
decoded.AttributeCertificateV1 = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray();
211+
}
212+
else if (tag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 2)))
213+
{
214+
tmpSpan = reader.ReadEncodedValue();
215+
decoded.AttributeCertificateV2 = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray();
216+
}
217+
else if (tag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 3)))
218+
{
219+
System.Security.Cryptography.Pkcs.Asn1.OtherCertificateFormat tmpOtherCertificateFormat;
220+
System.Security.Cryptography.Pkcs.Asn1.OtherCertificateFormat.Decode(ref reader, new Asn1Tag(TagClass.ContextSpecific, 3), rebind, out tmpOtherCertificateFormat);
221+
decoded.OtherCertificateFormat = tmpOtherCertificateFormat;
222+
223+
}
110224
else
111225
{
112226
throw new CryptographicException();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<asn:Sequence
3+
xmlns:asn="http://schemas.dot.net/asnxml/201808/"
4+
name="OtherCertificateFormat"
5+
namespace="System.Security.Cryptography.Pkcs.Asn1">
6+
7+
<!--
8+
https://tools.ietf.org/html/rfc5652#section-10.2.2
9+
10+
OtherCertificateFormat ::= SEQUENCE {
11+
otherCertFormat OBJECT IDENTIFIER,
12+
otherCert ANY DEFINED BY otherCertFormat
13+
}
14+
-->
15+
<asn:ObjectIdentifier name="OtherCertFormat" backingType="string" />
16+
<asn:AnyValue name="OtherCert" />
17+
</asn:Sequence>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
4+
#pragma warning disable SA1028 // ignore whitespace warnings for generated code
5+
using System;
6+
using System.Formats.Asn1;
7+
using System.Runtime.InteropServices;
8+
9+
namespace System.Security.Cryptography.Pkcs.Asn1
10+
{
11+
[StructLayout(LayoutKind.Sequential)]
12+
internal partial struct OtherCertificateFormat
13+
{
14+
internal string OtherCertFormat;
15+
internal ReadOnlyMemory<byte> OtherCert;
16+
17+
internal void Encode(AsnWriter writer)
18+
{
19+
Encode(writer, Asn1Tag.Sequence);
20+
}
21+
22+
internal void Encode(AsnWriter writer, Asn1Tag tag)
23+
{
24+
writer.PushSequence(tag);
25+
26+
try
27+
{
28+
writer.WriteObjectIdentifier(OtherCertFormat);
29+
}
30+
catch (ArgumentException e)
31+
{
32+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
33+
}
34+
try
35+
{
36+
writer.WriteEncodedValue(OtherCert.Span);
37+
}
38+
catch (ArgumentException e)
39+
{
40+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
41+
}
42+
writer.PopSequence(tag);
43+
}
44+
45+
internal static OtherCertificateFormat Decode(ReadOnlyMemory<byte> encoded, AsnEncodingRules ruleSet)
46+
{
47+
return Decode(Asn1Tag.Sequence, encoded, ruleSet);
48+
}
49+
50+
internal static OtherCertificateFormat Decode(Asn1Tag expectedTag, ReadOnlyMemory<byte> encoded, AsnEncodingRules ruleSet)
51+
{
52+
try
53+
{
54+
AsnValueReader reader = new AsnValueReader(encoded.Span, ruleSet);
55+
56+
DecodeCore(ref reader, expectedTag, encoded, out OtherCertificateFormat decoded);
57+
reader.ThrowIfNotEmpty();
58+
return decoded;
59+
}
60+
catch (AsnContentException e)
61+
{
62+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
63+
}
64+
}
65+
66+
internal static void Decode(ref AsnValueReader reader, ReadOnlyMemory<byte> rebind, out OtherCertificateFormat decoded)
67+
{
68+
Decode(ref reader, Asn1Tag.Sequence, rebind, out decoded);
69+
}
70+
71+
internal static void Decode(ref AsnValueReader reader, Asn1Tag expectedTag, ReadOnlyMemory<byte> rebind, out OtherCertificateFormat decoded)
72+
{
73+
try
74+
{
75+
DecodeCore(ref reader, expectedTag, rebind, out decoded);
76+
}
77+
catch (AsnContentException e)
78+
{
79+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
80+
}
81+
}
82+
83+
private static void DecodeCore(ref AsnValueReader reader, Asn1Tag expectedTag, ReadOnlyMemory<byte> rebind, out OtherCertificateFormat decoded)
84+
{
85+
decoded = default;
86+
AsnValueReader sequenceReader = reader.ReadSequence(expectedTag);
87+
ReadOnlySpan<byte> rebindSpan = rebind.Span;
88+
int offset;
89+
ReadOnlySpan<byte> tmpSpan;
90+
91+
decoded.OtherCertFormat = sequenceReader.ReadObjectIdentifier();
92+
tmpSpan = sequenceReader.ReadEncodedValue();
93+
decoded.OtherCert = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray();
94+
95+
sequenceReader.ThrowIfNotEmpty();
96+
}
97+
}
98+
}

src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,16 @@ public X509Certificate2Collection Certificates
8989

9090
foreach (CertificateChoiceAsn choice in certChoices)
9191
{
92-
Debug.Assert(choice.Certificate.HasValue);
93-
coll.Add(new X509Certificate2(choice.Certificate.Value.ToArray()));
92+
if (choice.Certificate.HasValue)
93+
{
94+
coll.Add(new X509Certificate2(choice.Certificate.Value
95+
#if NET5_0_OR_GREATER
96+
.Span
97+
#else
98+
.ToArray()
99+
#endif
100+
));
101+
}
94102
}
95103

96104
return coll;

src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,15 @@ public static void CheckNoSignature_FromCoreFx_TamperIssuerName()
16021602
signers[0].CheckHash();
16031603
}
16041604

1605+
[Fact]
1606+
public static void Decode_CanDecodeWithAttributeCertificate()
1607+
{
1608+
SignedCms cms = new SignedCms();
1609+
cms.Decode(SignedDocuments.TstWithAttributeCertificate);
1610+
Assert.Equal(2, cms.Certificates.Count);
1611+
cms.CheckSignature(verifySignatureOnly: true);
1612+
}
1613+
16051614
private static void CheckNoSignature(byte[] encoded, bool badOid=false)
16061615
{
16071616
SignedCms cms = new SignedCms();

0 commit comments

Comments
 (0)