Skip to content

Commit c7863ce

Browse files
SignedCms add test and reduce allocations
1 parent 5f3e1ff commit c7863ce

File tree

2 files changed

+137
-27
lines changed

2 files changed

+137
-27
lines changed

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

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,10 @@ private bool CheckHash(bool compatMode)
493493
Debug.Assert(_signatureAlgorithm == Oids.NoSignature);
494494

495495
// The signature is a hash of the message or signed attributes.
496-
return VerifyHashedMessage(compatMode, contentToVerify => _signature.Span.SequenceEqual(contentToVerify));
496+
return VerifyHashedMessage(
497+
compatMode,
498+
_signature,
499+
static (signature, contentToVerify) => signature.Span.SequenceEqual(contentToVerify));
497500
}
498501

499502
private X509Certificate2? FindSignerCertificate()
@@ -604,12 +607,13 @@ private CmsHash GetContentHash(ReadOnlyMemory<byte> content, ReadOnlyMemory<byte
604607
return hasher;
605608
}
606609

607-
private static bool VerifyAttributes(
610+
private static bool VerifyAttributes<TState>(
608611
ReadOnlySpan<byte> digest,
609612
AttributeAsn[] signedAttributes,
610613
bool compatMode,
611614
bool needsContentAttr,
612-
VerifyCallback verify)
615+
TState state,
616+
VerifyCallback<TState> verify)
613617
{
614618
bool hasMatchingDigestAttr = false;
615619
bool hasContentAttr = false;
@@ -663,23 +667,40 @@ private static bool VerifyAttributes(
663667

664668
if (compatMode)
665669
{
666-
byte[] encoded = writer.Encode();
667-
encoded[0] = 0x31;
668-
return verify(encoded);
670+
int encodedLength = writer.GetEncodedLength();
671+
byte[]? rented = null;
672+
Span<byte> encoded = encodedLength <= 256
673+
? stackalloc byte[256]
674+
: (rented = CryptoPool.Rent(encodedLength));
675+
676+
try
677+
{
678+
encoded = encoded.Slice(0, encodedLength);
679+
writer.Encode(encoded);
680+
encoded[0] = 0x31;
681+
return verify(state, encoded);
682+
}
683+
finally
684+
{
685+
if (rented != null)
686+
{
687+
CryptoPool.Return(rented);
688+
}
689+
}
669690
}
670691
else
671692
{
672693
#if NET9_0_OR_GREATER
673-
return writer.Encode(verify, static (verify, encoded) => verify(encoded));
694+
return writer.Encode((state, verify), static (state, encoded) => state.verify(state.state, encoded));
674695
#else
675-
return verify(writer.Encode());
696+
return verify(state, writer.Encode());
676697
#endif
677698
}
678699
}
679700

680-
private delegate bool VerifyCallback(ReadOnlySpan<byte> contentToVerify);
701+
private delegate bool VerifyCallback<TState>(TState state, ReadOnlySpan<byte> contentToVerify);
681702

682-
private bool VerifyHashedMessage(bool compatMode, VerifyCallback verify)
703+
private bool VerifyHashedMessage<TState>(bool compatMode, TState state, VerifyCallback<TState> verify)
683704
{
684705
ReadOnlyMemory<byte> content = GetContentForVerification(out ReadOnlyMemory<byte>? additionalContent);
685706

@@ -710,7 +731,7 @@ private bool VerifyHashedMessage(bool compatMode, VerifyCallback verify)
710731
throw new CryptographicException(SR.Cryptography_Cms_MissingAuthenticatedAttribute);
711732
}
712733

713-
return verify(contentHash);
734+
return verify(state, contentHash);
714735
}
715736

716737
// Since there are signed attributes, we need to verify those instead.
@@ -720,8 +741,10 @@ private bool VerifyHashedMessage(bool compatMode, VerifyCallback verify)
720741
_signedAttributes,
721742
compatMode,
722743
needsContentAttr: false,
723-
span =>
744+
(state, hasher, verify),
745+
static (state, span) =>
724746
{
747+
CmsHash hasher = state.hasher;
725748
hasher.AppendData(span);
726749

727750
#if NET || NETSTANDARD2_1
@@ -740,12 +763,12 @@ private bool VerifyHashedMessage(bool compatMode, VerifyCallback verify)
740763
byte[] attrHash = hasher.GetHashAndReset();
741764
#endif
742765

743-
return verify(attrHash);
766+
return state.verify(state.state, attrHash);
744767
});
745768
}
746769
}
747770

748-
private bool VerifyPureMessage(bool compatMode, VerifyCallback verify)
771+
private bool VerifyPureMessage<TState>(bool compatMode, TState state, VerifyCallback<TState> verify)
749772
{
750773
ReadOnlyMemory<byte> content = GetContentForVerification(out ReadOnlyMemory<byte>? additionalContent);
751774

@@ -760,7 +783,7 @@ private bool VerifyPureMessage(bool compatMode, VerifyCallback verify)
760783

761784
if (!additionalContent.HasValue)
762785
{
763-
return verify(content.Span);
786+
return verify(state, content.Span);
764787
}
765788

766789
// If there are multiple pieces of content, concatenate them and verify.
@@ -770,7 +793,7 @@ private bool VerifyPureMessage(bool compatMode, VerifyCallback verify)
770793
{
771794
additionalContent.Value.Span.CopyTo(rented);
772795
content.Span.CopyTo(rented.AsSpan(additionalContent.Value.Length));
773-
return verify(rented.AsSpan(0, contentToVerifyLength));
796+
return verify(state, rented.AsSpan(0, contentToVerifyLength));
774797
}
775798
finally
776799
{
@@ -806,7 +829,8 @@ private bool VerifyPureMessage(bool compatMode, VerifyCallback verify)
806829
// it is invalid for countersigners. We'll just ignore it for now, but if we decide to
807830
// allow countersigners to omit the content type, we should check that `this` is a countersigner.
808831
needsContentAttr: false,
809-
verify);
832+
(state, verify),
833+
static (state, span) => state.verify(state.state, span));
810834
}
811835
}
812836

@@ -896,19 +920,42 @@ private bool VerifySignature(
896920
return false;
897921
}
898922

899-
Func<bool, VerifyCallback, bool> verifier = signatureProcessor.NeedsHashedMessage ? VerifyHashedMessage : VerifyPureMessage;
900-
return verifier(compatMode, contentToVerify =>
901-
signatureProcessor.VerifySignature(
923+
if (signatureProcessor.NeedsHashedMessage)
924+
{
925+
return VerifyHashedMessage(
926+
compatMode,
927+
(@this: this, signatureProcessor, certificate),
928+
static (state, contentToVerify) =>
929+
state.signatureProcessor.VerifySignature(
930+
#if NET || NETSTANDARD2_1
931+
contentToVerify,
932+
state.@this._signature,
933+
#else
934+
contentToVerify.ToArray(),
935+
state.@this._signature.ToArray(),
936+
#endif
937+
state.@this.DigestAlgorithm.Value,
938+
state.@this._signatureAlgorithmParameters,
939+
state.certificate));
940+
}
941+
else
942+
{
943+
return VerifyPureMessage(
944+
compatMode,
945+
(@this: this, signatureProcessor, certificate),
946+
static (state, contentToVerify) =>
947+
state.signatureProcessor.VerifySignature(
902948
#if NET || NETSTANDARD2_1
903-
contentToVerify,
904-
_signature,
949+
contentToVerify,
950+
state.@this._signature,
905951
#else
906-
contentToVerify.ToArray(),
907-
_signature.ToArray(),
952+
contentToVerify.ToArray(),
953+
state.@this._signature.ToArray(),
908954
#endif
909-
DigestAlgorithm.Value,
910-
_signatureAlgorithmParameters,
911-
certificate));
955+
state.@this.DigestAlgorithm.Value,
956+
state.@this._signatureAlgorithmParameters,
957+
state.certificate));
958+
}
912959
}
913960

914961
private static int FindAttributeIndexByOid(AttributeAsn[] attributes, Oid oid, int startIndex = 0)

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,69 @@ public static void ComputeSignature_SlhDsa_NoSignature()
17021702
}
17031703
}
17041704

1705+
// Ed25519 certificate from https://datatracker.ietf.org/doc/html/rfc8410#section-10.2
1706+
private const string UnknownAlgorithmCert =
1707+
"""
1708+
MIIBLDCB36ADAgECAghWAUdKKo3DMDAFBgMrZXAwGTEXMBUGA1UEAwwOSUVURiBUZX
1709+
N0IERlbW8wHhcNMTYwODAxMTIxOTI0WhcNNDAxMjMxMjM1OTU5WjAZMRcwFQYDVQQD
1710+
DA5JRVRGIFRlc3QgRGVtbzAqMAUGAytlbgMhAIUg8AmJMKdUdIt93LQ+91oNvzoNJj
1711+
ga9OukqY6qm05qo0UwQzAPBgNVHRMBAf8EBTADAQEAMA4GA1UdDwEBAAQEAwIDCDAg
1712+
BgNVHQ4BAQAEFgQUmx9e7e0EM4Xk97xiPFl1uQvIuzswBQYDK2VwA0EAryMB/t3J5v
1713+
/BzKc9dNZIpDmAgs3babFOTQbs+BolzlDUwsPrdGxO3YNGhW7Ibz3OGhhlxXrCe1Cg
1714+
w1AH9efZBw==
1715+
""";
1716+
1717+
[Fact]
1718+
public static void ComputeSignature_UnknownAlgorithm_NoSignature()
1719+
{
1720+
using X509Certificate2 cert = X509CertificateLoader.LoadCertificate(Convert.FromBase64String(UnknownAlgorithmCert));
1721+
1722+
byte[] message = "Hello World!"u8.ToArray();
1723+
SignedCms cms = new SignedCms(new ContentInfo(message));
1724+
CmsSigner cmsSigner = new CmsSigner(SubjectIdentifierType.NoSignature, cert)
1725+
{
1726+
DigestAlgorithm = new Oid(Oids.Sha256, Oids.Sha256)
1727+
};
1728+
1729+
cms.ComputeSignature(cmsSigner);
1730+
1731+
// "NoSignature" OID
1732+
Assert.Equal("1.3.6.1.5.5.7.6.2", cms.SignerInfos[0].SignatureAlgorithm.Value);
1733+
1734+
byte[] messageHash = Convert.FromBase64String("f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=");
1735+
byte[] signature = cms.SignerInfos[0].GetSignature();
1736+
1737+
Assert.Equal(messageHash, signature);
1738+
1739+
SignedCms cmsNoCert = new SignedCms(new ContentInfo(message));
1740+
cmsNoCert.Decode(cms.Encode());
1741+
cmsNoCert.SignerInfos[0].CheckHash();
1742+
}
1743+
1744+
[Fact]
1745+
public static void ComputeSignature_NoSignature_DefaultDigest()
1746+
{
1747+
// A certificate shouldn't really be required here, but on .NET Framework
1748+
// it will encounter throw a NullReferenceException.
1749+
using X509Certificate2 cert = Certificates.RSAKeyTransferCapi1.GetCertificate();
1750+
1751+
byte[] message = "Hello World!"u8.ToArray();
1752+
SignedCms cms = new SignedCms(new ContentInfo(message));
1753+
1754+
// Use default value for DigestAlgorithm
1755+
CmsSigner signer = new CmsSigner(SubjectIdentifierType.NoSignature, cert);
1756+
1757+
cms.ComputeSignature(signer);
1758+
1759+
byte[] defaultDigestSignature = cms.SignerInfos[0].GetSignature();
1760+
byte[] messageHash = Convert.FromBase64String(
1761+
PlatformDetection.IsNetFramework
1762+
? "Lve95gjOVATpfV8EL5X4nxwjKHE=" // Sha1
1763+
: "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk="); // Sha256
1764+
1765+
Assert.Equal(messageHash, defaultDigestSignature);
1766+
}
1767+
17051768
private static void CheckNoSignature(byte[] encoded, bool badOid=false)
17061769
{
17071770
SignedCms cms = new SignedCms();

0 commit comments

Comments
 (0)