diff --git a/src/libraries/Common/src/Internal/Cryptography/PkcsHelpers.cs b/src/libraries/Common/src/Internal/Cryptography/PkcsHelpers.cs index 7c18e78e2fcc17..10bc0179aff2cf 100644 --- a/src/libraries/Common/src/Internal/Cryptography/PkcsHelpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/PkcsHelpers.cs @@ -353,9 +353,12 @@ public static Pkcs9AttributeObject CreateBestPkcs9AttributeObjectAvailable(Oid o }; } + public static AttributeAsn[] NormalizeAttributeSet(AttributeAsn[] setItems) => + NormalizeAttributeSet(setItems, out _); + public static AttributeAsn[] NormalizeAttributeSet( AttributeAsn[] setItems, - Action? encodedValueProcessor = null) + out byte[] encodedValue) { byte[] normalizedValue; @@ -370,7 +373,7 @@ public static AttributeAsn[] NormalizeAttributeSet( writer.PopSetOf(); normalizedValue = writer.Encode(); - encodedValueProcessor?.Invoke(normalizedValue); + encodedValue = normalizedValue; try { diff --git a/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs b/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs index cbd9e0be921342..55e50666acdee5 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs @@ -142,6 +142,18 @@ Oids.EcPublicKey or }; } + internal static IncrementalHash CreateIncrementalHash(HashAlgorithmName hashAlgorithmName) + { + try + { + return IncrementalHash.CreateHash(hashAlgorithmName); + } + catch (PlatformNotSupportedException ex) + { + throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName), ex); + } + } + internal static CryptographicException CreateAlgorithmUnknownException(AsnWriter encodedId) { #if NET10_0_OR_GREATER diff --git a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs index ee8aac3ae75ae9..93e4524b91a9a8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs @@ -66,6 +66,7 @@ internal static partial class Oids internal const string Pkcs7Hashed = "1.2.840.113549.1.7.5"; internal const string Pkcs7Encrypted = "1.2.840.113549.1.7.6"; + // Hash algorithms internal const string Md5 = "1.2.840.113549.2.5"; internal const string Sha1 = "1.3.14.3.2.26"; internal const string Sha256 = "2.16.840.1.101.3.4.2.1"; @@ -74,6 +75,8 @@ internal static partial class Oids internal const string Sha3_256 = "2.16.840.1.101.3.4.2.8"; internal const string Sha3_384 = "2.16.840.1.101.3.4.2.9"; internal const string Sha3_512 = "2.16.840.1.101.3.4.2.10"; + internal const string Shake128 = "2.16.840.1.101.3.4.2.11"; + internal const string Shake256 = "2.16.840.1.101.3.4.2.12"; // DSA CMS uses the combined signature+digest OID internal const string DsaWithSha1 = "1.2.840.10040.4.3"; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/SlhDsa/SlhDsaTestData.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/SlhDsa/SlhDsaTestData.cs index f8531c13ea943d..43ac953e2c2324 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/SlhDsa/SlhDsaTestData.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/SlhDsa/SlhDsaTestData.cs @@ -553,19 +553,46 @@ public static partial class SlhDsaTestData // // Get base64 encrypted private key info: // > openssl pkcs8 -topk8 -outform DER -v2 "aes-192-cbc" -v2prf hmacWithSHA384 -iter 10 -in private.pem | base64 -w 64 - public record SlhDsaGeneratedKeyInfo( - int Id, - SlhDsaAlgorithm Algorithm, - string SecretKeyHex, - string Pkcs8PrivateKeyBase64, - string Pkcs8PublicKeyBase64, - string Pkcs8EncryptedPrivateKeyBase64, - string CertificateBase64, - string SelfSignedCertificatePfxBase64, - string ThumbprintHex, - string EncryptionPassword, - PbeParameters EncryptionParameters) + public class SlhDsaGeneratedKeyInfo { + public SlhDsaGeneratedKeyInfo( + int Id, + SlhDsaAlgorithm Algorithm, + string SecretKeyHex, + string Pkcs8PrivateKeyBase64, + string Pkcs8PublicKeyBase64, + string Pkcs8EncryptedPrivateKeyBase64, + string CertificateBase64, + string SelfSignedCertificatePfxBase64, + string ThumbprintHex, + string EncryptionPassword, + PbeParameters EncryptionParameters) + { + this.Id = Id; + this.Algorithm = Algorithm; + this.SecretKeyHex = SecretKeyHex; + this.Pkcs8PrivateKeyBase64 = Pkcs8PrivateKeyBase64; + this.Pkcs8PublicKeyBase64 = Pkcs8PublicKeyBase64; + this.Pkcs8EncryptedPrivateKeyBase64 = Pkcs8EncryptedPrivateKeyBase64; + this.CertificateBase64 = CertificateBase64; + this.SelfSignedCertificatePfxBase64 = SelfSignedCertificatePfxBase64; + this.ThumbprintHex = ThumbprintHex; + this.EncryptionPassword = EncryptionPassword; + this.EncryptionParameters = EncryptionParameters; + } + + public int Id { get; } + public SlhDsaAlgorithm Algorithm { get; } + public string SecretKeyHex { get; } + public string Pkcs8PrivateKeyBase64 { get; } + public string Pkcs8PublicKeyBase64 { get; } + public string Pkcs8EncryptedPrivateKeyBase64 { get; } + public string CertificateBase64 { get; } + public string SelfSignedCertificatePfxBase64 { get; } + public string ThumbprintHex { get; } + public string EncryptionPassword { get; } + public PbeParameters EncryptionParameters { get; } + public byte[] SecretKey => SecretKeyHex.HexToByteArray(); public byte[] PublicKey => SecretKey.AsSpan(Algorithm.SecretKeySizeInBytes/2).ToArray(); public byte[] Pkcs8PrivateKey => Convert.FromBase64String(Pkcs8PrivateKeyBase64); @@ -574,9 +601,9 @@ public record SlhDsaGeneratedKeyInfo( public byte[] EncryptionPasswordBytes => Encoding.UTF8.GetBytes(EncryptionPassword); // Assuming UTF-8 encoding public byte[] Certificate => Convert.FromBase64String(CertificateBase64); public byte[] SelfSignedCertificatePfx => Convert.FromBase64String(SelfSignedCertificatePfxBase64); - public string EncryptedPem => PemEncoding.WriteString("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey); - public string PrivateKeyPem => PemEncoding.WriteString("PRIVATE KEY", Pkcs8PrivateKey); - public string PublicKeyPem => PemEncoding.WriteString("PUBLIC KEY", Pkcs8PublicKey); + public string EncryptedPem => ByteUtils.PemEncode("ENCRYPTED PRIVATE KEY", Pkcs8EncryptedPrivateKey); + public string PrivateKeyPem => ByteUtils.PemEncode("PRIVATE KEY", Pkcs8PrivateKey); + public string PublicKeyPem => ByteUtils.PemEncode("PUBLIC KEY", Pkcs8PublicKey); public byte[] Thumbprint => ThumbprintHex.HexToByteArray(); public override string ToString() => @@ -588,16 +615,35 @@ from info in GeneratedKeyInfosRaw select new object[] { info }; public static partial SlhDsaGeneratedKeyInfo[] GeneratedKeyInfosRaw { get; } - - public record SlhDsaKeyGenTestVector( - int TestCaseId, - SlhDsaAlgorithm Algorithm, - string SecretKeySeedHex, - string SecretKeyPrfHex, - string PublicKeySeedHex, - string SecretKeyHex, - string PublicKeyHex) + + public class SlhDsaKeyGenTestVector { + public SlhDsaKeyGenTestVector( + int TestCaseId, + SlhDsaAlgorithm Algorithm, + string SecretKeySeedHex, + string SecretKeyPrfHex, + string PublicKeySeedHex, + string SecretKeyHex, + string PublicKeyHex) + { + this.TestCaseId = TestCaseId; + this.Algorithm = Algorithm; + this.SecretKeySeedHex = SecretKeySeedHex; + this.SecretKeyPrfHex = SecretKeyPrfHex; + this.PublicKeySeedHex = PublicKeySeedHex; + this.SecretKeyHex = SecretKeyHex; + this.PublicKeyHex = PublicKeyHex; + } + + public int TestCaseId { get; } + public SlhDsaAlgorithm Algorithm { get; } + public string SecretKeySeedHex { get; } + public string SecretKeyPrfHex { get; } + public string PublicKeySeedHex { get; } + public string SecretKeyHex { get; } + public string PublicKeyHex { get; } + public byte[] SecretKeySeed => SecretKeySeedHex.HexToByteArray(); public byte[] SecretKeyPrf => SecretKeyPrfHex.HexToByteArray(); public byte[] PublicKeySeed => PublicKeySeedHex.HexToByteArray(); @@ -722,16 +768,37 @@ public record SlhDsaKeyGenTestVector( ), ]; - public record SlhDsaSigVerTestVector( - int TestCaseId, - bool TestPassed, - SlhDsaAlgorithm Algorithm, - string SecretKeyHex, - string PublicKeyHex, - string MessageHex, - string ContextHex, - string SignatureHex) + public class SlhDsaSigVerTestVector { + public SlhDsaSigVerTestVector( + int TestCaseId, + bool TestPassed, + SlhDsaAlgorithm Algorithm, + string SecretKeyHex, + string PublicKeyHex, + string MessageHex, + string ContextHex, + string SignatureHex) + { + this.TestCaseId = TestCaseId; + this.TestPassed = TestPassed; + this.Algorithm = Algorithm; + this.SecretKeyHex = SecretKeyHex; + this.PublicKeyHex = PublicKeyHex; + this.MessageHex = MessageHex; + this.ContextHex = ContextHex; + this.SignatureHex = SignatureHex; + } + + public int TestCaseId { get; } + public bool TestPassed { get; } + public SlhDsaAlgorithm Algorithm { get; } + public string SecretKeyHex { get; } + public string PublicKeyHex { get; } + public string MessageHex { get; } + public string ContextHex { get; } + public string SignatureHex { get; } + public byte[] SecretKey => SecretKeyHex.HexToByteArray(); public byte[] PublicKey => PublicKeyHex.HexToByteArray(); public byte[] Message => MessageHex.HexToByteArray(); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs new file mode 100644 index 00000000000000..1f49d6af945884 --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs @@ -0,0 +1,228 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Security.Cryptography.SLHDsa.Tests; +using Xunit; +using Xunit.Sdk; + +namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation +{ + public static partial class PrivateKeyAssociationTests + { + private static partial Func CopyWithPrivateKey_SlhDsa { get; } + private static partial Func GetSlhDsaPublicKey { get; } + private static partial Func GetSlhDsaPrivateKey { get; } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void GetSlhDsaPublicKeyTest() + { + // Cert without private key + using (X509Certificate2 cert = X509CertificateLoader.LoadCertificate(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate)) + using (SlhDsa? certKey = GetSlhDsaPublicKey(cert)) + { + Assert.NotNull(certKey); + AssertExtensions.SequenceEqual(SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue, certKey.ExportSlhDsaPublicKey()); + + // Verify the key is not actually private + Assert.ThrowsAny(() => certKey.SignData([1, 2, 3])); + } + + // Cert with private key + using (X509Certificate2 cert = LoadShlDsaIetfCertificateWithPrivateKey()) + using (SlhDsa? certKey = GetSlhDsaPublicKey(cert)) + { + Assert.NotNull(certKey); + AssertExtensions.SequenceEqual(SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue, certKey.ExportSlhDsaPublicKey()); + + // Verify the key is not actually private + Assert.ThrowsAny(() => certKey.SignData([1, 2, 3])); + } + } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void GetSlhDsaPrivateKeyTest() + { + // Cert without private key + using (X509Certificate2 cert = X509CertificateLoader.LoadCertificate(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate)) + { + using (SlhDsa? certKey = GetSlhDsaPrivateKey(cert)) + { + Assert.Null(certKey); + } + } + + // Cert with private key + using (X509Certificate2 certWithPrivateKey = LoadShlDsaIetfCertificateWithPrivateKey()) + { + using (SlhDsa? certKey = GetSlhDsaPrivateKey(certWithPrivateKey)) + { + Assert.NotNull(certKey); + + // Verify the key is actually private + AssertExtensions.SequenceEqual( + SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue, + certKey.ExportSlhDsaSecretKey()); + } + } + } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void CheckCopyWithPrivateKey_SlhDsa() + { + Random rng = new Random(); + + using (X509Certificate2 pubOnly = X509CertificateLoader.LoadCertificate(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate)) + using (SlhDsa privKey = SlhDsa.ImportPkcs8PrivateKey(SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyPkcs8)) + using (X509Certificate2 wrongAlg = X509CertificateLoader.LoadCertificate(TestData.CertWithEnhancedKeyUsage)) + { + CheckCopyWithPrivateKey( + pubOnly, + wrongAlg, + privKey, + [ + () => SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_128s), + () => SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_192f), + () => SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaShake256f), + ], + CopyWithPrivateKey_SlhDsa, + GetSlhDsaPublicKey, + GetSlhDsaPrivateKey, + (priv, pub) => + { + byte[] data = new byte[rng.Next(97)]; + rng.NextBytes(data); + + byte[] signature = priv.SignData(data); + Assert.True(pub.VerifyData(data, signature)); + }); + } + } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void CheckCopyWithPrivateKey_SlhDsa_OtherSlhDsa() + { + using (X509Certificate2 pubOnly = X509CertificateLoader.LoadCertificate(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate)) + { + using (SlhDsaMockImplementation publicSlhDsa = SlhDsaMockImplementation.Create(SlhDsaAlgorithm.SlhDsaSha2_128s)) + { + Exception e = new Exception("no secret key"); + publicSlhDsa.ExportSlhDsaSecretKeyCoreHook = _ => throw e; + publicSlhDsa.ExportSlhDsaPublicKeyCoreHook = (Span destination) => + SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue.CopyTo(destination); + + Assert.Same(e, AssertExtensions.Throws(() => CopyWithPrivateKey_SlhDsa(pubOnly, publicSlhDsa))); + } + + SlhDsaMockImplementation privateSlhDsa = SlhDsaMockImplementation.Create(SlhDsaAlgorithm.SlhDsaSha2_128s); + privateSlhDsa.ExportSlhDsaPublicKeyCoreHook = (Span destination) => + SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue.CopyTo(destination); + privateSlhDsa.ExportSlhDsaSecretKeyCoreHook = (Span destination) => + SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue.CopyTo(destination); + + using (X509Certificate2 privCert = CopyWithPrivateKey_SlhDsa(pubOnly, privateSlhDsa)) + { + AssertExtensions.TrueExpression(privCert.HasPrivateKey); + + using (SlhDsa certPrivateSlhDsa = GetSlhDsaPrivateKey(privCert)) + { + AssertExtensions.SequenceEqual( + SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue, + certPrivateSlhDsa.ExportSlhDsaSecretKey()); + + privateSlhDsa.Dispose(); + privateSlhDsa.ExportSlhDsaPublicKeyCoreHook = _ => Assert.Fail(); + privateSlhDsa.ExportSlhDsaSecretKeyCoreHook = _ => Assert.Fail(); + + // Ensure the key is actual a clone + AssertExtensions.SequenceEqual( + SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue, + certPrivateSlhDsa.ExportSlhDsaSecretKey()); + } + } + } + } + + private static partial void CheckCopyWithPrivateKey( + X509Certificate2 cert, + X509Certificate2 wrongAlgorithmCert, + TKey correctPrivateKey, + IEnumerable> incorrectKeys, + Func copyWithPrivateKey, + Func getPublicKey, + Func getPrivateKey, + Action keyProver) + where TKey : class, IDisposable + { + Exception e = AssertExtensions.Throws( + null, + () => copyWithPrivateKey(wrongAlgorithmCert, correctPrivateKey)); + + Assert.Contains("algorithm", e.Message); + + List generatedKeys = new(); + + foreach (Func func in incorrectKeys) + { + TKey incorrectKey = func(); + generatedKeys.Add(incorrectKey); + + e = AssertExtensions.Throws( + "privateKey", + () => copyWithPrivateKey(cert, incorrectKey)); + + Assert.Contains("key does not match the public key for this certificate", e.Message); + } + + using (X509Certificate2 withKey = copyWithPrivateKey(cert, correctPrivateKey)) + { + e = AssertExtensions.Throws( + () => copyWithPrivateKey(withKey, correctPrivateKey)); + + Assert.Contains("already has an associated private key", e.Message); + + foreach (TKey incorrectKey in generatedKeys) + { + e = AssertExtensions.Throws( + () => copyWithPrivateKey(withKey, incorrectKey)); + + Assert.Contains("already has an associated private key", e.Message); + } + + using (TKey pub = getPublicKey(withKey)) + using (TKey pub2 = getPublicKey(withKey)) + using (TKey pubOnly = getPublicKey(cert)) + using (TKey priv = getPrivateKey(withKey)) + using (TKey priv2 = getPrivateKey(withKey)) + { + Assert.NotSame(pub, pub2); + Assert.NotSame(pub, pubOnly); + Assert.NotSame(pub2, pubOnly); + Assert.NotSame(priv, priv2); + + keyProver(priv, pub2); + keyProver(priv2, pub); + keyProver(priv, pubOnly); + + priv.Dispose(); + pub2.Dispose(); + + keyProver(priv2, pub); + keyProver(priv2, pubOnly); + } + } + + foreach (TKey incorrectKey in generatedKeys) + { + incorrectKey.Dispose(); + } + } + + private static X509Certificate2 LoadShlDsaIetfCertificateWithPrivateKey() + { + using (X509Certificate2 cert = X509CertificateLoader.LoadCertificate(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate)) + using (SlhDsa? privateKey = SlhDsa.ImportSlhDsaSecretKey(SlhDsaAlgorithm.SlhDsaSha2_128s, SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue)) + return cert.CopyWithPrivateKey(privateKey); + } + } +} diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj index 94e922dfa5c1fa..c1a35ea084a4e0 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj @@ -23,7 +23,11 @@ + + + @@ -350,8 +354,6 @@ System\Security\Cryptography\Asn1\MLKemPrivateKeyBothAsn.xml.cs System\Security\Cryptography\Asn1\MLKemPrivateKeyBothAsn.xml - + /// Helper methods to access keys on . + /// + public static class X509CertificateKeyAccessors + { + /// + /// Gets the public key from this certificate. + /// + /// + /// The X509 certificate that contains the public key. + /// + /// + /// The public key, or if this certificate does not have an SLH-DSA public key. + /// + /// + /// is . + /// + /// + /// The certificate has an SLH-DSA public key, but the platform does not support SLH-DSA. + /// + /// + /// The public key was invalid, or otherwise could not be imported. + /// + [ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static SlhDsa? GetSlhDsaPublicKey(this X509Certificate2 certificate) => +#if NET10_0_OR_GREATER + certificate.GetSlhDsaPublicKey(); +#else + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa))); +#endif + + /// + /// Gets the private key from this certificate. + /// + /// + /// The X509 certificate that contains the private key. + /// + /// + /// The private key, or if this certificate does not have an SLH-DSA private key. + /// + /// + /// An error occurred accessing the private key. + /// + [ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static SlhDsa? GetSlhDsaPrivateKey(this X509Certificate2 certificate) => +#if NET10_0_OR_GREATER + certificate.GetSlhDsaPrivateKey(); +#else + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa))); +#endif + + /// + /// Combines a private key with a certificate containing the associated public key into a + /// new instance that can access the private key. + /// + /// + /// The X509 certificate that contains the public key. + /// + /// + /// The SLH-DSA private key that corresponds to the SLH-DSA public key in this certificate. + /// + /// + /// A new certificate with the property set to . + /// The current certificate isn't modified. + /// + /// + /// is . + /// + /// + /// is . + /// + /// + /// The specified private key doesn't match the public key for this certificate. + /// + /// + /// The certificate already has an associated private key. + /// + [ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, SlhDsa privateKey) => +#if NET10_0_OR_GREATER + certificate.CopyWithPrivateKey(privateKey); +#else + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa))); +#endif + } +} diff --git a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj index 0b65d647583296..94c7dc3e0ac90d 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj @@ -1,4 +1,4 @@ - + $(NetFrameworkCurrent);$(NetCoreAppCurrent) @@ -148,6 +148,8 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\SlhDsa\SlhDsaTestHelpers.cs" /> + + diff --git a/src/libraries/Microsoft.Bcl.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs b/src/libraries/Microsoft.Bcl.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs new file mode 100644 index 00000000000000..68662982485824 --- /dev/null +++ b/src/libraries/Microsoft.Bcl.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation +{ + public static partial class PrivateKeyAssociationTests + { + private static partial Func CopyWithPrivateKey_SlhDsa => + X509CertificateKeyAccessors.CopyWithPrivateKey; + + private static partial Func GetSlhDsaPublicKey => + X509CertificateKeyAccessors.GetSlhDsaPublicKey; + + private static partial Func GetSlhDsaPrivateKey => + X509CertificateKeyAccessors.GetSlhDsaPrivateKey; + + private static partial void CheckCopyWithPrivateKey( + X509Certificate2 cert, + X509Certificate2 wrongAlgorithmCert, + TKey correctPrivateKey, + IEnumerable> incorrectKeys, + Func copyWithPrivateKey, + Func getPublicKey, + Func getPrivateKey, + Action keyProver) + where TKey : class, IDisposable; + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.csproj index c5739eed9c97ed..864dfd189a6b4d 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.csproj @@ -9,6 +9,14 @@ + + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs b/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs index 91290677204be9..3f170ef312fcaa 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs @@ -15,7 +15,10 @@ public CmsRecipient(System.Security.Cryptography.Pkcs.SubjectIdentifierType reci public sealed partial class CmsSigner { public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.AsymmetricAlgorithm? privateKey) { } + [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] + public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.SlhDsa? privateKey) { } public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.RSA? privateKey, System.Security.Cryptography.RSASignaturePadding? signaturePadding) { } + public bool HasPrivateKey { get { throw null; } } public System.Security.Cryptography.AsymmetricAlgorithm? PrivateKey { get { throw null; } set { } } public System.Security.Cryptography.RSASignaturePadding? SignaturePadding { get { throw null; } set { } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.cs index 69525a16b001e9..ba2df743228e23 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.cs @@ -74,7 +74,7 @@ public override byte[] GetSubjectKeyIdentifier(X509Certificate2 certificate) return GetPrivateKey(certificate); } - private static T? GetPrivateKey(X509Certificate2 certificate) where T : AsymmetricAlgorithm + private static T? GetPrivateKey(X509Certificate2 certificate) where T : class, IDisposable { if (typeof(T) == typeof(RSA)) return (T?)(object?)certificate.GetRSAPrivateKey(); @@ -84,6 +84,8 @@ public override byte[] GetSubjectKeyIdentifier(X509Certificate2 certificate) if (typeof(T) == typeof(DSA) && Internal.Cryptography.Helpers.IsDSASupported) return (T?)(object?)certificate.GetDSAPrivateKey(); #endif + if (typeof(T) == typeof(SlhDsa) && SlhDsa.IsSupported) + return (T?)(object?)certificate.GetSlhDsaPrivateKey(); Debug.Fail($"Unknown key type requested: {typeof(T).FullName}"); return null; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/PkcsPalWindows.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/PkcsPalWindows.cs index db93dff50c67ec..c0720f65dad7b4 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/PkcsPalWindows.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/PkcsPalWindows.cs @@ -99,7 +99,7 @@ public sealed override byte[] GetSubjectKeyIdentifier(X509Certificate2 certifica return GetPrivateKey(certificate, silent, preferNCrypt: false); } - private static T? GetPrivateKey(X509Certificate2 certificate, bool silent, bool preferNCrypt) where T : AsymmetricAlgorithm + private static T? GetPrivateKey(X509Certificate2 certificate, bool silent, bool preferNCrypt) where T : class, IDisposable { if (!certificate.HasPrivateKey) { @@ -150,6 +150,8 @@ public sealed override byte[] GetSubjectKeyIdentifier(X509Certificate2 certifica return (T)(object)new ECDsaCng(cngKey); if (typeof(T) == typeof(DSA)) return (T)(object)new DSACng(cngKey); + if (typeof(T) == typeof(SlhDsa)) + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa))); Debug.Fail($"Unknown CNG key type request: {typeof(T).FullName}"); return null; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsPal.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsPal.cs index 500047cf86ff13..5daa7ad2e22614 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsPal.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsPal.cs @@ -76,12 +76,12 @@ protected PkcsPal() /// /// Retrieve a private key object for the certificate to use with signing. /// - public abstract T? GetPrivateKeyForSigning(X509Certificate2 certificate, bool silent) where T : AsymmetricAlgorithm; + public abstract T? GetPrivateKeyForSigning(X509Certificate2 certificate, bool silent) where T : class, IDisposable; /// /// Retrieve a private key object for the certificate to use with decryption. /// - public abstract T? GetPrivateKeyForDecryption(X509Certificate2 certificate, bool silent) where T : AsymmetricAlgorithm; + public abstract T? GetPrivateKeyForDecryption(X509Certificate2 certificate, bool silent) where T : class, IDisposable; /// /// Get the one instance of PkcsPal. diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx index 9a3eb297b2e8d6..a9016f2f9d3f3f 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx @@ -283,6 +283,9 @@ The key in the enveloped message is not valid or could not be decoded. + + SignatureIdentifierType.NoSignature is not valid with the provided certificate. + PKCS12 (PFX) without a supplied password has exceeded maximum allowed iterations. See https://go.microsoft.com/fwlink/?linkid=2233907 for more information. diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index a5296feece890c..cc3bd8acb8999d 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -7,6 +7,7 @@ true true $(NoWarn);CA5384 + $(NoWarn);SYSLIB5006 false true Provides support for PKCS and CMS algorithms. @@ -73,6 +74,8 @@ System.Security.Cryptography.Pkcs.EnvelopedCms + System\Security\Cryptography\Pkcs\Asn1\SigningCertificateV2Asn.xml + + @@ -706,7 +711,11 @@ System.Security.Cryptography.Pkcs.EnvelopedCms - + + + + + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsHash.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsHash.cs new file mode 100644 index 00000000000000..2b8a095a6780f3 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsHash.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.Cryptography; + +namespace System.Security.Cryptography.Pkcs +{ + internal abstract class CmsHash : IDisposable + { + internal static CmsHash Create(Oid oid, bool forVerification) => + oid.Value switch + { +#if NET + Oids.Shake128 => new CmsShake128Hash(), + Oids.Shake256 => new CmsShake256Hash(), +#endif + _ => new CmsIncrementalHash(oid, forVerification), + }; + + public abstract void Dispose(); + internal abstract void AppendData(ReadOnlySpan data); + internal abstract byte[] GetHashAndReset(); + +#if NET || NETSTANDARD2_1 + internal abstract bool TryGetHashAndReset(Span destination, out int bytesWritten); +#endif + +#if NET + private sealed class CmsShake256Hash : CmsHash + { + // RFC 8702 specifies SHAKE256 in CMS must use 512 bits of output. + private const int OutputSizeBytes = 512 / 8; + + private readonly Shake256 _shake256; + + internal CmsShake256Hash() + { + _shake256 = new Shake256(); + } + + public override void Dispose() => _shake256.Dispose(); + internal override void AppendData(ReadOnlySpan data) => _shake256.AppendData(data); + internal override byte[] GetHashAndReset() => _shake256.GetHashAndReset(OutputSizeBytes); + + internal override bool TryGetHashAndReset(Span destination, out int bytesWritten) + { + if (destination.Length < OutputSizeBytes) + { + bytesWritten = 0; + return false; + } + + _shake256.GetHashAndReset(destination.Slice(0, OutputSizeBytes)); + bytesWritten = OutputSizeBytes; + return true; + } + } + + private sealed class CmsShake128Hash : CmsHash + { + // RFC 8702 specifies SHAKE128 in CMS must use 256 bits of output. + private const int OutputSizeBytes = 256 / 8; + + private readonly Shake128 _shake128; + + internal CmsShake128Hash() + { + _shake128 = new Shake128(); + } + + public override void Dispose() => _shake128.Dispose(); + internal override void AppendData(ReadOnlySpan data) => _shake128.AppendData(data); + internal override byte[] GetHashAndReset() => _shake128.GetHashAndReset(OutputSizeBytes); + + internal override bool TryGetHashAndReset(Span destination, out int bytesWritten) + { + if (destination.Length < OutputSizeBytes) + { + bytesWritten = 0; + return false; + } + + _shake128.GetHashAndReset(destination.Slice(0, OutputSizeBytes)); + bytesWritten = OutputSizeBytes; + return true; + } + } +#endif + + private sealed class CmsIncrementalHash : CmsHash + { + private readonly IncrementalHash _incrementalHash; + + internal CmsIncrementalHash(Oid oid, bool forVerification) + { + _incrementalHash = Helpers.CreateIncrementalHash(PkcsHelpers.GetDigestAlgorithm(oid.Value, forVerification)); + } + + public override void Dispose() => _incrementalHash.Dispose(); + internal override void AppendData(ReadOnlySpan data) => _incrementalHash.AppendData(data); + internal override byte[] GetHashAndReset() => _incrementalHash.GetHashAndReset(); + +#if NET || NETSTANDARD2_1 + internal override bool TryGetHashAndReset(Span destination, out int bytesWritten) => + _incrementalHash.TryGetHashAndReset(destination, out bytesWritten); +#endif + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs index 183520aa9c3f0a..adef3197785863 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -37,10 +36,8 @@ internal DSACmsSignature(string? signatureAlgorithm, HashAlgorithmName expectedD _expectedDigest = expectedDigest; } - protected override bool VerifyKeyType(AsymmetricAlgorithm key) - { - return (key as DSA) != null; - } + protected override bool VerifyKeyType(object key) => key is DSA; + internal override bool NeedsHashedMessage => true; internal override bool VerifySignature( #if NET || NETSTANDARD2_1 @@ -51,10 +48,11 @@ internal override bool VerifySignature( byte[] signature, #endif string? digestAlgorithmOid, - HashAlgorithmName digestAlgorithmName, ReadOnlyMemory? signatureParameters, X509Certificate2 certificate) { + HashAlgorithmName digestAlgorithmName = PkcsHelpers.GetDigestAlgorithm(digestAlgorithmOid, forVerification: true); + if (_expectedDigest != digestAlgorithmName) { throw new CryptographicException( @@ -104,11 +102,11 @@ protected override bool Sign( #if NET || NETSTANDARD2_1 ReadOnlySpan dataHash, #else - byte[] dataHash, + ReadOnlyMemory dataHash, #endif - HashAlgorithmName hashAlgorithmName, + string? hashAlgorithmOid, X509Certificate2 certificate, - AsymmetricAlgorithm? key, + object? key, bool silent, [NotNullWhen(true)] out string? signatureAlgorithm, [NotNullWhen(true)] out byte[]? signatureValue, @@ -130,11 +128,14 @@ protected override bool Sign( } string? oidValue = - hashAlgorithmName == HashAlgorithmName.SHA1 ? Oids.DsaWithSha1 : - hashAlgorithmName == HashAlgorithmName.SHA256 ? Oids.DsaWithSha256 : - hashAlgorithmName == HashAlgorithmName.SHA384 ? Oids.DsaWithSha384 : - hashAlgorithmName == HashAlgorithmName.SHA512 ? Oids.DsaWithSha512 : - null; + hashAlgorithmOid switch + { + Oids.Sha1 => Oids.DsaWithSha1, + Oids.Sha256 => Oids.DsaWithSha256, + Oids.Sha384 => Oids.DsaWithSha384, + Oids.Sha512 => Oids.DsaWithSha512, + _ => null + }; if (oidValue == null) { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs index e89e6e5232d514..0541974cd8b107 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs @@ -38,10 +38,8 @@ internal ECDsaCmsSignature(string? signatureAlgorithm, HashAlgorithmName? expect _expectedDigest = expectedDigest; } - protected override bool VerifyKeyType(AsymmetricAlgorithm key) - { - return (key as ECDsa) != null; - } + protected override bool VerifyKeyType(object key) => key is ECDsa; + internal override bool NeedsHashedMessage => true; internal override bool VerifySignature( #if NET || NETSTANDARD2_1 @@ -52,10 +50,11 @@ internal override bool VerifySignature( byte[] signature, #endif string? digestAlgorithmOid, - HashAlgorithmName digestAlgorithmName, ReadOnlyMemory? signatureParameters, X509Certificate2 certificate) { + HashAlgorithmName digestAlgorithmName = PkcsHelpers.GetDigestAlgorithm(digestAlgorithmOid, forVerification: true); + if (_expectedDigest != null && _expectedDigest != digestAlgorithmName) { throw new CryptographicException( @@ -110,9 +109,9 @@ protected override bool Sign( #else byte[] dataHash, #endif - HashAlgorithmName hashAlgorithmName, + string? hashAlgorithmOid, X509Certificate2 certificate, - AsymmetricAlgorithm? certKey, + object? certKey, bool silent, [NotNullWhen(true)] out string? signatureAlgorithm, [NotNullWhen(true)] out byte[]? signatureValue, @@ -132,16 +131,19 @@ protected override bool Sign( } string? oidValue = - hashAlgorithmName == HashAlgorithmName.SHA1 ? Oids.ECDsaWithSha1 : - hashAlgorithmName == HashAlgorithmName.SHA256 ? Oids.ECDsaWithSha256 : - hashAlgorithmName == HashAlgorithmName.SHA384 ? Oids.ECDsaWithSha384 : - hashAlgorithmName == HashAlgorithmName.SHA512 ? Oids.ECDsaWithSha512 : + hashAlgorithmOid switch + { + Oids.Sha1 => Oids.ECDsaWithSha1, + Oids.Sha256 => Oids.ECDsaWithSha256, + Oids.Sha384 => Oids.ECDsaWithSha384, + Oids.Sha512 => Oids.ECDsaWithSha512, #if NET8_0_OR_GREATER - hashAlgorithmName == HashAlgorithmName.SHA3_256 ? Oids.ECDsaWithSha3_256 : - hashAlgorithmName == HashAlgorithmName.SHA3_384 ? Oids.ECDsaWithSha3_384 : - hashAlgorithmName == HashAlgorithmName.SHA3_512 ? Oids.ECDsaWithSha3_512 : + Oids.Sha3_256 => Oids.ECDsaWithSha3_256, + Oids.Sha3_384 => Oids.ECDsaWithSha3_384, + Oids.Sha3_512 => Oids.ECDsaWithSha3_512, #endif - null; + _ => null, + }; if (oidValue == null) { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs index e0459dc899b7c5..9d4d237172e8a0 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs @@ -39,10 +39,8 @@ protected RSACmsSignature(string? signatureAlgorithm, HashAlgorithmName? expecte _expectedDigest = expectedDigest; } - protected override bool VerifyKeyType(AsymmetricAlgorithm key) - { - return (key as RSA) != null; - } + protected override bool VerifyKeyType(object key) => key is RSA; + internal override bool NeedsHashedMessage => true; internal override bool VerifySignature( #if NET || NETSTANDARD2_1 @@ -53,10 +51,11 @@ internal override bool VerifySignature( byte[] signature, #endif string? digestAlgorithmOid, - HashAlgorithmName digestAlgorithmName, ReadOnlyMemory? signatureParameters, X509Certificate2 certificate) { + HashAlgorithmName digestAlgorithmName = PkcsHelpers.GetDigestAlgorithm(digestAlgorithmOid, forVerification: true); + if (_expectedDigest.HasValue && _expectedDigest.Value != digestAlgorithmName) { throw new CryptographicException( @@ -104,7 +103,7 @@ private protected static bool SignCore( #endif HashAlgorithmName hashAlgorithmName, X509Certificate2 certificate, - AsymmetricAlgorithm? key, + object? key, bool silent, RSASignaturePadding signaturePadding, [NotNullWhen(true)] out byte[]? signatureValue) @@ -202,9 +201,9 @@ protected override bool Sign( #else byte[] dataHash, #endif - HashAlgorithmName hashAlgorithmName, + string? hashAlgorithmOid, X509Certificate2 certificate, - AsymmetricAlgorithm? key, + object? key, bool silent, [NotNullWhen(true)] out string? signatureAlgorithm, [NotNullWhen(true)] out byte[]? signatureValue, @@ -212,7 +211,7 @@ protected override bool Sign( { bool result = SignCore( dataHash, - hashAlgorithmName, + PkcsHelpers.GetDigestAlgorithm(hashAlgorithmOid), certificate, key, silent, @@ -322,14 +321,16 @@ protected override bool Sign( #else byte[] dataHash, #endif - HashAlgorithmName hashAlgorithmName, + string? hashAlgorithmOid, X509Certificate2 certificate, - AsymmetricAlgorithm? key, + object? key, bool silent, [NotNullWhen(true)] out string? signatureAlgorithm, [NotNullWhen(true)] out byte[]? signatureValue, out byte[]? signatureParameters) { + HashAlgorithmName hashAlgorithmName = PkcsHelpers.GetDigestAlgorithm(hashAlgorithmOid); + bool result = SignCore( dataHash, hashAlgorithmName, diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.SlhDsa.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.SlhDsa.cs new file mode 100644 index 00000000000000..940b565030ae08 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.SlhDsa.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography.X509Certificates; +using Internal.Cryptography; + +namespace System.Security.Cryptography.Pkcs +{ + internal partial class CmsSignature + { + static partial void PrepareRegistrationSlhDsa(Dictionary lookup) + { + lookup.Add(Oids.SlhDsaSha2_128s, new SlhDsaCmsSignature(Oids.SlhDsaSha2_128s)); + lookup.Add(Oids.SlhDsaShake128s, new SlhDsaCmsSignature(Oids.SlhDsaShake128s)); + lookup.Add(Oids.SlhDsaSha2_128f, new SlhDsaCmsSignature(Oids.SlhDsaSha2_128f)); + lookup.Add(Oids.SlhDsaShake128f, new SlhDsaCmsSignature(Oids.SlhDsaShake128f)); + lookup.Add(Oids.SlhDsaSha2_192s, new SlhDsaCmsSignature(Oids.SlhDsaSha2_192s)); + lookup.Add(Oids.SlhDsaShake192s, new SlhDsaCmsSignature(Oids.SlhDsaShake192s)); + lookup.Add(Oids.SlhDsaSha2_192f, new SlhDsaCmsSignature(Oids.SlhDsaSha2_192f)); + lookup.Add(Oids.SlhDsaShake192f, new SlhDsaCmsSignature(Oids.SlhDsaShake192f)); + lookup.Add(Oids.SlhDsaSha2_256s, new SlhDsaCmsSignature(Oids.SlhDsaSha2_256s)); + lookup.Add(Oids.SlhDsaShake256s, new SlhDsaCmsSignature(Oids.SlhDsaShake256s)); + lookup.Add(Oids.SlhDsaSha2_256f, new SlhDsaCmsSignature(Oids.SlhDsaSha2_256f)); + lookup.Add(Oids.SlhDsaShake256f, new SlhDsaCmsSignature(Oids.SlhDsaShake256f)); + } + + private sealed class SlhDsaCmsSignature : CmsSignature + { + private string _signatureAlgorithm; + + internal SlhDsaCmsSignature(string signatureAlgorithm) + { + _signatureAlgorithm = signatureAlgorithm; + } + + protected override bool VerifyKeyType(object key) => key is SlhDsa; + internal override bool NeedsHashedMessage => false; + + internal override RSASignaturePadding? SignaturePadding => null; + + internal override bool VerifySignature( +#if NET || NETSTANDARD2_1 + ReadOnlySpan valueHash, + ReadOnlyMemory signature, +#else + byte[] valueHash, + byte[] signature, +#endif + string? digestAlgorithmOid, + ReadOnlyMemory? signatureParameters, + X509Certificate2 certificate) + { + if (signatureParameters.HasValue) + { + throw new CryptographicException( + SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, _signatureAlgorithm)); + } + + // The spec (as of May 5, 2025) has strength requirements on the hash, but we will + // not enforce them here. If the callers wants to enforce them, they can do so by themselves. + + SlhDsa? publicKey = certificate.GetSlhDsaPublicKey(); + + if (publicKey is null) + { + return false; + } + + using (publicKey) + { + return publicKey.VerifyData( + valueHash, +#if NET || NETSTANDARD2_1 + signature.Span +#else + signature +#endif + ); + } + } + + protected override bool Sign( +#if NET || NETSTANDARD2_1 + ReadOnlySpan dataHash, +#else + byte[] dataHash, +#endif + string? hashAlgorithmOid, + X509Certificate2 certificate, + object? key, + bool silent, + [NotNullWhen(true)] out string? signatureAlgorithm, + [NotNullWhen(true)] out byte[]? signatureValue, + out byte[]? signatureParameters) + { + // The spec (as of May 5, 2025) has strength requirements on the hash, but we will + // not enforce them here. It is up to the caller to choose an appropriate hash. + + signatureParameters = null; + + SlhDsa? signingKey = key as SlhDsa; + IDisposable? signingKeyResources = null; + + if (signingKey is null) + { + // If there's no private key, fall back to the public key for a "no private key" exception. + signingKeyResources = signingKey = + PkcsPal.Instance.GetPrivateKeyForSigning(certificate, silent) ?? certificate.GetSlhDsaPublicKey(); + + if (signingKey is null) + { + signatureAlgorithm = null; + signatureValue = null; + return false; + } + } + + using (signingKeyResources) + { + // Don't pool because we will likely return this buffer to the caller. + byte[] signature = new byte[signingKey.Algorithm.SignatureSizeInBytes]; + signingKey.SignData(dataHash, signature); + + if (key != null) + { + using (SlhDsa certKey = certificate.GetSlhDsaPublicKey()!) + { + if (!certKey.VerifyData(dataHash, signature)) + { + signatureAlgorithm = null; + signatureValue = null; + return false; + } + } + } + + signatureValue = signature; + signatureAlgorithm = _signatureAlgorithm; + return true; + } + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs index a0595c61dc3c00..ac08a6c975df92 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.cs @@ -20,14 +20,17 @@ static CmsSignature() PrepareRegistrationRsa(s_lookup); PrepareRegistrationDsa(s_lookup); PrepareRegistrationECDsa(s_lookup); + PrepareRegistrationSlhDsa(s_lookup); } static partial void PrepareRegistrationRsa(Dictionary lookup); static partial void PrepareRegistrationDsa(Dictionary lookup); static partial void PrepareRegistrationECDsa(Dictionary lookup); + static partial void PrepareRegistrationSlhDsa(Dictionary lookup); internal abstract RSASignaturePadding? SignaturePadding { get; } - protected abstract bool VerifyKeyType(AsymmetricAlgorithm key); + protected abstract bool VerifyKeyType(object key); + internal abstract bool NeedsHashedMessage { get; } internal abstract bool VerifySignature( #if NET || NETSTANDARD2_1 @@ -38,7 +41,6 @@ internal abstract bool VerifySignature( byte[] signature, #endif string? digestAlgorithmOid, - HashAlgorithmName digestAlgorithmName, ReadOnlyMemory? signatureParameters, X509Certificate2 certificate); @@ -48,9 +50,9 @@ protected abstract bool Sign( #else byte[] dataHash, #endif - HashAlgorithmName hashAlgorithmName, + string? hashAlgorithmOid, X509Certificate2 certificate, - AsymmetricAlgorithm? key, + object? key, bool silent, [NotNullWhen(true)] out string? signatureAlgorithm, [NotNullWhen(true)] out byte[]? signatureValue, @@ -58,7 +60,7 @@ protected abstract bool Sign( internal static CmsSignature? ResolveAndVerifyKeyType( string signatureAlgorithmOid, - AsymmetricAlgorithm? key, + object? key, RSASignaturePadding? rsaSignaturePadding) { // Rules: @@ -111,34 +113,23 @@ protected abstract bool Sign( return null; } - internal static bool Sign( + internal bool Sign( #if NET || NETSTANDARD2_1 ReadOnlySpan dataHash, #else byte[] dataHash, #endif - HashAlgorithmName hashAlgorithmName, + string? hashAlgorithmOid, X509Certificate2 certificate, - AsymmetricAlgorithm? key, + object? key, bool silent, - RSASignaturePadding? rsaSignaturePadding, out string? oid, out ReadOnlyMemory signatureValue, out ReadOnlyMemory signatureParameters) { - CmsSignature? processor = ResolveAndVerifyKeyType(certificate.GetKeyAlgorithm(), key, rsaSignaturePadding); - - if (processor == null) - { - oid = null; - signatureValue = default; - signatureParameters = default; - return false; - } - - bool signed = processor.Sign( + bool signed = Sign( dataHash, - hashAlgorithmName, + hashAlgorithmOid, certificate, key, silent, diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs index 15acc4d28cfe88..3ff267bb3f8f3a 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSigner.cs @@ -1,10 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; using System.Security.Cryptography.Asn1; using System.Security.Cryptography.Asn1.Pkcs7; @@ -14,19 +14,33 @@ namespace System.Security.Cryptography.Pkcs { - public sealed class CmsSigner + public sealed partial class CmsSigner { private static readonly Oid s_defaultAlgorithm = Oids.Sha256Oid; private SubjectIdentifierType _signerIdentifierType; private RSASignaturePadding? _signaturePadding; + private IDisposable? _privateKey; public X509Certificate2? Certificate { get; set; } + +#if NET || NETSTANDARD2_1 + public AsymmetricAlgorithm? PrivateKey +#else + private AsymmetricAlgorithm? PrivateKey +#endif + { + get => _privateKey as AsymmetricAlgorithm; + set => _privateKey = value; + } + #if NET || NETSTANDARD2_1 - public AsymmetricAlgorithm? PrivateKey { get; set; } + public #else - private AsymmetricAlgorithm? PrivateKey { get; set; } + private #endif + bool HasPrivateKey => _privateKey is not null; + public X509Certificate2Collection Certificates { get; } = new X509Certificate2Collection(); public Oid DigestAlgorithm { get; set; } public X509IncludeOption IncludeOption { get; set; } @@ -89,7 +103,8 @@ public CmsSigner(X509Certificate2? certificate) [EditorBrowsable(EditorBrowsableState.Never)] public CmsSigner(CspParameters parameters) => throw new PlatformNotSupportedException(); - public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate) : this(signerIdentifierType, certificate, null) + public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate) + : this(signerIdentifierType, certificate, null, null) { } @@ -103,6 +118,18 @@ public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? c { } + +#if NET || NETSTANDARD2_1 + [Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public +#else + private +#endif + CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate, SlhDsa? privateKey) + : this(signerIdentifierType, certificate, privateKey, signaturePadding: null) + { + } + /// /// Initializes a new instance of the CmsSigner class with a specified signer /// certificate, subject identifier type, private key object, and RSA signature padding. @@ -137,7 +164,7 @@ public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? c private CmsSigner( SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate, - AsymmetricAlgorithm? privateKey, + object? privateKey, RSASignaturePadding? signaturePadding) { if (signaturePadding is not null && @@ -172,7 +199,10 @@ private CmsSigner( Certificate = certificate; DigestAlgorithm = s_defaultAlgorithm.CopyOid(); - PrivateKey = privateKey; + + Debug.Assert(privateKey is null or AsymmetricAlgorithm or SlhDsa); + _privateKey = (IDisposable?)privateKey; + _signaturePadding = signaturePadding; } @@ -188,85 +218,120 @@ internal void CheckCertificateValue() throw new PlatformNotSupportedException(SR.Cryptography_Cms_NoSignerCert); } - if (PrivateKey == null && !Certificate.HasPrivateKey) + if (_privateKey == null && !Certificate.HasPrivateKey) { throw new CryptographicException(SR.Cryptography_Cms_Signing_RequiresPrivateKey); } } - internal SignerInfoAsn Sign( - ReadOnlyMemory data, - string? contentTypeOid, - bool silent, - out X509Certificate2Collection chainCerts) + private byte[] PrepareAttributesToSign(ReadOnlySpan contentHash, string? contentTypeOid, out AsnWriter newSignedAttrsWriter) { - HashAlgorithmName hashAlgorithmName = PkcsHelpers.GetDigestAlgorithm(DigestAlgorithm); - SignerInfoAsn newSignerInfo = default; - newSignerInfo.DigestAlgorithm.Algorithm = DigestAlgorithm.Value!; - byte[] dataHash; + List signedAttrs = PkcsHelpers.BuildAttributes(SignedAttributes); + + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + writer.WriteOctetString(contentHash); - IncrementalHash hasher; + signedAttrs.Add( + new AttributeAsn + { + AttrType = Oids.MessageDigest, + AttrValues = new[] { new ReadOnlyMemory(writer.Encode()) }, + }); - try + if (contentTypeOid != null) { - hasher = IncrementalHash.CreateHash(hashAlgorithmName); + writer.Reset(); + writer.WriteObjectIdentifierForCrypto(contentTypeOid); + + signedAttrs.Add( + new AttributeAsn + { + AttrType = Oids.ContentType, + AttrValues = new[] { new ReadOnlyMemory(writer.Encode()) }, + }); } - catch (PlatformNotSupportedException ex) + // else if we're in pure mode: we *should* add a content type according to + // the SLH-DSA and ML-DSA spec. However, the only case when the content type is null + // is when we're countersigning, and RFC 5652 specifically states that + // countersignatures must not contain a content type. We'll leave it as is for now + // as countersignatures don't seem to be in the SLH-DSA CMS spec. + + // Use the serializer/deserializer to DER-normalize the attribute order. + SignedAttributesSet signedAttrsSet = default; + signedAttrsSet.SignedAttributes = PkcsHelpers.NormalizeAttributeSet( + signedAttrs.ToArray(), + out byte[] attributesToSign); + + // Since this contains user data in a context where BER is permitted, use BER. + // There shouldn't be any observable difference here between BER and DER, though, + // since the top level fields were written by NormalizeSet. + newSignedAttrsWriter = new AsnWriter(AsnEncodingRules.BER); + signedAttrsSet.Encode(newSignedAttrsWriter); + + return attributesToSign; + } + + internal ReadOnlyMemory GetPureMessageToSign( + ReadOnlyMemory data, + string? contentTypeOid, + out ReadOnlyMemory? signedAttributesAsn) + { + byte[] dataHash; + // In pure mode we will always sign the attributes rather than the message content even + // when signing the content is allowed. In general the attribute payload is smaller. + using (CmsHash hasher = CmsHash.Create(DigestAlgorithm, forVerification: false)) { - throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName), ex); + hasher.AppendData(data.Span); + dataHash = hasher.GetHashAndReset(); } - using (hasher) + byte[] contentToSign = PrepareAttributesToSign(dataHash, contentTypeOid, out AsnWriter newSignedAttrsWriter); + signedAttributesAsn = newSignedAttrsWriter.Encode(); + return contentToSign; + } + + internal ReadOnlyMemory GetHashedMessageToSign( + ReadOnlyMemory data, + string? contentTypeOid, + out ReadOnlyMemory? signedAttributesAsn) + { + using (CmsHash hasher = CmsHash.Create(DigestAlgorithm, forVerification: false)) { hasher.AppendData(data.Span); - dataHash = hasher.GetHashAndReset(); + byte[] dataHash = hasher.GetHashAndReset(); // If the user specified attributes (not null, count > 0) we need attributes. // If the content type is null we're counter-signing, and need the message digest attr. // If the content type is otherwise not-data we need to record it as the content-type attr. if (SignedAttributes?.Count > 0 || contentTypeOid != Oids.Pkcs7Data) { - List signedAttrs = PkcsHelpers.BuildAttributes(SignedAttributes); - - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); - writer.WriteOctetString(dataHash); - - signedAttrs.Add( - new AttributeAsn - { - AttrType = Oids.MessageDigest, - AttrValues = new[] { new ReadOnlyMemory(writer.Encode()) }, - }); - - if (contentTypeOid != null) - { - writer.Reset(); - writer.WriteObjectIdentifierForCrypto(contentTypeOid); - - signedAttrs.Add( - new AttributeAsn - { - AttrType = Oids.ContentType, - AttrValues = new[] { new ReadOnlyMemory(writer.Encode()) }, - }); - } + hasher.AppendData(PrepareAttributesToSign(dataHash, contentTypeOid, out AsnWriter newSignedAttrsWriter)); + signedAttributesAsn = newSignedAttrsWriter.Encode(); + return hasher.GetHashAndReset(); + } - // Use the serializer/deserializer to DER-normalize the attribute order. - SignedAttributesSet signedAttrsSet = default; - signedAttrsSet.SignedAttributes = PkcsHelpers.NormalizeAttributeSet( - signedAttrs.ToArray(), - hasher.AppendData); + signedAttributesAsn = null; + return dataHash; + } + } - // Since this contains user data in a context where BER is permitted, use BER. - // There shouldn't be any observable difference here between BER and DER, though, - // since the top level fields were written by NormalizeSet. - AsnWriter attrsWriter = new AsnWriter(AsnEncodingRules.BER); - signedAttrsSet.Encode(attrsWriter); - newSignerInfo.SignedAttributes = attrsWriter.Encode(); + internal ReadOnlyMemory GetMessageToSign( + bool shouldHash, + ReadOnlyMemory data, + string? contentTypeOid, + out ReadOnlyMemory? signedAttributesAsn) => + shouldHash + ? GetHashedMessageToSign(data, contentTypeOid, out signedAttributesAsn) + : GetPureMessageToSign(data, contentTypeOid, out signedAttributesAsn); - dataHash = hasher.GetHashAndReset(); - } - } + internal SignerInfoAsn Sign( + ReadOnlyMemory data, + string? contentTypeOid, + bool silent, + out X509Certificate2Collection chainCerts) + { + SignerInfoAsn newSignerInfo = default; + newSignerInfo.DigestAlgorithm.Algorithm = DigestAlgorithm.Value!; switch (SignerIdentifierType) { @@ -313,19 +378,60 @@ internal SignerInfoAsn Sign( if (SignerIdentifierType == SubjectIdentifierType.NoSignature) { + // The behavior of this scenario should match Windows which currently does not + // implement PQC. So we do a best effort determination of whether the algorithm + // is a pure algorithm and throw if so. This is subject to change once Windows + // implements PQC. + string? keyAlgorithm = null; + if (Certificate != null) + { + try + { + keyAlgorithm = Certificate.GetKeyAlgorithm(); + } + catch (CryptographicException) + { + } + } + + if (keyAlgorithm != null) + { + CmsSignature? processor = CmsSignature.ResolveAndVerifyKeyType(keyAlgorithm, _privateKey, SignaturePadding); + if (processor?.NeedsHashedMessage == false) + { + throw new CryptographicException(SR.Cryptography_Cms_CertificateDoesNotSupportNoSignature); + } + } + + ReadOnlyMemory messageToSign = + GetMessageToSign(shouldHash: true, data, contentTypeOid, out newSignerInfo.SignedAttributes); + signatureAlgorithm = Oids.NoSignature; - signatureValue = dataHash; + signatureValue = messageToSign; signed = true; } else { - signed = CmsSignature.Sign( - dataHash, - hashAlgorithmName, + CmsSignature? processor = CmsSignature.ResolveAndVerifyKeyType(Certificate!.GetKeyAlgorithm(), _privateKey, SignaturePadding); + if (processor == null) + { + throw new CryptographicException(SR.Cryptography_Cms_CannotDetermineSignatureAlgorithm); + } + + bool shouldHash = processor.NeedsHashedMessage; + ReadOnlyMemory messageToSign = + GetMessageToSign(shouldHash, data, contentTypeOid, out newSignerInfo.SignedAttributes); + + signed = processor.Sign( +#if NET || NETSTANDARD2_1 + messageToSign.Span, +#else + messageToSign.ToArray(), +#endif + DigestAlgorithm.Value, Certificate!, - PrivateKey, + _privateKey, silent, - SignaturePadding, out signatureAlgorithm, out signatureValue, out signatureParameters); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs index fedffc1b7aab58..52bbd6d0b95f9b 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs @@ -418,15 +418,14 @@ public void RemoveSignature(SignerInfo signerInfo) RemoveSignature(idx); } - internal ReadOnlySpan GetHashableContentSpan() + internal ReadOnlyMemory GetHashableContentMemory() { Debug.Assert(_heldContent.HasValue); ReadOnlyMemory content = _heldContent.Value; - ReadOnlySpan contentSpan = content.Span; if (!_hasPkcs7Content) { - return contentSpan; + return content; } // In PKCS#7 compat, only return the contents within the outermost tag. @@ -434,13 +433,13 @@ internal ReadOnlySpan GetHashableContentSpan() try { AsnDecoder.ReadEncodedValue( - contentSpan, + content.Span, AsnEncodingRules.BER, out int contentOffset, out int contentLength, out _); - return contentSpan.Slice(contentOffset, contentLength); + return content.Slice(contentOffset, contentLength); } catch (AsnContentException e) { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignerInfo.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignerInfo.cs index 095ec400cf12de..c3f88d54b65578 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignerInfo.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignerInfo.cs @@ -483,17 +483,17 @@ public void CheckHash() private bool CheckHash(bool compatMode) { - using (IncrementalHash? hasher = PrepareDigest(compatMode)) + // compatMode only affects attribute processing so if there are none then + // compatMode true and false are the same. So short circuit when true. + if (_signedAttributes == null && compatMode) { - if (hasher == null) - { - Debug.Assert(compatMode, $"{nameof(PrepareDigest)} returned null for the primary check"); - return false; - } - - byte[] expectedSignature = hasher.GetHashAndReset(); - return _signature.Span.SequenceEqual(expectedSignature); + return false; } + + Debug.Assert(_signatureAlgorithm == Oids.NoSignature); + + // The signature is a hash of the message or signed attributes. + return VerifyHashedMessage(compatMode, contentToVerify => _signature.Span.SequenceEqual(contentToVerify)); } private X509Certificate2? FindSignerCertificate() @@ -558,22 +558,9 @@ private bool CheckHash(bool compatMode) return match; } - private IncrementalHash? PrepareDigest(bool compatMode) + private ReadOnlyMemory GetContentForVerification(out ReadOnlyMemory? additionalContent) { - HashAlgorithmName hashAlgorithmName = GetDigestAlgorithm(); - - - IncrementalHash hasher; - - try - { - hasher = IncrementalHash.CreateHash(hashAlgorithmName); - } - catch (PlatformNotSupportedException ex) - { - throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName), ex); - } - + additionalContent = null; if (_parentSignerInfo == null) { // Windows compatibility: If a document was loaded in detached mode, @@ -591,100 +578,236 @@ private bool CheckHash(bool compatMode) embeddedContent.Value, documentData.EncapContentInfo.ContentType); - hasher.AppendData(hashableContent.Span); + additionalContent = hashableContent; } } - hasher.AppendData(_document.GetHashableContentSpan()); + return _document.GetHashableContentMemory(); } else { - hasher.AppendData(_parentSignerInfo._signature.Span); + // We are a counter-signer, so the content is the signature of the parent + return _parentSignerInfo._signature; } + } - // A Counter-Signer always requires signed attributes. - // If any signed attributes are present, message-digest is required. - bool invalid = _parentSignerInfo != null || _signedAttributes != null; - - if (_signedAttributes != null) + private CmsHash GetContentHash(ReadOnlyMemory content, ReadOnlyMemory? additionalContent) + { + CmsHash hasher = CmsHash.Create(DigestAlgorithm, forVerification: true); + if (additionalContent.HasValue) { - byte[] contentDigest = hasher.GetHashAndReset(); + hasher.AppendData(additionalContent.Value.Span); + } - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); - { - // Some CMS implementations exist which do not sort the attributes prior to - // generating the signature. While they are not, technically, validly signed, - // Windows and OpenSSL both support trying in the document order rather than - // a sorted order. To accomplish this we will build as a SEQUENCE OF, but feed - // the SET OF into the hasher. - if (compatMode) - { - writer.PushSequence(); - } - else - { - writer.PushSetOf(); - } + hasher.AppendData(content.Span); - foreach (AttributeAsn attr in _signedAttributes) - { - attr.Encode(writer); + return hasher; + } - // .NET Framework doesn't seem to validate the content type attribute, - // so we won't, either. + private static bool VerifyAttributes( + ReadOnlySpan digest, + AttributeAsn[] signedAttributes, + bool compatMode, + bool needsContentAttr, + VerifyCallback verify) + { + bool hasMatchingDigestAttr = false; + bool hasContentAttr = false; - if (attr.AttrType == Oids.MessageDigest) - { - CryptographicAttributeObject obj = PkcsHelpers.MakeAttribute(attr); + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); - if (obj.Values.Count != 1) - { - throw new CryptographicException(SR.Cryptography_BadHashValue); - } + // Some CMS implementations exist which do not sort the attributes prior to + // generating the signature. While they are not, technically, validly signed, + // Windows and OpenSSL both support trying in the document order rather than + // a sorted order. To accomplish this we will build as a SEQUENCE OF, but feed + // the SET OF into the hasher. + using (compatMode ? writer.PushSequence() : writer.PushSetOf()) + { + foreach (AttributeAsn attr in signedAttributes) + { + attr.Encode(writer); - var digestAttr = (Pkcs9MessageDigest)obj.Values[0]; + // .NET Framework doesn't seem to validate the content type attribute, + // so we won't, either. - if (!contentDigest.AsSpan().SequenceEqual(digestAttr.MessageDigest)) - { - throw new CryptographicException(SR.Cryptography_BadHashValue); - } + if (attr.AttrType == Oids.MessageDigest) + { + CryptographicAttributeObject obj = PkcsHelpers.MakeAttribute(attr); - invalid = false; + if (obj.Values.Count != 1) + { + throw new CryptographicException(SR.Cryptography_BadHashValue); } - } - if (compatMode) - { - writer.PopSequence(); + var digestAttr = (Pkcs9MessageDigest)obj.Values[0]; - byte[] encoded = writer.Encode(); - encoded[0] = 0x31; - hasher.AppendData(encoded); + if (!digest.SequenceEqual(digestAttr.MessageDigest)) + { + throw new CryptographicException(SR.Cryptography_BadHashValue); + } + + hasMatchingDigestAttr = true; } - else + else if (attr.AttrType == Oids.ContentType) { - writer.PopSetOf(); + hasContentAttr = true; + } + } + } + + // Message-digest is required when signed attributes are present. + if (!hasMatchingDigestAttr || (needsContentAttr && !hasContentAttr)) + { + throw new CryptographicException(SR.Cryptography_Cms_MissingAuthenticatedAttribute); + } + if (compatMode) + { + byte[] encoded = writer.Encode(); + encoded[0] = 0x31; + return verify(encoded); + } + else + { #if NET9_0_OR_GREATER - writer.Encode(hasher, static (hasher, encoded) => hasher.AppendData(encoded)); + return writer.Encode(verify, static (verify, encoded) => verify(encoded)); #else - hasher.AppendData(writer.Encode()); + return verify(writer.Encode()); #endif + } + } + + private delegate bool VerifyCallback(ReadOnlySpan contentToVerify); + + private bool VerifyHashedMessage(bool compatMode, VerifyCallback verify) + { + ReadOnlyMemory content = GetContentForVerification(out ReadOnlyMemory? additionalContent); + + using (CmsHash hasher = GetContentHash(content, additionalContent)) + { +#if NET || NETSTANDARD2_1 + // SHA-2-512 is the biggest digest type we know about. + Span contentHash = stackalloc byte[512 / 8]; + + if (hasher.TryGetHashAndReset(contentHash, out int bytesWritten)) + { + contentHash = contentHash.Slice(0, bytesWritten); + } + else + { + contentHash = hasher.GetHashAndReset(); + } +#else + byte[] contentHash = hasher.GetHashAndReset(); +#endif + + // If there are no signed attributes, we can just verify the content directly. + if (_signedAttributes == null) + { + // A Counter-Signer always requires signed attributes. + if (_parentSignerInfo != null) + { + throw new CryptographicException(SR.Cryptography_Cms_MissingAuthenticatedAttribute); } + + return verify(contentHash); } + + // Since there are signed attributes, we need to verify those instead. + return + VerifyAttributes( + contentHash, + _signedAttributes, + compatMode, + needsContentAttr: false, + span => + { + hasher.AppendData(span); + +#if NET || NETSTANDARD2_1 + // SHA-2-512 is the biggest digest type we know about. + Span attrHash = stackalloc byte[512 / 8]; + + if (hasher.TryGetHashAndReset(attrHash, out int bytesWritten)) + { + attrHash = attrHash.Slice(0, bytesWritten); + } + else + { + attrHash = hasher.GetHashAndReset(); + } +#else + byte[] attrHash = hasher.GetHashAndReset(); +#endif + + return verify(attrHash); + }); } - else if (compatMode) + } + + private bool VerifyPureMessage(bool compatMode, VerifyCallback verify) + { + ReadOnlyMemory content = GetContentForVerification(out ReadOnlyMemory? additionalContent); + + // If there are no signed attributes, we can just verify the content directly. + if (_signedAttributes == null) { - // If there were no signed attributes there's nothing to be compatible about. - return null; + // A Counter-Signer always requires signed attributes. + if (_parentSignerInfo != null) + { + throw new CryptographicException(SR.Cryptography_Cms_MissingAuthenticatedAttribute); + } + + if (!additionalContent.HasValue) + { + return verify(content.Span); + } + + // If there are multiple pieces of content, concatenate them and verify. + int contentToVerifyLength = content.Length + additionalContent.Value.Length; + byte[] rented = CryptoPool.Rent(contentToVerifyLength); + try + { + additionalContent.Value.Span.CopyTo(rented); + content.Span.CopyTo(rented.AsSpan(additionalContent.Value.Length)); + return verify(rented.AsSpan(0, contentToVerifyLength)); + } + finally + { + CryptoPool.Return(rented); + } } - if (invalid) + // Since there are signed attributes, we need to verify those instead. + using (CmsHash hasher = GetContentHash(content, additionalContent)) { - throw new CryptographicException(SR.Cryptography_Cms_MissingAuthenticatedAttribute); - } +#if NET || NETSTANDARD2_1 + // SHA-2-512 is the biggest digest type we know about. + Span contentHash = stackalloc byte[512 / 8]; - return hasher; + if (hasher.TryGetHashAndReset(contentHash, out int bytesWritten)) + { + contentHash = contentHash.Slice(0, bytesWritten); + } + else + { + contentHash = hasher.GetHashAndReset(); + } +#else + byte[] contentHash = hasher.GetHashAndReset(); +#endif + + return + VerifyAttributes( + contentHash, + _signedAttributes, + compatMode, + // IETF spec for SLH-DSA/ML-DSA requires that the content type be present but RFC 5652 says + // it is invalid for countersigners. We'll just ignore it for now, but if we decide to + // allow countersigners to omit the content type, we should check that `this` is a countersigner. + needsContentAttr: false, + verify); + } } private void Verify( @@ -766,46 +889,26 @@ private bool VerifySignature( X509Certificate2 certificate, bool compatMode) { - using (IncrementalHash? hasher = PrepareDigest(compatMode)) + // compatMode only affects attribute processing so if there are none then + // compatMode true and false are the same. So short circuit when true. + if (_signedAttributes == null && compatMode) { - if (hasher == null) - { - Debug.Assert(compatMode, $"{nameof(PrepareDigest)} returned null for the primary check"); - return false; - } + return false; + } + Func verifier = signatureProcessor.NeedsHashedMessage ? VerifyHashedMessage : VerifyPureMessage; + return verifier(compatMode, contentToVerify => + signatureProcessor.VerifySignature( #if NET || NETSTANDARD2_1 - // SHA-2-512 is the biggest digest type we know about. - Span digestValue = stackalloc byte[512 / 8]; - ReadOnlySpan digest = digestValue; - ReadOnlyMemory signature = _signature; - - if (hasher.TryGetHashAndReset(digestValue, out int bytesWritten)) - { - digest = digestValue.Slice(0, bytesWritten); - } - else - { - digest = hasher.GetHashAndReset(); - } + contentToVerify, + _signature, #else - byte[] digest = hasher.GetHashAndReset(); - byte[] signature = _signature.ToArray(); + contentToVerify.ToArray(), + _signature.ToArray(), #endif - - return signatureProcessor.VerifySignature( - digest, - signature, DigestAlgorithm.Value, - hasher.AlgorithmName, _signatureAlgorithmParameters, - certificate); - } - } - - private HashAlgorithmName GetDigestAlgorithm() - { - return PkcsHelpers.GetDigestAlgorithm(DigestAlgorithm.Value!, forVerification: true); + certificate)); } private static int FindAttributeIndexByOid(AttributeAsn[] attributes, Oid oid, int startIndex = 0) diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/Certificates.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/Certificates.cs index fb95bd9e36387d..1d8c682cdcdd0e 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/Certificates.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/Certificates.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Linq; +using System.Security.Cryptography.SLHDsa.Tests; using Test.Cryptography; namespace System.Security.Cryptography.Pkcs.Tests @@ -32,6 +34,13 @@ internal static class Certificates public static readonly CertLoader RsaOaep2048_Sha1Parameters = new CertLoaderFromRawData(RawData.RsaOaep2048_Sha1ParametersCert, RawData.RsaOaep2048_Sha1ParametersPfx, "1111"); public static readonly CertLoader RsaOaep2048_Sha256Parameters = new CertLoaderFromRawData(RawData.RsaOaep2048_Sha256ParametersCert, RawData.RsaOaep2048_Sha256ParametersPfx, "1111"); public static readonly CertLoader RsaOaep2048_NoParameters = new CertLoaderFromRawData(RawData.RsaOaep2048_NoParametersCert, RawData.RsaOaep2048_NoParametersPfx, "1111"); + public static readonly CertLoader SlhDsaSha2_128s_Ietf = new CertLoaderFromRawData(SlhDsaTestData.IetfSlhDsaSha2_128sCertificate, SlhDsaTestData.IetfSlhDsaSha2_128sCertificatePfx, "PLACEHOLDER"); + public static readonly CertLoader[] SlhDsaGeneratedCerts = LoadSlhDsaCerts(); + + private static CertLoader[] LoadSlhDsaCerts() => + SlhDsaTestData.GeneratedKeyInfosRaw + .Select(info => new CertLoaderFromRawData(info.Certificate, info.SelfSignedCertificatePfx, info.EncryptionPassword)) + .ToArray(); // Note: the raw data is its own (nested) class to avoid problems with static field initialization ordering. private static class RawData diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs index a9e73c30df756e..ae8957fded5f65 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs @@ -67,6 +67,8 @@ internal static class Oids public const string Sha3_256 = "2.16.840.1.101.3.4.2.8"; public const string Sha3_384 = "2.16.840.1.101.3.4.2.9"; public const string Sha3_512 = "2.16.840.1.101.3.4.2.10"; + public const string Shake128 = "2.16.840.1.101.3.4.2.11"; + public const string Shake256 = "2.16.840.1.101.3.4.2.12"; // RFC3161 Timestamping public const string TstInfo = "1.2.840.113549.1.9.16.1.4"; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/CmsSignerTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/CmsSignerTests.cs index 27be5cf21152f1..15ad675e372b7c 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/CmsSignerTests.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/CmsSignerTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; +using System.Security.Cryptography.SLHDsa.Tests; using Xunit; namespace System.Security.Cryptography.Pkcs.Tests @@ -57,6 +58,44 @@ public static void SignaturePadding_Null() privateKey: null, signaturePadding: null); // Assert.NoThrow } + + [Fact] + public static void HasPrivateKey_IsCorrect() + { + CmsSigner signer = new CmsSigner(); + AssertExtensions.FalseExpression(signer.HasPrivateKey); + + using (RSA rsa = RSA.Create()) + { + // Create signer with RSA key + signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, null, rsa); + AssertExtensions.TrueExpression(signer.HasPrivateKey); + Assert.NotNull(signer.PrivateKey); + Assert.Equal(rsa.SignatureAlgorithm, signer.PrivateKey.SignatureAlgorithm); + + signer.PrivateKey = null; + AssertExtensions.FalseExpression(signer.HasPrivateKey); + + if (SlhDsa.IsSupported) + { + using SlhDsa slhDsa = + SlhDsa.ImportSlhDsaSecretKey( + SlhDsaAlgorithm.SlhDsaSha2_128s, + SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue); + + // Create signer with SlhDsa key + signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, null, slhDsa); + AssertExtensions.TrueExpression(signer.HasPrivateKey); + Assert.Null(signer.PrivateKey); // SlhDsa does not expose the private key + + // Change private key to RSA key + signer.PrivateKey = rsa; + AssertExtensions.TrueExpression(signer.HasPrivateKey); + Assert.NotNull(signer.PrivateKey); + Assert.Equal(rsa.SignatureAlgorithm, signer.PrivateKey.SignatureAlgorithm); + } + } + } #endif } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.cs index c0c1421daac423..b81b4850216a17 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.SLHDsa.Tests; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; using Test.Cryptography; @@ -670,6 +673,79 @@ public static void AddFirstSigner_ECDSA(SubjectIdentifierType identifierType, bo cms.CheckSignature(true); } + public static IEnumerable AddFirstSignerSlhDsaTestData => + from sit in new[] { SubjectIdentifierType.IssuerAndSerialNumber, SubjectIdentifierType.SubjectKeyIdentifier } + from detached in new[] { false, true } + from algorithms in new (SlhDsaAlgorithm signAlgorithm, string hashAlgorithm)[] + { + (SlhDsaAlgorithm.SlhDsaSha2_128s, Oids.Sha256), + (SlhDsaAlgorithm.SlhDsaShake128f, Oids.Shake128), + (SlhDsaAlgorithm.SlhDsaSha2_256f, Oids.Sha512), + (SlhDsaAlgorithm.SlhDsaShake256f, Oids.Shake256), + } + from SlhDsaTestData.SlhDsaGeneratedKeyInfo info in SlhDsaTestData.GeneratedKeyInfosRaw + where info.Algorithm == algorithms.signAlgorithm // Find the matching test data for the algorithm + select new object[] { sit, detached, algorithms.hashAlgorithm, info }; + + [ConditionalTheory(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + [MemberData(nameof(AddFirstSignerSlhDsaTestData))] + public static void AddFirstSigner_SlhDsa(SubjectIdentifierType identifierType, bool detached, string digestOid, SlhDsaTestData.SlhDsaGeneratedKeyInfo info) + { + ContentInfo contentInfo = new ContentInfo(new byte[] { 9, 8, 7, 6, 5 }); + SignedCms cms = new SignedCms(contentInfo, detached); + + CertLoader loader = Certificates.SlhDsaGeneratedCerts.Single(cert => cert.CerData.SequenceEqual(info.Certificate)); + using (X509Certificate2 signerCert = loader.TryGetCertificateWithPrivateKey()) + { + CmsSigner signer = new CmsSigner(identifierType, signerCert); + signer.IncludeOption = X509IncludeOption.EndCertOnly; + signer.DigestAlgorithm = new Oid(digestOid, digestOid); + cms.ComputeSignature(signer); + } + + Assert.Single(cms.SignerInfos); + Assert.Single(cms.Certificates); + + int expectedVersion = identifierType == SubjectIdentifierType.SubjectKeyIdentifier ? 3 : 1; + Assert.Equal(expectedVersion, cms.Version); + + SignerInfo firstSigner = cms.SignerInfos[0]; + Assert.Equal(identifierType, firstSigner.SignerIdentifier.Type); + Assert.NotNull(firstSigner.Certificate); + Assert.NotSame(cms.Certificates[0], firstSigner.Certificate); + Assert.Equal(cms.Certificates[0], firstSigner.Certificate); + + byte[] signature = firstSigner.GetSignature(); + Assert.NotEmpty(signature); + + // SLH-DSA Oids are all under 2.16.840.1.101.3.4.3. + Assert.StartsWith("2.16.840.1.101.3.4.3.", firstSigner.SignatureAlgorithm.Value); + + cms.CheckSignature(true); + byte[] encoded = cms.Encode(); + + cms = new SignedCms(); + cms.Decode(encoded); + + Assert.Single(cms.SignerInfos); + Assert.Single(cms.Certificates); + Assert.Equal(expectedVersion, cms.Version); + Assert.Equal(identifierType, cms.SignerInfos[0].SignerIdentifier.Type); + Assert.Equal(firstSigner.Certificate, cms.SignerInfos[0].Certificate); + + byte[] sig2 = cms.SignerInfos[0].GetSignature(); + Assert.Equal(signature, sig2); + + if (detached) + { + Assert.Throws(() => cms.CheckSignature(true)); + cms = new SignedCms(contentInfo, detached); + cms.Decode(encoded); + } + + cms.CheckSignature(true); + } + [Theory] [InlineData(false, false)] [InlineData(false, true)] @@ -1611,6 +1687,21 @@ public static void Decode_CanDecodeWithAttributeCertificate() cms.CheckSignature(verifySignatureOnly: true); } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void ComputeSignature_SlhDsa_NoSignature() + { + ContentInfo contentInfo = new ContentInfo(new byte[] { 9, 8, 7, 6, 5 }); + SignedCms cms = new SignedCms(contentInfo, false); + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.GetCertificate()) + { + CmsSigner cmsSigner = new CmsSigner(SubjectIdentifierType.NoSignature, cert); + + AssertExtensions.ThrowsContains( + () => cms.ComputeSignature(cmsSigner), + "SignatureIdentifierType.NoSignature is not valid with the provided certificate."); + } + } + private static void CheckNoSignature(byte[] encoded, bool badOid=false) { SignedCms cms = new SignedCms(); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs index 2e1911ba11d3ea..faa41a19c7cb8b 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsTests.netcoreapp.cs @@ -4,7 +4,10 @@ using System.Collections.Generic; using System.Formats.Asn1; using System.Linq; +using System.Security.Cryptography.SLHDsa.Tests; using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.Unicode; using Test.Cryptography; using Xunit; @@ -101,6 +104,16 @@ public static void SignCmsUsingExplicitECDsaP521Key() } } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void SignCmsUsingExplicitSlhDsaKey() + { + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.TryGetCertificateWithPrivateKey()) + using (SlhDsa key = cert.GetSlhDsaPrivateKey()) + { + VerifyWithExplicitPrivateKey(cert, key); + } + } + [Fact] [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] public static void CounterSignCmsUsingExplicitRSAKeyForFirstSignerAndDSAForCounterSignature() @@ -139,6 +152,30 @@ public static void CounterSignCmsUsingExplicitECDsaKeyForFirstSignerAndRSAForCou } } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void CounterSignCmsUsingExplicitECDsaKeyForFirstSignerAndSlhDsaForCounterSignature() + { + using (X509Certificate2 cert = Certificates.ECDsaP256Win.TryGetCertificateWithPrivateKey()) + using (ECDsa key = cert.GetECDsaPrivateKey()) + using (X509Certificate2 counterSignerCert = Certificates.SlhDsaSha2_128s_Ietf.TryGetCertificateWithPrivateKey()) + using (SlhDsa counterSignerKey = counterSignerCert.GetSlhDsaPrivateKey()) + { + VerifyCounterSignatureWithExplicitPrivateKey(cert, key, counterSignerCert, counterSignerKey); + } + } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void CounterSignCmsUsingExplicitSlhDsaKeyForFirstSignerAndRSAForCounterSignature() + { + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.TryGetCertificateWithPrivateKey()) + using (SlhDsa key = cert.GetSlhDsaPrivateKey()) + using (X509Certificate2 counterSignerCert = Certificates.RSA2048SignatureOnly.TryGetCertificateWithPrivateKey()) + using (RSA counterSignerKey = counterSignerCert.GetRSAPrivateKey()) + { + VerifyCounterSignatureWithExplicitPrivateKey(cert, key, counterSignerCert, counterSignerKey); + } + } + [Fact] public static void SignCmsUsingRSACertAndECDsaKeyThrows() { @@ -189,6 +226,38 @@ public static void SignCmsUsingEDCSaCertAndRSAaKeyThrows() } } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void SignCmsUsingECDsaCertAndSlhDsaKeyThrows() + { + byte[] content = { 9, 8, 7, 6, 5 }; + + ContentInfo contentInfo = new ContentInfo(content); + SignedCms cms = new SignedCms(contentInfo, detached: false); + + using (X509Certificate2 cert = Certificates.ECDsaP256Win.GetCertificate()) + using (SlhDsa key = SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_128f)) + { + CmsSigner signer = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, cert, key); + Assert.Throws(() => cms.ComputeSignature(signer)); + } + } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void SignCmsUsingSlhDsaCertAndRSAKeyThrows() + { + byte[] content = { 9, 8, 7, 6, 5 }; + + ContentInfo contentInfo = new ContentInfo(content); + SignedCms cms = new SignedCms(contentInfo, detached: false); + + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.GetCertificate()) + using (RSA key = RSA.Create()) + { + CmsSigner signer = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, cert, key); + Assert.Throws(() => cms.ComputeSignature(signer)); + } + } + [Fact] public static void SignCmsUsingRSACertWithNotMatchingKeyThrows() { @@ -240,6 +309,22 @@ public static void SignCmsUsingECDsaCertWithNotMatchingKeyThrows() } } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void SignCmsUsingSlhDsaCertWithNotMatchingKeyThrows() + { + byte[] content = { 9, 8, 7, 6, 5 }; + + ContentInfo contentInfo = new ContentInfo(content); + SignedCms cms = new SignedCms(contentInfo, detached: false); + + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.GetCertificate()) + using (SlhDsa key = SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_128s)) + { + CmsSigner signer = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, cert, key); + Assert.Throws(() => cms.ComputeSignature(signer)); + } + } + [ConditionalFact(typeof(SignatureSupport), nameof(SignatureSupport.SupportsRsaSha1Signatures))] public static void AddCertificate() { @@ -471,6 +556,26 @@ public static void AddSigner_ECDSA_EphemeralKey() } } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void AddSigner_SlhDsa_EphemeralKey() + { + using (SlhDsa slhDsa = SlhDsa.ImportSlhDsaSecretKey(SlhDsaAlgorithm.SlhDsaSha2_128s, SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue)) + using (X509Certificate2 publicCertificate = Certificates.SlhDsaSha2_128s_Ietf.GetCertificate()) + using (X509Certificate2 certificateWithKey = Certificates.SlhDsaSha2_128s_Ietf.TryGetCertificateWithPrivateKey(exportable: true)) + { + using (X509Certificate2 certWithEphemeralKey = publicCertificate.CopyWithPrivateKey(slhDsa)) + { + ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 }); + SignedCms cms = new SignedCms(content, false); + CmsSigner signer = new CmsSigner(certWithEphemeralKey) + { + IncludeOption = X509IncludeOption.EndCertOnly + }; + cms.ComputeSignature(signer); + } + } + } + [Fact] public static void CreateSignature_DigestAlgorithmWithSignatureOid_Prohibited() { @@ -612,6 +717,40 @@ public static void CreateSignature_Ecdsa_ThrowsWithRsaSignaturePadding() } } + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void CreateSignature_SlhDsa_ThrowsWithRsaSignaturePadding() + { + ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 }); + SignedCms cms = new SignedCms(content); + + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.TryGetCertificateWithPrivateKey()) + { + CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert, null, RSASignaturePadding.Pss); + Assert.ThrowsAny(() => cms.ComputeSignature(signer)); + } + } + + [ConditionalTheory(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + [InlineData(Oids.RsaPkcs1Sha256)] + public static void ComputeSignature_SlhDsa_ThrowsWithUnsupportedHash(string hashAlgorithm) + { + ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 }); + SignedCms cms = new SignedCms(content); + + SlhDsa slhDsa = + SlhDsa.ImportSlhDsaSecretKey( + SlhDsaAlgorithm.SlhDsaSha2_128s, + SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue); + + using (slhDsa) + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.GetCertificate()) + { + CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert); + signer.DigestAlgorithm = new Oid(hashAlgorithm, null); + Assert.Throws(() => cms.ComputeSignature(signer)); + } + } + [Fact] public static void AddCertificate_CollectionContainsAttributeCertificate() { @@ -752,6 +891,33 @@ public static void ComputeSignature_Ecdsa_Sha3_Roundtrip(string hashAlgorithm) } } + [ConditionalTheory(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + [InlineData(Oids.Sha3_256)] + [InlineData(Oids.Sha3_384)] + [InlineData(Oids.Sha3_512)] + public static void ComputeSignature_SlhDsa_Roundtrip(string hashAlgorithm) + { + ContentInfo content = new ContentInfo(new byte[] { 1, 2, 3 }); + SignedCms cms = new SignedCms(content); + byte[] cmsBytes; + + using (X509Certificate2 cert = Certificates.SlhDsaSha2_128s_Ietf.TryGetCertificateWithPrivateKey()) + { + CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert); + signer.DigestAlgorithm = new Oid(hashAlgorithm, null); + + cms.ComputeSignature(signer); + cmsBytes = cms.Encode(); + cms = new SignedCms(); + cms.Decode(cmsBytes); + cms.CheckSignature(true); // Assert.NoThrow + Assert.Single(cms.SignerInfos); + + SignerInfo signerInfo = cms.SignerInfos[0]; + Assert.Equal(hashAlgorithm, signerInfo.DigestAlgorithm.Value); + } + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.DoesNotSupportSha3))] [InlineData(Oids.Sha3_256)] [InlineData(Oids.Sha3_384)] @@ -844,7 +1010,18 @@ public static void ExistingDocument_Ecdsa_Sha256_FromNetFX() Assert.Equal(Oids.EcPublicKey, signerInfo.SignatureAlgorithm.Value); } - private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, AsymmetricAlgorithm key) + private delegate CmsSigner CreateSignerFunc(SubjectIdentifierType sit, X509Certificate2 cert, TKey key); + private static CreateSignerFunc CreateAsymmetricAlgorithmSigner = (sit, cert, key) => + { + return new CmsSigner(sit, cert, key); + }; + + private static CreateSignerFunc CreateSlhDsaSigner = (sit, cert, key) => + { + return new CmsSigner(sit, cert, key); + }; + + private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, object key) { using (var pubCert = new X509Certificate2(cert.RawData)) { @@ -854,11 +1031,9 @@ private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, Asymmetr ContentInfo contentInfo = new ContentInfo(content); SignedCms cms = new SignedCms(contentInfo); - CmsSigner signer = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, pubCert, key) - { - IncludeOption = X509IncludeOption.EndCertOnly, - DigestAlgorithm = key is DSA ? new Oid(Oids.Sha1, Oids.Sha1) : new Oid(Oids.Sha256, Oids.Sha256) - }; + CmsSigner signer = CreateCmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, pubCert, key); + signer.IncludeOption = X509IncludeOption.EndCertOnly; + signer.DigestAlgorithm = key is DSA ? new Oid(Oids.Sha1, Oids.Sha1) : new Oid(Oids.Sha256, Oids.Sha256); cms.ComputeSignature(signer); cms.CheckSignature(true); @@ -868,7 +1043,7 @@ private static void VerifyWithExplicitPrivateKey(X509Certificate2 cert, Asymmetr } } - private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate2 cert, AsymmetricAlgorithm key, X509Certificate2 counterSignerCert, AsymmetricAlgorithm counterSignerKey) + private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate2 cert, object key, X509Certificate2 counterSignerCert, object counterSignerKey) { Assert.NotNull(key); Assert.NotNull(counterSignerKey); @@ -881,17 +1056,13 @@ private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate ContentInfo contentInfo = new ContentInfo(content); SignedCms cms = new SignedCms(contentInfo); - CmsSigner cmsSigner = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, pubCert, key) - { - IncludeOption = X509IncludeOption.EndCertOnly, - DigestAlgorithm = key is DSA ? new Oid(Oids.Sha1, Oids.Sha1) : new Oid(Oids.Sha256, Oids.Sha256) - }; + CmsSigner cmsSigner = CreateCmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, pubCert, key); + cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly; + cmsSigner.DigestAlgorithm = key is DSA ? new Oid(Oids.Sha1, Oids.Sha1) : new Oid(Oids.Sha256, Oids.Sha256); - CmsSigner cmsCounterSigner = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, counterSignerPubCert, counterSignerKey) - { - IncludeOption = X509IncludeOption.EndCertOnly, - DigestAlgorithm = counterSignerKey is DSA ? new Oid(Oids.Sha1, Oids.Sha1) : new Oid(Oids.Sha256, Oids.Sha256) - }; + CmsSigner cmsCounterSigner = CreateCmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, counterSignerPubCert, counterSignerKey); + cmsCounterSigner.IncludeOption = X509IncludeOption.EndCertOnly; + cmsCounterSigner.DigestAlgorithm = counterSignerKey is DSA ? new Oid(Oids.Sha1, Oids.Sha1) : new Oid(Oids.Sha256, Oids.Sha256); cms.ComputeSignature(cmsSigner); Assert.Equal(1, cms.SignerInfos.Count); @@ -905,6 +1076,16 @@ private static void VerifyCounterSignatureWithExplicitPrivateKey(X509Certificate } } + private static CmsSigner CreateCmsSigner(SubjectIdentifierType sit, X509Certificate2 cert, object key) + { + return key switch + { + AsymmetricAlgorithm asymmetricKey => new CmsSigner(sit, cert, asymmetricKey), + SlhDsa slhDsaKey => new CmsSigner(sit, cert, slhDsaKey), + _ => throw new NotSupportedException($"Unsupported key type: {key.GetType().Name}"), + }; + } + private static int CountCertificateChoices(byte[] encoded) { AsnReader reader = new AsnReader(encoded, AsnEncodingRules.BER); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs index c5b7c000e2b733..b1df0a8abaa3fa 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedCmsWholeDocumentTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Xml; using Test.Cryptography; using Xunit; @@ -615,5 +616,115 @@ public static void ReadRsaPkcs1DoubleCounterSigned() // Assert.NotThrows cms.CheckHash(); } + + [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] + public static void ReadSlhDsaDocument() + { + SignedCms cms = new SignedCms(); + cms.Decode(SignedDocuments.SlhDsa_Sha256_SignedDocument); + + Assert.Equal(1, cms.Version); + + ContentInfo contentInfo = cms.ContentInfo; + + Assert.Equal("1.2.840.113549.1.7.1", contentInfo.ContentType.Value); + AssertExtensions.SequenceEqual("Hello World!"u8, contentInfo.Content); + + X509Certificate2Collection certs = cms.Certificates; + Assert.Single(certs); + + X509Certificate2 topLevelCert = certs[0]; + Assert.Equal("Bogus SLH-DSA-SHA2-128s CA", topLevelCert.GetNameInfo(X509NameType.SimpleName, false)); + + Assert.Equal( + new DateTimeOffset(2024, 10, 16, 13, 42, 12, TimeSpan.Zero), + new DateTimeOffset(topLevelCert.NotBefore)); + + Assert.Equal( + new DateTimeOffset(2034, 10, 14, 13, 42, 12, TimeSpan.Zero), + new DateTimeOffset(topLevelCert.NotAfter)); + + SignerInfoCollection signers = cms.SignerInfos; + Assert.Single(signers); + + SignerInfo signer = signers[0]; + Assert.Equal(1, signer.Version); + Assert.Equal(SubjectIdentifierType.IssuerAndSerialNumber, signer.SignerIdentifier.Type); + + X509IssuerSerial issuerSerial = (X509IssuerSerial)signer.SignerIdentifier.Value; + Assert.Equal(topLevelCert.IssuerName.Name, issuerSerial.IssuerName); + Assert.Equal("438563A26901992C39CFBC40571B5FA3CCC78845", issuerSerial.SerialNumber); + Assert.Equal("2.16.840.1.101.3.4.2.1", signer.DigestAlgorithm.Value); +#if NET + Assert.Equal("2.16.840.1.101.3.4.3.20", signer.SignatureAlgorithm.Value); +#endif + + CryptographicAttributeObjectCollection signedAttrs = signer.SignedAttributes; + Assert.Equal(4, signedAttrs.Count); + + Assert.Equal("1.2.840.113549.1.9.3", signedAttrs[0].Oid.Value); + Assert.Equal("1.2.840.113549.1.9.5", signedAttrs[1].Oid.Value); + Assert.Equal("1.2.840.113549.1.9.4", signedAttrs[2].Oid.Value); + Assert.Equal("1.2.840.113549.1.9.15", signedAttrs[3].Oid.Value); + + Assert.Equal(1, signedAttrs[0].Values.Count); + Assert.Equal(1, signedAttrs[1].Values.Count); + Assert.Equal(1, signedAttrs[2].Values.Count); + Assert.Equal(1, signedAttrs[3].Values.Count); + + Pkcs9ContentType contentTypeAttr = (Pkcs9ContentType)signedAttrs[0].Values[0]; + Assert.Equal("1.2.840.113549.1.7.1", contentTypeAttr.ContentType.Value); + + Pkcs9SigningTime signingTimeAttr = (Pkcs9SigningTime)signedAttrs[1].Values[0]; + Assert.Equal( + new DateTimeOffset(2025, 5, 8, 7, 59, 35, TimeSpan.Zero), + new DateTimeOffset(signingTimeAttr.SigningTime)); + + Pkcs9MessageDigest messageDigestAttr = (Pkcs9MessageDigest)signedAttrs[2].Values[0]; + Assert.Equal( + "7F83B1657FF1FC53B92DC18148A1D65DFC2D4B1FA3D677284ADDD200126D9069", + messageDigestAttr.MessageDigest.ByteArrayToHex()); + + Assert.IsType(signedAttrs[3].Values[0]); +#if !NET + Assert.NotSame(signedAttrs[3].Oid, signedAttrs[3].Values[0].Oid); +#endif + Assert.Equal( + "306A300B060960864801650304012A300B0609608648016503040116300B0609" + + "608648016503040102300A06082A864886F70D0307300E06082A864886F70D03" + + "0202020080300D06082A864886F70D0302020140300706052B0E030207300D06" + + "082A864886F70D0302020128", + signedAttrs[3].Values[0].RawData.ByteArrayToHex()); + +#if NET + // Long signature so just check the end + AssertExtensions.TrueExpression( + signer.GetSignature().ByteArrayToHex().EndsWith("BB18DA1D24460D6528B66A")); +#endif + + CryptographicAttributeObjectCollection unsignedAttrs = signer.UnsignedAttributes; + Assert.Empty(unsignedAttrs); + + SignerInfoCollection counterSigners = signer.CounterSignerInfos; + Assert.Empty(counterSigners); + + X509Certificate2 signerCertificate = signer.Certificate; + Assert.Equal( + "O=Bogus SLH-DSA-SHA2-128s CA, L=Paris, C=FR", + signerCertificate.SubjectName.Name); + + // CheckHash always throws for certificate-based signers. + Assert.Throws(() => signer.CheckHash()); + + // Assert.NotThrows + signer.CheckSignature(true); + + // Since there are no NoSignature signers the document CheckHash will succeed. + // Assert.NotThrows + cms.CheckHash(); + + // Assert.NotThrows + cms.CheckSignature(true); + } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedDocuments.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedDocuments.cs index 06c739ef3ba6c0..1caf361c5b1290 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedDocuments.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignedDocuments.cs @@ -1822,5 +1822,302 @@ internal static class SignedDocuments "300B06072A8648CE3D020105000446304402203557687B26E650E4F86F4B" + "77A5BF5851350C96F01142696CC1391632CB95C3370220017FD4D9329F00" + "1EC74210CD34CAEE3878B2302602DB7930347E104679734291").HexToByteArray(); + + // produced with + // echo -n "Hello World!" | openssl cms -sign -signer ietfcert.pfx -md sha256 -nodetach -outform DER + internal static readonly byte[] SlhDsa_Sha256_SignedDocument = Convert.FromBase64String( + """ + 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 + DWUotmo= + """); } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignerInfoTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignerInfoTests.cs index bc000918a0081d..3881441e1594eb 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignerInfoTests.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/SignedCms/SignerInfoTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.SLHDsa.Tests; using System.Security.Cryptography.X509Certificates; using Test.Cryptography; using Xunit; @@ -829,6 +830,82 @@ public static void AddCounterSigner_ECDSA(SubjectIdentifierType identifierType, cms.CheckSignature(true); } + public static IEnumerable AddCounterSignerSlhDsaTestData => + from sit in new[] { SubjectIdentifierType.IssuerAndSerialNumber, SubjectIdentifierType.SubjectKeyIdentifier } + from algorithms in new (SlhDsaAlgorithm signAlgorithm, string hashAlgorithm)[] + { + (SlhDsaAlgorithm.SlhDsaSha2_128s, Oids.Sha256), + (SlhDsaAlgorithm.SlhDsaShake128f, Oids.Shake128), + (SlhDsaAlgorithm.SlhDsaSha2_256f, Oids.Sha512), + (SlhDsaAlgorithm.SlhDsaShake256f, Oids.Shake256), + } + from SlhDsaTestData.SlhDsaGeneratedKeyInfo info in SlhDsaTestData.GeneratedKeyInfosRaw + where info.Algorithm == algorithms.signAlgorithm // Find the matching test data for the algorithm + select new object[] { sit, algorithms.hashAlgorithm, info }; + + public static bool SlhDsaAndRsaSha1SignaturesSupported => + SignatureSupport.SupportsRsaSha1Signatures && SlhDsa.IsSupported; + + [ConditionalTheory(nameof(SlhDsaAndRsaSha1SignaturesSupported))] + [MemberData(nameof(AddCounterSignerSlhDsaTestData))] + public static void AddCounterSigner_SlhDsa(SubjectIdentifierType identifierType, string digestOid, SlhDsaTestData.SlhDsaGeneratedKeyInfo info) + { + SignedCms cms = new SignedCms(); + cms.Decode(SignedDocuments.RsaPkcs1OneSignerIssuerAndSerialNumber); + Assert.Single(cms.Certificates); + + SignerInfo firstSigner = cms.SignerInfos[0]; + Assert.Empty(firstSigner.CounterSignerInfos); + Assert.Empty(firstSigner.UnsignedAttributes); + + CertLoader loader = Certificates.SlhDsaGeneratedCerts.Single(cert => cert.CerData.SequenceEqual(info.Certificate)); + using (X509Certificate2 signerCert = loader.TryGetCertificateWithPrivateKey()) + { + CmsSigner signer = new CmsSigner(identifierType, signerCert); + signer.IncludeOption = X509IncludeOption.EndCertOnly; + signer.DigestAlgorithm = new Oid(digestOid, digestOid); + firstSigner.ComputeCounterSignature(signer); + } + + Assert.Empty(firstSigner.CounterSignerInfos); + Assert.Empty(firstSigner.UnsignedAttributes); + + SignerInfo firstSigner2 = cms.SignerInfos[0]; + Assert.Single(firstSigner2.CounterSignerInfos); + Assert.Single(firstSigner2.UnsignedAttributes); + + Assert.Single(cms.SignerInfos); + Assert.Equal(2, cms.Certificates.Count); + + SignerInfo counterSigner = firstSigner2.CounterSignerInfos[0]; + + int expectedVersion = identifierType == SubjectIdentifierType.IssuerAndSerialNumber ? 1 : 3; + Assert.Equal(expectedVersion, counterSigner.Version); + + // On .NET Framework there will be two attributes, because Windows emits the + // content-type attribute even for counter-signers. + int expectedCount = 1; +#if NETFRAMEWORK + expectedCount = 2; +#endif + Assert.Equal(expectedCount, counterSigner.SignedAttributes.Count); + Assert.Equal(Oids.MessageDigest, counterSigner.SignedAttributes[expectedCount - 1].Oid.Value); + + Assert.NotEqual(firstSigner2.Certificate, counterSigner.Certificate); + Assert.Equal(2, cms.Certificates.Count); + + byte[] signature = counterSigner.GetSignature(); + Assert.NotEmpty(signature); + + // SLH-DSA Oids are all under 2.16.840.1.101.3.4.3. + Assert.StartsWith("2.16.840.1.101.3.4.3.", counterSigner.SignatureAlgorithm.Value); + + cms.CheckSignature(true); + byte[] encoded = cms.Encode(); + cms.Decode(encoded); + cms.CheckSignature(true); + } + [ConditionalFact(typeof(SignatureSupport), nameof(SignatureSupport.SupportsRsaSha1Signatures))] public static void AddFirstCounterSigner_NoSignature_NoPrivateKey() { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj b/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj index ae18f9bf7a00a6..a7e844895504ab 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj @@ -1,10 +1,15 @@ - + true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetFrameworkCurrent) $(NoWarn);SYSLIB0057 + $(NoWarn);SYSLIB5006 + + + SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_128s), - () => SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_192f), - () => SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaShake256f), - ], - (cert, key) => cert.CopyWithPrivateKey(key), - cert => cert.GetSlhDsaPublicKey(), - cert => cert.GetSlhDsaPrivateKey(), - (priv, pub) => - { - byte[] data = new byte[RandomNumberGenerator.GetInt32(97)]; - RandomNumberGenerator.Fill(data); - - byte[] signature = priv.SignData(data); - Assert.True(pub.VerifyData(data, signature)); - }); - } - } - - [ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))] - public static void CheckCopyWithPrivateKey_SlhDsa_OtherSlhDsa() - { - string certPem = PemEncoding.WriteString("CERTIFICATE", SlhDsaTestData.IetfSlhDsaSha2_128sCertificate); - - using (X509Certificate2 pubOnly = X509Certificate2.CreateFromPem(certPem)) - { - using (SlhDsaMockImplementation publicSlhDsa = SlhDsaMockImplementation.Create(SlhDsaAlgorithm.SlhDsaSha2_128s)) - { - Exception e = new Exception("no secret key"); - publicSlhDsa.ExportSlhDsaSecretKeyCoreHook = _ => throw e; - publicSlhDsa.ExportSlhDsaPublicKeyCoreHook = (Span destination) => - SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue.CopyTo(destination); - - Assert.Same(e, AssertExtensions.Throws(() => pubOnly.CopyWithPrivateKey(publicSlhDsa))); - } - - SlhDsaMockImplementation privateSlhDsa = SlhDsaMockImplementation.Create(SlhDsaAlgorithm.SlhDsaSha2_128s); - privateSlhDsa.ExportSlhDsaPublicKeyCoreHook = (Span destination) => - SlhDsaTestData.IetfSlhDsaSha2_128sPublicKeyValue.CopyTo(destination); - privateSlhDsa.ExportSlhDsaSecretKeyCoreHook = (Span destination) => - SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue.CopyTo(destination); - - using (X509Certificate2 privCert = pubOnly.CopyWithPrivateKey(privateSlhDsa)) - { - AssertExtensions.TrueExpression(privCert.HasPrivateKey); - - using (SlhDsa certPrivateSlhDsa = privCert.GetSlhDsaPrivateKey()) - { - AssertExtensions.SequenceEqual( - SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue, - certPrivateSlhDsa.ExportSlhDsaSecretKey()); + private static partial Func CopyWithPrivateKey_SlhDsa => + (cert, key) => cert.CopyWithPrivateKey(key); - privateSlhDsa.Dispose(); - privateSlhDsa.ExportSlhDsaPublicKeyCoreHook = _ => Assert.Fail(); - privateSlhDsa.ExportSlhDsaSecretKeyCoreHook = _ => Assert.Fail(); + private static partial Func GetSlhDsaPublicKey => + cert => cert.GetSlhDsaPublicKey(); - // Ensure the key is actual a clone - AssertExtensions.SequenceEqual( - SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue, - certPrivateSlhDsa.ExportSlhDsaSecretKey()); - } - } - } - } + private static partial Func GetSlhDsaPrivateKey => + cert => cert.GetSlhDsaPrivateKey(); - private static void CheckCopyWithPrivateKey( + private static partial void CheckCopyWithPrivateKey( X509Certificate2 cert, X509Certificate2 wrongAlgorithmCert, TKey correctPrivateKey, @@ -933,70 +862,6 @@ private static void CheckCopyWithPrivateKey( Func getPublicKey, Func getPrivateKey, Action keyProver) - where TKey : class, IDisposable - { - Exception e = AssertExtensions.Throws( - null, - () => copyWithPrivateKey(wrongAlgorithmCert, correctPrivateKey)); - - Assert.Contains("algorithm", e.Message); - - List generatedKeys = new(); - - foreach (Func func in incorrectKeys) - { - TKey incorrectKey = func(); - generatedKeys.Add(incorrectKey); - - e = AssertExtensions.Throws( - "privateKey", - () => copyWithPrivateKey(cert, incorrectKey)); - - Assert.Contains("key does not match the public key for this certificate", e.Message); - } - - using (X509Certificate2 withKey = copyWithPrivateKey(cert, correctPrivateKey)) - { - e = AssertExtensions.Throws( - () => copyWithPrivateKey(withKey, correctPrivateKey)); - - Assert.Contains("already has an associated private key", e.Message); - - foreach (TKey incorrectKey in generatedKeys) - { - e = AssertExtensions.Throws( - () => copyWithPrivateKey(withKey, incorrectKey)); - - Assert.Contains("already has an associated private key", e.Message); - } - - using (TKey pub = getPublicKey(withKey)) - using (TKey pub2 = getPublicKey(withKey)) - using (TKey pubOnly = getPublicKey(cert)) - using (TKey priv = getPrivateKey(withKey)) - using (TKey priv2 = getPrivateKey(withKey)) - { - Assert.NotSame(pub, pub2); - Assert.NotSame(pub, pubOnly); - Assert.NotSame(pub2, pubOnly); - Assert.NotSame(priv, priv2); - - keyProver(priv, pub2); - keyProver(priv2, pub); - keyProver(priv, pubOnly); - - priv.Dispose(); - pub2.Dispose(); - - keyProver(priv2, pub); - keyProver(priv2, pubOnly); - } - } - - foreach (TKey incorrectKey in generatedKeys) - { - incorrectKey.Dispose(); - } - } + where TKey : class, IDisposable; } }