Skip to content

Commit baf7e5d

Browse files
[release/7.0] Fix SignedCms certificate collection modification with attribute certificates (#80188)
* Fix SignedCms certificate collection modification with attribute certificates. When adding or removing certificates from the certificateSet collection, we assumed that the collection would only contain X.509 certificates. This changes the implementation so that when looking for duplicates, we skip over choices that are not an X.509 certificate when looking for a duplicate. The tests peek in to the SignedData ASN.1 to ensure that the attribute certificates are preserved during a round trip when encoding and decoding a CMS. * Tests are not applicable for .NET Framework * Clean up tests * Update package for servicing * Add tests for Compute{Counter}Signature with attribute certificates Co-authored-by: Kevin Jones <[email protected]>
1 parent dd6c533 commit baf7e5d

File tree

3 files changed

+122
-2
lines changed

3 files changed

+122
-2
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<IncludeDllSafeSearchPathAttribute>true</IncludeDllSafeSearchPathAttribute>
66
<NoWarn>$(NoWarn);CA5384</NoWarn>
77
<IsPackable>true</IsPackable>
8+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
9+
<ServicingVersion>1</ServicingVersion>
810
<PackageDescription>Provides support for PKCS and CMS algorithms.
911

1012
Commonly Used Types:

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ public void AddCertificate(X509Certificate2 certificate)
686686
{
687687
foreach (CertificateChoiceAsn cert in _signedData.CertificateSet!)
688688
{
689-
if (cert.Certificate!.Value.Span.SequenceEqual(rawData))
689+
if (cert.Certificate is not null && cert.Certificate.Value.Span.SequenceEqual(rawData))
690690
{
691691
throw new CryptographicException(SR.Cryptography_Cms_CertificateAlreadyInCollection);
692692
}
@@ -721,7 +721,7 @@ public void RemoveCertificate(X509Certificate2 certificate)
721721

722722
foreach (CertificateChoiceAsn cert in _signedData.CertificateSet!)
723723
{
724-
if (cert.Certificate!.Value.Span.SequenceEqual(rawData))
724+
if (cert.Certificate is not null && cert.Certificate.Value.Span.SequenceEqual(rawData))
725725
{
726726
PkcsHelpers.RemoveAt(ref _signedData.CertificateSet, idx);
727727
Reencode();

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

Lines changed: 118 additions & 0 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

44
using System.Collections.Generic;
5+
using System.Formats.Asn1;
56
using System.Linq;
67
using System.Security.Cryptography.X509Certificates;
78
using Test.Cryptography;
@@ -604,6 +605,92 @@ public static void CreateSignature_Ecdsa_ThrowsWithRsaSignaturePadding()
604605
}
605606
}
606607

608+
[Fact]
609+
public static void AddCertificate_CollectionContainsAttributeCertificate()
610+
{
611+
SignedCms signedCms = new SignedCms();
612+
signedCms.Decode(SignedDocuments.TstWithAttributeCertificate);
613+
signedCms.CheckSignature(true);
614+
615+
int countBefore = CountCertificateChoices(SignedDocuments.TstWithAttributeCertificate);
616+
617+
using (X509Certificate2 cert = Certificates.RSA2048SignatureOnly.GetCertificate())
618+
{
619+
signedCms.AddCertificate(cert);
620+
byte[] reEncoded = signedCms.Encode();
621+
int countAfter = CountCertificateChoices(reEncoded);
622+
Assert.Equal(countBefore + 1, countAfter);
623+
624+
signedCms = new SignedCms();
625+
signedCms.Decode(reEncoded);
626+
signedCms.CheckSignature(true);
627+
}
628+
}
629+
630+
[Fact]
631+
public static void RemoveCertificate_Existing_CollectionContainsAttributeCertificate()
632+
{
633+
SignedCms signedCms = new SignedCms();
634+
signedCms.Decode(SignedDocuments.TstWithAttributeCertificate);
635+
int countBefore = CountCertificateChoices(SignedDocuments.TstWithAttributeCertificate);
636+
637+
signedCms.RemoveCertificate(signedCms.Certificates[0]);
638+
byte[] reEncoded = signedCms.Encode();
639+
int countAfter = CountCertificateChoices(reEncoded);
640+
Assert.Equal(countBefore - 1, countAfter);
641+
}
642+
643+
[Fact]
644+
public static void RemoveCertificate_NonExisting_CollectionContainsAttributeCertificate()
645+
{
646+
SignedCms signedCms = new SignedCms();
647+
signedCms.Decode(SignedDocuments.TstWithAttributeCertificate);
648+
649+
using (X509Certificate2 cert = Certificates.RSA2048SignatureOnly.GetCertificate())
650+
{
651+
// Remove a non-existing certificate so that we are forced to enumerate the entire 'certificates[0]'
652+
// collection (including attribute certificates) looking for it.
653+
Assert.Throws<CryptographicException>(() => signedCms.RemoveCertificate(cert));
654+
}
655+
}
656+
657+
[Fact]
658+
public static void ComputeCounterSignature_PreservesAttributeCertificate()
659+
{
660+
SignedCms signedCms = new SignedCms();
661+
signedCms.Decode(SignedDocuments.TstWithAttributeCertificate);
662+
int countBefore = CountCertificateChoices(SignedDocuments.TstWithAttributeCertificate);
663+
664+
using (X509Certificate2 cert = Certificates.RSA2048SignatureOnly.TryGetCertificateWithPrivateKey())
665+
{
666+
CmsSigner signer = new CmsSigner(cert);
667+
SignerInfo info = signedCms.SignerInfos[0];
668+
info.ComputeCounterSignature(signer);
669+
}
670+
671+
byte[] encoded = signedCms.Encode();
672+
int countAfter = CountCertificateChoices(encoded);
673+
Assert.Equal(countBefore + 1, countAfter);
674+
}
675+
676+
[Fact]
677+
public static void ComputeSignature_PreservesAttributeCertificate()
678+
{
679+
SignedCms signedCms = new SignedCms();
680+
signedCms.Decode(SignedDocuments.TstWithAttributeCertificate);
681+
int countBefore = CountCertificateChoices(SignedDocuments.TstWithAttributeCertificate);
682+
683+
using (X509Certificate2 cert = Certificates.RSA2048SignatureOnly.TryGetCertificateWithPrivateKey())
684+
{
685+
CmsSigner signer = new CmsSigner(cert);
686+
signedCms.ComputeSignature(signer);
687+
}
688+
689+
byte[] encoded = signedCms.Encode();
690+
int countAfter = CountCertificateChoices(encoded);
691+
Assert.Equal(countBefore + 1, countAfter);
692+
}
693+
607694
private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, AsymmetricAlgorithm key)
608695
{
609696
using (var pubCert = new X509Certificate2(cert.RawData))
@@ -664,5 +751,36 @@ private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate
664751
Assert.Equal(counterSignerPubCert, cms.SignerInfos[0].CounterSignerInfos[0].Certificate);
665752
}
666753
}
754+
755+
private static int CountCertificateChoices(byte[] encoded)
756+
{
757+
AsnReader reader = new AsnReader(encoded, AsnEncodingRules.BER);
758+
reader = reader.ReadSequence();
759+
reader.ReadObjectIdentifier();
760+
reader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
761+
reader = reader.ReadSequence();
762+
763+
reader.ReadInteger(); // version
764+
reader.ReadSetOf(); // digestAlgorithms
765+
reader.ReadSequence(); // encapsulatedContentInfo
766+
767+
Asn1Tag expectedTag = new Asn1Tag(TagClass.ContextSpecific, 0, true); // certificates[0]
768+
769+
if (reader.PeekTag() == expectedTag)
770+
{
771+
AsnReader certs = reader.ReadSetOf(expectedTag);
772+
int count = 0;
773+
774+
while (certs.HasData)
775+
{
776+
certs.ReadEncodedValue();
777+
count++;
778+
}
779+
780+
return count;
781+
}
782+
783+
return 0;
784+
}
667785
}
668786
}

0 commit comments

Comments
 (0)