From dee594d925219b5152dcfbd444658fff6054c68d Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 24 Jun 2025 15:17:46 +0200 Subject: [PATCH 1/8] match ML-DSA export APIs with other PQC + Cose opt feedback --- .../src/System/Security/Cryptography/MLDsa.cs | 132 ++++++++++++++---- .../Cryptography/MLDsaImplementation.cs | 11 +- .../Cryptography/SlhDsaImplementation.cs | 1 + .../MLDsa/MLDsaTestsBase.cs | 39 ++---- .../tests/CoseTestHelpers.cs | 128 +++-------------- ...em.Security.Cryptography.Cose.Tests.csproj | 1 + .../tests/TestKeyRing.cs | 102 ++++++++++++++ .../ref/System.Security.Cryptography.cs | 9 +- .../MLDsaX509SignatureGenerator.cs | 4 +- .../X509Certificates/X509Certificate2.cs | 9 +- .../tests/X509Certificates/CertTests.cs | 3 +- .../PrivateKeyAssociationTests.cs | 19 +-- .../tests/X509Certificates/ExportTests.cs | 3 +- .../tests/X509Certificates/PfxTests.cs | 19 +-- 14 files changed, 270 insertions(+), 210 deletions(-) create mode 100644 src/libraries/System.Security.Cryptography.Cose/tests/TestKeyRing.cs diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs index 112b95c5b01960..13eb66e4faf6e1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs @@ -701,79 +701,151 @@ public string ExportEncryptedPkcs8PrivateKeyPem( /// /// Exports the public-key portion of the current key in the FIPS 204 public key format. /// - /// - /// The buffer to receive the public key. - /// /// - /// The number of bytes written to . + /// The FIPS 204 public key. /// + /// + /// An error occurred while exporting the key. + /// + /// The object has already been disposed. + public byte[] ExportMLDsaPublicKey() + { + ThrowIfDisposed(); + + byte[] destination = new byte[Algorithm.PublicKeySizeInBytes]; + ExportMLDsaPublicKeyCore(destination); + return destination; + } + + /// + /// Exports the public-key portion of the current key in the FIPS 204 public key format. + /// + /// + /// The buffer to receive the public key. Its length must be exactly + /// . + /// /// - /// is too small to hold the public key. + /// is the incorrect length to receive the public key. /// - public int ExportMLDsaPublicKey(Span destination) + /// + /// An error occurred while exporting the key. + /// + /// The object has already been disposed. + /// + /// is required to be exactly + /// in length. + /// + public void ExportMLDsaPublicKey(Span destination) { - if (destination.Length < Algorithm.PublicKeySizeInBytes) + int publicKeySizeInBytes = Algorithm.PublicKeySizeInBytes; + + if (destination.Length != publicKeySizeInBytes) { - throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + throw new ArgumentException( + SR.Format(SR.Argument_DestinationImprecise, publicKeySizeInBytes), + nameof(destination)); } ThrowIfDisposed(); - ExportMLDsaPublicKeyCore(destination.Slice(0, Algorithm.PublicKeySizeInBytes)); - return Algorithm.PublicKeySizeInBytes; + + ExportMLDsaPublicKeyCore(destination); } /// /// Exports the current key in the FIPS 204 secret key format. /// - /// - /// The buffer to receive the secret key. - /// /// - /// The number of bytes written to . + /// The FIPS 204 secret key. /// + /// + /// The current instance cannot export a secret key. + /// -or- + /// An error occurred while exporting the key. + /// + /// The object has already been disposed. + public byte[] ExportMLDsaSecretKey() + { + ThrowIfDisposed(); + + byte[] destination = new byte[Algorithm.SecretKeySizeInBytes]; + ExportMLDsaSecretKeyCore(destination); + return destination; + } + + /// + /// Exports the current key in the FIPS 204 secret key format. + /// + /// + /// The buffer to receive the secret key. Its length must be exactly + /// . + /// /// - /// is too small to hold the secret key. + /// is the incorrect length to receive the secret key. /// /// /// An error occurred while exporting the key. /// - public int ExportMLDsaSecretKey(Span destination) + public void ExportMLDsaSecretKey(Span destination) { - if (destination.Length < Algorithm.SecretKeySizeInBytes) + int secretKeySizeInBytes = Algorithm.SecretKeySizeInBytes; + + if (destination.Length != secretKeySizeInBytes) { - throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + throw new ArgumentException( + SR.Format(SR.Argument_DestinationImprecise, secretKeySizeInBytes), + nameof(destination)); } ThrowIfDisposed(); - ExportMLDsaSecretKeyCore(destination.Slice(0, Algorithm.SecretKeySizeInBytes)); - return Algorithm.SecretKeySizeInBytes; + + ExportMLDsaSecretKeyCore(destination); + } + + /// + /// Exports the private seed in the FIPS 204 private seed format. + /// + /// + /// The FIPS 204 private seed. + /// + /// + /// An error occurred while exporting the key. + /// + /// The object has already been disposed. + public byte[] ExportMLDsaPrivateSeed() + { + ThrowIfDisposed(); + + byte[] destination = new byte[Algorithm.PrivateSeedSizeInBytes]; + ExportMLDsaPrivateSeedCore(destination); + return destination; } /// /// Exports the private seed of the current key. /// /// - /// The buffer to receive the private seed. + /// The buffer to receive the private seed. Its length must be exactly + /// . /// - /// - /// The number of bytes written to . - /// /// - /// is too small to hold the private seed. + /// is the incorrect length to receive the private seed. /// /// /// An error occurred while exporting the private seed. /// - public int ExportMLDsaPrivateSeed(Span destination) + public void ExportMLDsaPrivateSeed(Span destination) { - if (destination.Length < Algorithm.PrivateSeedSizeInBytes) + int privateSeedSizeInBytes = Algorithm.PrivateSeedSizeInBytes; + if (destination.Length != privateSeedSizeInBytes) { - throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + throw new ArgumentException( + SR.Format(SR.Argument_DestinationImprecise, privateSeedSizeInBytes), + nameof(destination)); } ThrowIfDisposed(); - ExportMLDsaPrivateSeedCore(destination.Slice(0, Algorithm.PrivateSeedSizeInBytes)); - return Algorithm.PrivateSeedSizeInBytes; + + ExportMLDsaPrivateSeedCore(destination); } /// diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs index bb1f12ebd98284..de5788f12d515e 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs @@ -29,21 +29,20 @@ internal static MLDsaImplementation DuplicatePrivateKey(MLDsa key) MLDsaAlgorithm alg = key.Algorithm; byte[] rented = CryptoPool.Rent(alg.SecretKeySizeInBytes); - int written = 0; try { - written = key.ExportMLDsaPrivateSeed(rented); - return ImportSeed(alg, new ReadOnlySpan(rented, 0, written)); + key.ExportMLDsaPrivateSeed(rented); + return ImportSeed(alg, rented); } catch (CryptographicException) { - written = key.ExportMLDsaSecretKey(rented); - return ImportSecretKey(alg, new ReadOnlySpan(rented, 0, written)); + key.ExportMLDsaSecretKey(rented); + return ImportSecretKey(alg, rented); } finally { - CryptoPool.Return(rented, written); + CryptoPool.Return(rented); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/SlhDsaImplementation.cs b/src/libraries/Common/src/System/Security/Cryptography/SlhDsaImplementation.cs index 7ce51ec7f1e862..3f655e57815a05 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/SlhDsaImplementation.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/SlhDsaImplementation.cs @@ -27,6 +27,7 @@ internal static SlhDsaImplementation DuplicatePrivateKey(SlhDsa key) Span secretKey = (stackalloc byte[128])[..key.Algorithm.SecretKeySizeInBytes]; key.ExportSlhDsaSecretKey(secretKey); + try { return ImportSecretKey(key.Algorithm, secretKey); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs index 2675718198b45c..9d511eee563872 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs @@ -88,8 +88,7 @@ public void GenerateSignExportPublicVerifyWithPublicOnly(MLDsaAlgorithm algorith Assert.Equal(signature.Length, mldsa.SignData(data, signature)); AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); - publicKey = new byte[algorithm.PublicKeySizeInBytes]; - Assert.Equal(publicKey.Length, mldsa.ExportMLDsaPublicKey(publicKey)); + publicKey = mldsa.ExportMLDsaPublicKey(); } using (MLDsa mldsaPub = ImportPublicKey(algorithm, publicKey)) @@ -111,8 +110,7 @@ public void GenerateExportSecretKeySignAndVerify(MLDsaAlgorithm algorithm) signature = new byte[algorithm.SignatureSizeInBytes]; Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature)); - secretKey = new byte[algorithm.SecretKeySizeInBytes]; - Assert.Equal(secretKey.Length, mldsaTmp.ExportMLDsaSecretKey(secretKey)); + secretKey = mldsaTmp.ExportMLDsaSecretKey(); } using (MLDsa mldsa = ImportSecretKey(algorithm, secretKey)) @@ -141,8 +139,7 @@ public void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm) signature = new byte[algorithm.SignatureSizeInBytes]; Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature)); - privateSeed = new byte[algorithm.PrivateSeedSizeInBytes]; - Assert.Equal(privateSeed.Length, mldsaTmp.ExportMLDsaPrivateSeed(privateSeed)); + privateSeed = mldsaTmp.ExportMLDsaPrivateSeed(); } using (MLDsa mldsa = ImportPrivateSeed(algorithm, privateSeed)) @@ -159,10 +156,10 @@ public void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm) [Fact] public void ImportSecretKey_CannotReconstructSeed() { - byte[] secretKey = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes]; + byte[] secretKey; using (MLDsa mldsaOriginal = GenerateKey(MLDsaAlgorithm.MLDsa44)) { - Assert.Equal(secretKey.Length, mldsaOriginal.ExportMLDsaSecretKey(secretKey)); + secretKey = mldsaOriginal.ExportMLDsaSecretKey(); } using (MLDsa mldsa = ImportSecretKey(MLDsaAlgorithm.MLDsa44, secretKey)) @@ -174,21 +171,18 @@ public void ImportSecretKey_CannotReconstructSeed() [Fact] public void ImportSeed_CanReconstructSecretKey() { - byte[] secretKey = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes]; - byte[] seed = new byte[MLDsaAlgorithm.MLDsa44.PrivateSeedSizeInBytes]; + byte[] secretKey; + byte[] seed; using (MLDsa mldsaOriginal = GenerateKey(MLDsaAlgorithm.MLDsa44)) { - Assert.Equal(secretKey.Length, mldsaOriginal.ExportMLDsaSecretKey(secretKey)); - Assert.Equal(seed.Length, mldsaOriginal.ExportMLDsaPrivateSeed(seed)); + secretKey = mldsaOriginal.ExportMLDsaSecretKey(); + seed = mldsaOriginal.ExportMLDsaPrivateSeed(); } using (MLDsa mldsa = ImportPrivateSeed(MLDsaAlgorithm.MLDsa44, seed)) { - byte[] secretKey2 = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes]; - byte[] seed2 = new byte[MLDsaAlgorithm.MLDsa44.PrivateSeedSizeInBytes]; - - Assert.Equal(secretKey2.Length, mldsa.ExportMLDsaSecretKey(secretKey2)); - Assert.Equal(seed2.Length, mldsa.ExportMLDsaPrivateSeed(seed2)); + byte[] secretKey2 = mldsa.ExportMLDsaSecretKey(); + byte[] seed2 = mldsa.ExportMLDsaPrivateSeed(); AssertExtensions.SequenceEqual(secretKey, secretKey2); AssertExtensions.SequenceEqual(seed, seed2); @@ -209,15 +203,12 @@ public void NistImportSecretKeyVerifyExportsAndSignature(MLDsaNistTestCase testC { using MLDsa mldsa = ImportSecretKey(testCase.Algorithm, testCase.SecretKey); - byte[] pubKey = new byte[testCase.Algorithm.PublicKeySizeInBytes]; - Assert.Equal(pubKey.Length, mldsa.ExportMLDsaPublicKey(pubKey)); + byte[] pubKey = mldsa.ExportMLDsaPublicKey(); AssertExtensions.SequenceEqual(testCase.PublicKey, pubKey); - byte[] secretKey = new byte[testCase.Algorithm.SecretKeySizeInBytes]; - Assert.Equal(secretKey.Length, mldsa.ExportMLDsaSecretKey(secretKey)); - - byte[] seed = new byte[testCase.Algorithm.PrivateSeedSizeInBytes]; - Assert.Throws(() => mldsa.ExportMLDsaPrivateSeed(seed)); + byte[] secretKey = mldsa.ExportMLDsaSecretKey(); + AssertExtensions.SequenceEqual(testCase.SecretKey, secretKey); + Assert.Throws(() => mldsa.ExportMLDsaPrivateSeed()); Assert.Equal(testCase.ShouldPass, mldsa.VerifyData(testCase.Message, testCase.Signature, testCase.Context)); } diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs index 5537532205105e..30f8b4e9a088f0 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs @@ -464,123 +464,30 @@ private static void AssertHeaders(CborReader reader, List<(CoseHeaderLabel, Read } } - private static ECParameters _ec256Parameters = CreateECParameters("nistP256", "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8", "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4", "V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM"); - private static ECParameters _ec384Parameters = CreateECParameters("nistP384", "kTJyP2KSsBBhnb4kjWmMF7WHVsY55xUPgb7k64rDcjatChoZ1nvjKmYmPh5STRKc", "mM0weMVU2DKsYDxDJkEP9hZiRZtB8fPfXbzINZj_fF7YQRynNWedHEyzAJOX2e8s", "ok3Nq97AXlpEusO7jIy1FZATlBP9PNReMU7DWbkLQ5dU90snHuuHVDjEPmtV0fTo"); - private static ECParameters _ec512Parameters = CreateECParameters("nistP521", "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"); - - [ThreadStatic] - private static ECDsa? t_es256; - [ThreadStatic] - private static ECDsa? t_es384; [ThreadStatic] - private static ECDsa? t_es512; + private static TestKeyRing? t_threadKeys; + private static TestKeyRing ThreadKeys => (t_threadKeys ??= new TestKeyRing()); - internal static ECDsa ES256 => t_es256 ??= CreateECDsa(_ec256Parameters, true); - internal static ECDsa ES384 => t_es384 ??= CreateECDsa(_ec384Parameters, true); - internal static ECDsa ES512 => t_es512 ??= CreateECDsa(_ec512Parameters, true); - - [ThreadStatic] - private static ECDsa? t_es256WithoutPrivateKey; - [ThreadStatic] - private static ECDsa? t_es384WithoutPrivateKey; - [ThreadStatic] - private static ECDsa? t_es512WithoutPrivateKey; + internal static ECDsa ES256 => ThreadKeys.ES256; + internal static ECDsa ES384 => ThreadKeys.ES384; + internal static ECDsa ES512 => ThreadKeys.ES512; - private static ECDsa ES256WithoutPrivateKey => t_es256WithoutPrivateKey ??= CreateECDsa(_ec256Parameters, false); - private static ECDsa ES384WithoutPrivateKey => t_es384WithoutPrivateKey ??= CreateECDsa(_ec384Parameters, false); - private static ECDsa ES512WithoutPrivateKey => t_es512WithoutPrivateKey ??= CreateECDsa(_ec512Parameters, false); + internal static ECDsa ES256WithoutPrivateKey => ThreadKeys.ES256WithoutPrivateKey; + internal static ECDsa ES384WithoutPrivateKey => ThreadKeys.ES384WithoutPrivateKey; + internal static ECDsa ES512WithoutPrivateKey => ThreadKeys.ES512WithoutPrivateKey; internal static ECDsa DefaultKey => ES256; internal static HashAlgorithmName DefaultHash { get; } = HashAlgorithmName.SHA256; - [ThreadStatic] - internal static RSA? t_rsaKey; - [ThreadStatic] - internal static RSA? t_rsaKeyWithoutPrivateKey; - - internal static RSA RSAKey => t_rsaKey ??= CreateRSA(true); - internal static RSA RSAKeyWithoutPrivateKey => t_rsaKeyWithoutPrivateKey ??= CreateRSA(false); - - [ThreadStatic] - private static MLDsa? t_mldsa44Key; - [ThreadStatic] - private static MLDsa? t_mldsa44KeyWithoutPrivateKey; - [ThreadStatic] - private static MLDsa? t_mldsa65Key; - [ThreadStatic] - private static MLDsa? t_mldsa65KeyWithoutPrivateKey; - [ThreadStatic] - private static MLDsa? t_mldsa87Key; - [ThreadStatic] - private static MLDsa? t_mldsa87KeyWithoutPrivateKey; - - internal static MLDsa MLDsa44Key => t_mldsa44Key ??= CreateMLDsa(MLDsaAlgorithm.MLDsa44, true); - internal static MLDsa MLDsa44KeyWithoutPrivateKey => t_mldsa44KeyWithoutPrivateKey ??= CreateMLDsa(MLDsaAlgorithm.MLDsa44, false); - internal static MLDsa MLDsa65Key => t_mldsa65Key ??= CreateMLDsa(MLDsaAlgorithm.MLDsa65, true); - internal static MLDsa MLDsa65KeyWithoutPrivateKey => t_mldsa65KeyWithoutPrivateKey ??= CreateMLDsa(MLDsaAlgorithm.MLDsa65, false); - internal static MLDsa MLDsa87Key => t_mldsa87Key ??= CreateMLDsa(MLDsaAlgorithm.MLDsa87, true); - internal static MLDsa MLDsa87KeyWithoutPrivateKey => t_mldsa87KeyWithoutPrivateKey ??= CreateMLDsa(MLDsaAlgorithm.MLDsa87, false); - - private static ECParameters CreateECParameters(string curveFriendlyName, string base64UrlQx, string base64UrlQy, string base64UrlPrivateKey) - { - return new ECParameters() - { - Curve = ECCurve.CreateFromFriendlyName(curveFriendlyName), - Q = new ECPoint - { - X = Base64UrlEncoder.DecodeBytes(base64UrlQx), - Y = Base64UrlEncoder.DecodeBytes(base64UrlQy), - }, - D = Base64UrlEncoder.DecodeBytes(base64UrlPrivateKey), - }; - } - - private static ECDsa CreateECDsa(ECParameters parameters, bool includePrivateKey) - { - ECParameters parametersLocalCopy = parameters; - if (!includePrivateKey) - { - parametersLocalCopy.D = null; - } - - return ECDsa.Create(parametersLocalCopy); - } - - private static RSA CreateRSA(bool includePrivateKey) - { - var rsaParameters = new RSAParameters - { - Modulus = ByteUtils.HexToByteArray("BC7E29D0DF7E20CC9DC8D509E0F68895922AF0EF452190D402C61B554334A7BF91C9A570240F994FAE1B69035BCFAD4F7E249EB26087C2665E7C958C967B1517413DC3F97A431691A5999B257CC6CD356BAD168D929B8BAE9020750E74CF60F6FD35D6BB3FC93FC28900478694F508B33E7C00E24F90EDF37457FC3E8EFCFD2F42306301A8205AB740515331D5C18F0C64D4A43BE52FC440400F6BFC558A6E32884C2AF56F29E5C52780CEA7285F5C057FC0DFDA232D0ADA681B01495D9D0E32196633588E289E59035FF664F056189F2F10FE05827B796C326E3E748FFA7C589ED273C9C43436CDDB4A6A22523EF8BCB2221615B799966F1ABA5BC84B7A27CF"), - Exponent = ByteUtils.HexToByteArray("010001"), - D = ByteUtils.HexToByteArray("0969FF04FCC1E1647C20402CF3F736D4CAE33F264C1C6EE3252CFCC77CDEF533D700570AC09A50D7646EDFB1F86A13BCABCF00BD659F27813D08843597271838BC46ED4743FE741D9BC38E0BF36D406981C7B81FCE54861CEBFB85AD23A8B4833C1BEE18C05E4E436A869636980646EECB839E4DAF434C9C6DFBF3A55CE1DB73E4902F89384BD6F9ECD3399FB1ED4B83F28D356C8E619F1F0DC96BBE8B75C1812CA58F360259EAEB1D17130C3C0A2715A99BE49898E871F6088A29570DC2FFA0CEFFFA27F1F055CBAABFD8894E0CC24F176E34EBAD32278A466F8A34A685ACC8207D9EC1FCBBD094996DC73C6305FCA31668BE57B1699D0BB456CC8871BFFBCD"), - P = ByteUtils.HexToByteArray("F331593E147FD3A3235675F0D36A06E5426F7C5E78E49B2ACD3E268BA50E48ED2A52F3B4FA492D6BCF70EB3F915A716078A113652E3FA4C6D50AF8606C2D2C28ECAF083B712D6CEE1263C1205DA03BBBFA6F5C2D8B1A96194089CACB306C844A832E2B032B5F96A7EAB6CFE1107299013C8B0E9F089BBABBC504DD8BC138BA4B"), - Q = ByteUtils.HexToByteArray("C66B5DDCAB7017E14083F2854F61997F35636C86F2F92B172D2555588EE1ED899BA6B6ADEC0A02024B2E78A91C891256A8571E0EFB3BAC3F41724DE036EC8FA0F93E2CFBDDA59C6FF1816EB3DC938D4E45912423F3F34B7E96C39E2E4D65A3DCD6DFD2B4EF527841001272F77855B6D75D40D54BB65BD1DF8538E96EC4DAD60D"), - DP = ByteUtils.HexToByteArray("1F677CFDBE49EF7B7EA1B8A33BB9D260229F20F1562D373864BEA4DD9D97E5A4F2B53991624CB6D7D836DDBA1CBC102E0405D0EA5CF98CFEBC1E298AD20D5749859EE8B23C604053D1FE1DBF5F37C4DEF66D10FB349E5F49AD82DDB435719DF7BD4EE5F107D5D52FA3E8AD9983B538BAE72591E2C98ACAA75ABED1192DFF7457"), - DQ = ByteUtils.HexToByteArray("2CCC9F13ACCD9146B57755318E3BBE197FA7642090097C162E86485FC75AF173E965D9C7290D1569092A83E9C2DC9BFC5EE3D490935EE4C41F75BC698C5D1B0CC059AE746B95F1DD408CF5BEBC65C038D4F23153C0C7C4DADF1569C890870B5958568ECF755D8C73389DF1C138353A242414F853B0E7C85A0C4D4E3F4949139D"), - InverseQ = ByteUtils.HexToByteArray("7B6E2406FD03BC75EA22AB94A8D242506A6BBFE36BC8132DBBCE50B8425425062B697AFA180F5685E90E11EB5712D2E6E2B24E2A1E7C75D5940E08301E824470EF38561BE3E9D05F9FCA8E6F69A028A928E85E58212E789BA577B80378D7A995FA6AFEA74BE364661A679F82776C5905F43F7A35692986271E594E1D11F9668D"), - }; - - if (!includePrivateKey) - { - return RSA.Create(new RSAParameters { Modulus = rsaParameters.Modulus, Exponent = rsaParameters.Exponent }); - } - - return RSA.Create(rsaParameters); - } - - private static MLDsa CreateMLDsa(MLDsaAlgorithm algorithm, bool includePrivateKey) - { - MLDsaNistTestCase nistKey = MLDsaTestsData.GetPassingNistTestCase(algorithm); + internal static RSA RSAKey => ThreadKeys.RSAKey; + internal static RSA RSAKeyWithoutPrivateKey => ThreadKeys.RSAKeyWithoutPrivateKey; - if (includePrivateKey) - { - return MLDsa.ImportMLDsaSecretKey(algorithm, nistKey.SecretKey); - } - else - { - return MLDsa.ImportMLDsaPublicKey(algorithm, nistKey.PublicKey); - } - } + internal static MLDsa MLDsa44Key => ThreadKeys.MLDsa44Key; + internal static MLDsa MLDsa44KeyWithoutPrivateKey => ThreadKeys.MLDsa44KeyWithoutPrivateKey; + internal static MLDsa MLDsa65Key => ThreadKeys.MLDsa65Key; + internal static MLDsa MLDsa65KeyWithoutPrivateKey => ThreadKeys.MLDsa65KeyWithoutPrivateKey; + internal static MLDsa MLDsa87Key => ThreadKeys.MLDsa87Key; + internal static MLDsa MLDsa87KeyWithoutPrivateKey => ThreadKeys.MLDsa87KeyWithoutPrivateKey; internal static bool AlgorithmNeedsHashAlgorithm(CoseAlgorithm algorithm) => algorithm is @@ -596,7 +503,8 @@ internal static bool AlgorithmIsSupported(CoseAlgorithm algorithm) case CoseAlgorithm.MLDsa65: case CoseAlgorithm.MLDsa87: return MLDsa.IsSupported; - default: return true; + default: + return true; } } diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/System.Security.Cryptography.Cose.Tests.csproj b/src/libraries/System.Security.Cryptography.Cose/tests/System.Security.Cryptography.Cose.Tests.csproj index 5d1346e348d573..c7bd4df0c7f3d6 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/System.Security.Cryptography.Cose.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cose/tests/System.Security.Cryptography.Cose.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/TestKeyRing.cs b/src/libraries/System.Security.Cryptography.Cose/tests/TestKeyRing.cs new file mode 100644 index 00000000000000..ede7573f555ba9 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Cose/tests/TestKeyRing.cs @@ -0,0 +1,102 @@ +// 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.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Formats.Cbor; +using System.IO; +using System.Linq; +using System.Security.Cryptography.Tests; +using Microsoft.IdentityModel.Tokens; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Cose.Tests +{ + public class TestKeyRing + { + private static ECParameters _ec256Parameters = CreateECParameters("nistP256", "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8", "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4", "V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM"); + private static ECParameters _ec384Parameters = CreateECParameters("nistP384", "kTJyP2KSsBBhnb4kjWmMF7WHVsY55xUPgb7k64rDcjatChoZ1nvjKmYmPh5STRKc", "mM0weMVU2DKsYDxDJkEP9hZiRZtB8fPfXbzINZj_fF7YQRynNWedHEyzAJOX2e8s", "ok3Nq97AXlpEusO7jIy1FZATlBP9PNReMU7DWbkLQ5dU90snHuuHVDjEPmtV0fTo"); + private static ECParameters _ec512Parameters = CreateECParameters("nistP521", "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"); + + internal ECDsa ES256 => field ??= CreateECDsa(_ec256Parameters, true); + internal ECDsa ES384 => field ??= CreateECDsa(_ec384Parameters, true); + internal ECDsa ES512 => field ??= CreateECDsa(_ec512Parameters, true); + + internal ECDsa ES256WithoutPrivateKey => field ??= CreateECDsa(_ec256Parameters, false); + internal ECDsa ES384WithoutPrivateKey => field ??= CreateECDsa(_ec384Parameters, false); + internal ECDsa ES512WithoutPrivateKey => field ??= CreateECDsa(_ec512Parameters, false); + + internal RSA RSAKey => field ??= CreateRSA(true); + internal RSA RSAKeyWithoutPrivateKey => field ??= CreateRSA(false); + + internal MLDsa MLDsa44Key => field ??= CreateMLDsa(MLDsaAlgorithm.MLDsa44, true); + internal MLDsa MLDsa44KeyWithoutPrivateKey => field ??= CreateMLDsa(MLDsaAlgorithm.MLDsa44, false); + internal MLDsa MLDsa65Key => field ??= CreateMLDsa(MLDsaAlgorithm.MLDsa65, true); + internal MLDsa MLDsa65KeyWithoutPrivateKey => field ??= CreateMLDsa(MLDsaAlgorithm.MLDsa65, false); + internal MLDsa MLDsa87Key => field ??= CreateMLDsa(MLDsaAlgorithm.MLDsa87, true); + internal MLDsa MLDsa87KeyWithoutPrivateKey => field ??= CreateMLDsa(MLDsaAlgorithm.MLDsa87, false); + + private static ECParameters CreateECParameters(string curveFriendlyName, string base64UrlQx, string base64UrlQy, string base64UrlPrivateKey) + { + return new ECParameters() + { + Curve = ECCurve.CreateFromFriendlyName(curveFriendlyName), + Q = new ECPoint + { + X = Base64UrlEncoder.DecodeBytes(base64UrlQx), + Y = Base64UrlEncoder.DecodeBytes(base64UrlQy), + }, + D = Base64UrlEncoder.DecodeBytes(base64UrlPrivateKey), + }; + } + + private static ECDsa CreateECDsa(ECParameters parameters, bool includePrivateKey) + { + ECParameters parametersLocalCopy = parameters; + if (!includePrivateKey) + { + parametersLocalCopy.D = null; + } + + return ECDsa.Create(parametersLocalCopy); + } + + private static RSA CreateRSA(bool includePrivateKey) + { + var rsaParameters = new RSAParameters + { + Modulus = ByteUtils.HexToByteArray("BC7E29D0DF7E20CC9DC8D509E0F68895922AF0EF452190D402C61B554334A7BF91C9A570240F994FAE1B69035BCFAD4F7E249EB26087C2665E7C958C967B1517413DC3F97A431691A5999B257CC6CD356BAD168D929B8BAE9020750E74CF60F6FD35D6BB3FC93FC28900478694F508B33E7C00E24F90EDF37457FC3E8EFCFD2F42306301A8205AB740515331D5C18F0C64D4A43BE52FC440400F6BFC558A6E32884C2AF56F29E5C52780CEA7285F5C057FC0DFDA232D0ADA681B01495D9D0E32196633588E289E59035FF664F056189F2F10FE05827B796C326E3E748FFA7C589ED273C9C43436CDDB4A6A22523EF8BCB2221615B799966F1ABA5BC84B7A27CF"), + Exponent = ByteUtils.HexToByteArray("010001"), + D = ByteUtils.HexToByteArray("0969FF04FCC1E1647C20402CF3F736D4CAE33F264C1C6EE3252CFCC77CDEF533D700570AC09A50D7646EDFB1F86A13BCABCF00BD659F27813D08843597271838BC46ED4743FE741D9BC38E0BF36D406981C7B81FCE54861CEBFB85AD23A8B4833C1BEE18C05E4E436A869636980646EECB839E4DAF434C9C6DFBF3A55CE1DB73E4902F89384BD6F9ECD3399FB1ED4B83F28D356C8E619F1F0DC96BBE8B75C1812CA58F360259EAEB1D17130C3C0A2715A99BE49898E871F6088A29570DC2FFA0CEFFFA27F1F055CBAABFD8894E0CC24F176E34EBAD32278A466F8A34A685ACC8207D9EC1FCBBD094996DC73C6305FCA31668BE57B1699D0BB456CC8871BFFBCD"), + P = ByteUtils.HexToByteArray("F331593E147FD3A3235675F0D36A06E5426F7C5E78E49B2ACD3E268BA50E48ED2A52F3B4FA492D6BCF70EB3F915A716078A113652E3FA4C6D50AF8606C2D2C28ECAF083B712D6CEE1263C1205DA03BBBFA6F5C2D8B1A96194089CACB306C844A832E2B032B5F96A7EAB6CFE1107299013C8B0E9F089BBABBC504DD8BC138BA4B"), + Q = ByteUtils.HexToByteArray("C66B5DDCAB7017E14083F2854F61997F35636C86F2F92B172D2555588EE1ED899BA6B6ADEC0A02024B2E78A91C891256A8571E0EFB3BAC3F41724DE036EC8FA0F93E2CFBDDA59C6FF1816EB3DC938D4E45912423F3F34B7E96C39E2E4D65A3DCD6DFD2B4EF527841001272F77855B6D75D40D54BB65BD1DF8538E96EC4DAD60D"), + DP = ByteUtils.HexToByteArray("1F677CFDBE49EF7B7EA1B8A33BB9D260229F20F1562D373864BEA4DD9D97E5A4F2B53991624CB6D7D836DDBA1CBC102E0405D0EA5CF98CFEBC1E298AD20D5749859EE8B23C604053D1FE1DBF5F37C4DEF66D10FB349E5F49AD82DDB435719DF7BD4EE5F107D5D52FA3E8AD9983B538BAE72591E2C98ACAA75ABED1192DFF7457"), + DQ = ByteUtils.HexToByteArray("2CCC9F13ACCD9146B57755318E3BBE197FA7642090097C162E86485FC75AF173E965D9C7290D1569092A83E9C2DC9BFC5EE3D490935EE4C41F75BC698C5D1B0CC059AE746B95F1DD408CF5BEBC65C038D4F23153C0C7C4DADF1569C890870B5958568ECF755D8C73389DF1C138353A242414F853B0E7C85A0C4D4E3F4949139D"), + InverseQ = ByteUtils.HexToByteArray("7B6E2406FD03BC75EA22AB94A8D242506A6BBFE36BC8132DBBCE50B8425425062B697AFA180F5685E90E11EB5712D2E6E2B24E2A1E7C75D5940E08301E824470EF38561BE3E9D05F9FCA8E6F69A028A928E85E58212E789BA577B80378D7A995FA6AFEA74BE364661A679F82776C5905F43F7A35692986271E594E1D11F9668D"), + }; + + if (!includePrivateKey) + { + return RSA.Create(new RSAParameters { Modulus = rsaParameters.Modulus, Exponent = rsaParameters.Exponent }); + } + + return RSA.Create(rsaParameters); + } + + private static MLDsa CreateMLDsa(MLDsaAlgorithm algorithm, bool includePrivateKey) + { + MLDsaNistTestCase nistKey = MLDsaTestsData.GetPassingNistTestCase(algorithm); + + if (includePrivateKey) + { + return MLDsa.ImportMLDsaSecretKey(algorithm, nistKey.SecretKey); + } + else + { + return MLDsa.ImportMLDsaPublicKey(algorithm, nistKey.PublicKey); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index d9c5fb43a079a9..f3bfc940b1dbe0 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -1819,11 +1819,14 @@ protected virtual void Dispose(bool disposing) { } public string ExportEncryptedPkcs8PrivateKeyPem(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } public string ExportEncryptedPkcs8PrivateKeyPem(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } public string ExportEncryptedPkcs8PrivateKeyPem(string password, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } - public int ExportMLDsaPrivateSeed(System.Span destination) { throw null; } + public byte[] ExportMLDsaPrivateSeed() { throw null; } + public void ExportMLDsaPrivateSeed(System.Span destination) { } protected abstract void ExportMLDsaPrivateSeedCore(System.Span destination); - public int ExportMLDsaPublicKey(System.Span destination) { throw null; } + public byte[] ExportMLDsaPublicKey() { throw null; } + public void ExportMLDsaPublicKey(System.Span destination) { } protected abstract void ExportMLDsaPublicKeyCore(System.Span destination); - public int ExportMLDsaSecretKey(System.Span destination) { throw null; } + public byte[] ExportMLDsaSecretKey() { throw null; } + public void ExportMLDsaSecretKey(System.Span destination) { } protected abstract void ExportMLDsaSecretKeyCore(System.Span destination); public byte[] ExportPkcs8PrivateKey() { throw null; } public string ExportPkcs8PrivateKeyPem() { throw null; } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs index bc5d15737ea833..d40cb4b541a32b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs @@ -52,9 +52,7 @@ public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) protected override PublicKey BuildPublicKey() { Oid oid = new Oid(_key.Algorithm.Oid, null); - byte[] pkBytes = new byte[_key.Algorithm.PublicKeySizeInBytes]; - int written = _key.ExportMLDsaPublicKey(pkBytes); - Debug.Assert(written == pkBytes.Length); + byte[] pkBytes = _key.ExportMLDsaPublicKey(); return new PublicKey( oid, diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs index f99ae1c64bf5f8..ecd9cd58095cf8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs @@ -966,13 +966,10 @@ public X509Certificate2 CopyWithPrivateKey(MLDsa privateKey) throw new ArgumentException(SR.Cryptography_PrivateKey_DoesNotMatch, nameof(privateKey)); } - byte[] pk1 = new byte[publicKey.Algorithm.PublicKeySizeInBytes]; - byte[] pk2 = new byte[pk1.Length]; + byte[] pk1 = publicKey.ExportMLDsaPublicKey(); + byte[] pk2 = privateKey.ExportMLDsaPublicKey(); - int w1 = publicKey.ExportMLDsaPublicKey(pk1); - int w2 = privateKey.ExportMLDsaPublicKey(pk2); - - if (w1 != w2 || !pk1.AsSpan().SequenceEqual(pk2)) + if (pk1.Length != pk2.Length || !pk1.AsSpan().SequenceEqual(pk2)) { throw new ArgumentException(SR.Cryptography_PrivateKey_DoesNotMatch, nameof(privateKey)); } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs index c6d6aabab01bde..425cd1ed94b3bd 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs @@ -153,8 +153,7 @@ public static void PrivateKey_FromCertificate_CanExportPrivate_MLDsa() { Assert.NotNull(certKey); byte[] expectedKey = MLDsaTestsData.IetfMLDsa44.SecretKey; - byte[] actualKey = new byte[MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes]; - Assert.Equal(MLDsaAlgorithm.MLDsa44.SecretKeySizeInBytes, certKey.ExportMLDsaSecretKey(actualKey)); + byte[] actualKey = certKey.ExportMLDsaSecretKey(); AssertExtensions.SequenceEqual(expectedKey, actualKey); } } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs index 34d3065b494ed2..110436984ffbab 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs @@ -715,8 +715,7 @@ public static void GetMLDsaPublicKeyTest() using (MLDsa? certKey = GetMLDsaPublicKey(cert)) { Assert.NotNull(certKey); - byte[] publicKey = new byte[certKey.Algorithm.PublicKeySizeInBytes]; - Assert.Equal(publicKey.Length, certKey.ExportMLDsaPublicKey(publicKey)); + byte[] publicKey = certKey.ExportMLDsaPublicKey(); AssertExtensions.SequenceEqual(MLDsaTestsData.IetfMLDsa44.PublicKey, publicKey); // Verify the key is not actually private @@ -729,8 +728,7 @@ public static void GetMLDsaPublicKeyTest() using (MLDsa? certKey = GetMLDsaPublicKey(cert)) { Assert.NotNull(certKey); - byte[] publicKey = new byte[certKey.Algorithm.PublicKeySizeInBytes]; - Assert.Equal(publicKey.Length, certKey.ExportMLDsaPublicKey(publicKey)); + byte[] publicKey = certKey.ExportMLDsaPublicKey(); AssertExtensions.SequenceEqual(MLDsaTestsData.IetfMLDsa44.PublicKey, publicKey); // Verify the key is not actually private @@ -759,8 +757,7 @@ public static void GetMLDsaPrivateKeyTest() Assert.NotNull(certKey); // Verify the key is actually private - byte[] privateSeed = new byte[certKey.Algorithm.PrivateSeedSizeInBytes]; - Assert.Equal(privateSeed.Length, certKey.ExportMLDsaPrivateSeed(privateSeed)); + byte[] privateSeed = certKey.ExportMLDsaPrivateSeed(); AssertExtensions.SequenceEqual( MLDsaTestsData.IetfMLDsa44.PrivateSeed, privateSeed); @@ -857,8 +854,7 @@ public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_SecretKey() using (MLDsa certPrivateMLDsa = GetMLDsaPrivateKey(privCert)) { - byte[] secretKey = new byte[certPrivateMLDsa.Algorithm.SecretKeySizeInBytes]; - Assert.Equal(secretKey.Length, certPrivateMLDsa.ExportMLDsaSecretKey(secretKey)); + byte[] secretKey = certPrivateMLDsa.ExportMLDsaSecretKey(); AssertExtensions.SequenceEqual( MLDsaTestsData.IetfMLDsa44.SecretKey, secretKey); @@ -870,7 +866,7 @@ public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_SecretKey() privateMLDsa.TryExportPkcs8PrivateKeyHook = (_, out w) => { Assert.Fail(); w = 0; return false; }; // Ensure the key is actual a clone - Assert.Equal(secretKey.Length, certPrivateMLDsa.ExportMLDsaSecretKey(secretKey)); + secretKey = certPrivateMLDsa.ExportMLDsaSecretKey(); AssertExtensions.SequenceEqual( MLDsaTestsData.IetfMLDsa44.SecretKey, secretKey); @@ -921,8 +917,7 @@ public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_PrivateSeed() using (MLDsa certPrivateMLDsa = GetMLDsaPrivateKey(privCert)) { - byte[] secretKey = new byte[certPrivateMLDsa.Algorithm.PrivateSeedSizeInBytes]; - Assert.Equal(secretKey.Length, certPrivateMLDsa.ExportMLDsaPrivateSeed(secretKey)); + byte[] secretKey = certPrivateMLDsa.ExportMLDsaPrivateSeed(); AssertExtensions.SequenceEqual( MLDsaTestsData.IetfMLDsa44.PrivateSeed, secretKey); @@ -933,7 +928,7 @@ public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_PrivateSeed() privateMLDsa.TryExportPkcs8PrivateKeyHook = (_, out w) => { Assert.Fail(); w = 0; return false; }; // Ensure the key is actual a clone - Assert.Equal(secretKey.Length, certPrivateMLDsa.ExportMLDsaPrivateSeed(secretKey)); + secretKey = certPrivateMLDsa.ExportMLDsaPrivateSeed(); AssertExtensions.SequenceEqual( MLDsaTestsData.IetfMLDsa44.PrivateSeed, secretKey); diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/ExportTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/ExportTests.cs index bad6c553af54aa..70eb3214bf858c 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/ExportTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/ExportTests.cs @@ -419,8 +419,7 @@ public static void ExportPkcs12_MLDsa_Generated_Roundtrip(MLDsaKeyInfo info) Assert.Equal(info.Algorithm, mldsa.Algorithm); AssertExtensions.SequenceEqual(info.Certificate, reLoaded.RawData); - byte[] actualSecretKey = new byte[info.SecretKey.Length]; - Assert.Equal(actualSecretKey.Length, mldsa.ExportMLDsaSecretKey(actualSecretKey)); + byte[] actualSecretKey = mldsa.ExportMLDsaSecretKey(); AssertExtensions.SequenceEqual(info.SecretKey, actualSecretKey); } } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/PfxTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/PfxTests.cs index 2666f58d247934..922b33c02dab0d 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/PfxTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/PfxTests.cs @@ -685,8 +685,7 @@ public static void ReadMLDsa512PrivateKey_Seed_Pfx(X509KeyStorageFlags keyStorag Assert.Equal(info.Algorithm, mldsa.Algorithm); Assert.Equal("CN=LAMPS WG, O=IETF", cert.Subject); - byte[] seed = new byte[info.PrivateSeed.Length]; - Assert.Equal(seed.Length, mldsa.ExportMLDsaPrivateSeed(seed)); + byte[] seed = mldsa.ExportMLDsaPrivateSeed(); AssertExtensions.SequenceEqual(info.PrivateSeed, seed); } } @@ -706,9 +705,8 @@ public static void ReadMLDsa512PrivateKey_ExpandedKey_Pfx(X509KeyStorageFlags ke Assert.Equal(info.Algorithm, mldsa.Algorithm); Assert.Equal("CN=LAMPS WG, O=IETF", cert.Subject); - byte[] secretKey = new byte[info.SecretKey.Length]; - Assert.Throws(() => mldsa.ExportMLDsaPrivateSeed(secretKey)); - Assert.Equal(secretKey.Length, mldsa.ExportMLDsaSecretKey(secretKey)); + Assert.Throws(() => mldsa.ExportMLDsaPrivateSeed()); + byte[] secretKey = mldsa.ExportMLDsaSecretKey(); AssertExtensions.SequenceEqual(info.SecretKey, secretKey); } } @@ -728,14 +726,11 @@ public static void ReadMLDsa512PrivateKey_Both_Pfx(X509KeyStorageFlags keyStorag Assert.Equal(info.Algorithm, mldsa.Algorithm); Assert.Equal("CN=LAMPS WG, O=IETF", cert.Subject); - byte[] buffer = new byte[info.SecretKey.Length]; - Assert.Equal(info.PrivateSeed.Length, mldsa.ExportMLDsaPrivateSeed(buffer)); - AssertExtensions.SequenceEqual(info.PrivateSeed.AsSpan(), buffer.AsSpan(0, info.PrivateSeed.Length)); + byte[] seed = mldsa.ExportMLDsaPrivateSeed(); + AssertExtensions.SequenceEqual(info.PrivateSeed.AsSpan(), seed.AsSpan()); - Assert.Equal(info.SecretKey.Length, mldsa.ExportMLDsaSecretKey(buffer)); - AssertExtensions.SequenceEqual( - info.SecretKey, - buffer); + byte[] sk = mldsa.ExportMLDsaSecretKey(); + AssertExtensions.SequenceEqual(info.SecretKey, sk); } } From 8c550d7c14e94e0c81dba8539102e289898dcc29 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Wed, 25 Jun 2025 19:15:59 +0200 Subject: [PATCH 2/8] Import/Sign/Verify+tests --- .../src/System/Security/Cryptography/MLDsa.cs | 135 ++++++++++++++++-- .../MLDsa/MLDsaTestHelpers.cs | 6 + .../MLDsa/MLDsaTests.cs | 66 ++++++++- .../MLDsa/MLDsaTestsBase.cs | 30 ++-- .../ref/System.Security.Cryptography.cs | 7 +- .../MLDsaX509SignatureGenerator.cs | 7 +- .../tests/MLDsaOpenSslTests.Unix.cs | 12 +- .../PrivateKeyAssociationTests.cs | 4 +- .../X509Certificate2PemTests.cs | 3 +- 9 files changed, 215 insertions(+), 55 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs index 13eb66e4faf6e1..21eef437af030b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs @@ -85,17 +85,15 @@ public void Dispose() /// The data to sign. /// /// - /// The buffer to receive the signature. + /// The buffer to receive the signature. Its length must be exactly + /// . /// /// /// An optional context-specific value to limit the scope of the signature. /// The default value is an empty buffer. /// - /// - /// The number of bytes written to the buffer. - /// /// - /// The buffer in is too small to hold the signature. + /// The buffer in is the incorrect length to receive the signature. /// /// /// has a in excess of @@ -109,8 +107,17 @@ public void Dispose() /// -or- /// An error occurred while signing the data. /// - public int SignData(ReadOnlySpan data, Span destination, ReadOnlySpan context = default) + public void SignData(ReadOnlySpan data, Span destination, ReadOnlySpan context = default) { + int signatureSizeInBytes = Algorithm.SignatureSizeInBytes; + + if (destination.Length != signatureSizeInBytes) + { + throw new ArgumentException( + SR.Format(SR.Argument_DestinationImprecise, signatureSizeInBytes), + nameof(destination)); + } + if (context.Length > MaxContextLength) { throw new ArgumentOutOfRangeException( @@ -119,14 +126,44 @@ public int SignData(ReadOnlySpan data, Span destination, ReadOnlySpa SR.Argument_SignatureContextTooLong255); } - if (destination.Length < Algorithm.SignatureSizeInBytes) - { - throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); - } - ThrowIfDisposed(); - SignDataCore(data, context, destination.Slice(0, Algorithm.SignatureSizeInBytes)); - return Algorithm.SignatureSizeInBytes; + SignDataCore(data, context, destination); + } + + /// + /// Signs the specified data. + /// + /// + /// The data to sign. + /// + /// + /// An optional context-specific value to limit the scope of the signature. + /// The default value is . + /// + /// + /// is . + /// + /// + /// has a length in excess of 255 bytes. + /// + /// + /// This instance has been disposed. + /// + /// + /// The instance represents only a public key. + /// -or- + /// An error occurred while signing the data. + /// + /// + /// A context is treated as empty. + /// + public byte[] SignData(byte[] data, byte[]? context = default) + { + ArgumentNullException.ThrowIfNull(data); + + byte[] destination = new byte[Algorithm.SignatureSizeInBytes]; + SignData(new ReadOnlySpan(data), destination.AsSpan(), new ReadOnlySpan(context)); + return destination; } // TODO: SignPreHash @@ -179,6 +216,45 @@ public bool VerifyData(ReadOnlySpan data, ReadOnlySpan signature, Re return VerifyDataCore(data, context, signature); } + /// + /// Verifies that the specified signature is valid for this key and the provided data. + /// + /// + /// The data to verify. + /// + /// + /// The signature to verify. + /// + /// + /// The context value which was provided during signing. + /// The default value is . + /// + /// + /// if the signature validates the data; otherwise, . + /// + /// + /// or is . + /// + /// + /// has a length in excess of 255 bytes. + /// + /// + /// This instance has been disposed. + /// + /// + /// An error occurred while verifying the data. + /// + /// + /// A context is treated as empty. + /// + public bool VerifyData(byte[] data, byte[] signature, byte[]? context = default) + { + ArgumentNullException.ThrowIfNull(data); + ArgumentNullException.ThrowIfNull(signature); + + return VerifyData(new ReadOnlySpan(data), new ReadOnlySpan(signature), new ReadOnlySpan(context)); + } + // TODO: VerifyPreHash /// @@ -1368,6 +1444,17 @@ public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan< return MLDsaImplementation.ImportPublicKey(algorithm, source); } + /// + /// + /// or is . + /// + public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, byte[] source) + { + ArgumentNullException.ThrowIfNull(source); + + return ImportMLDsaPublicKey(algorithm, new ReadOnlySpan(source)); + } + /// /// Imports an ML-DSA private key in the FIPS 204 secret key format. /// @@ -1406,6 +1493,17 @@ public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan< return MLDsaImplementation.ImportSecretKey(algorithm, source); } + /// + /// + /// or is . + /// + public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, byte[] source) + { + ArgumentNullException.ThrowIfNull(source); + + return ImportMLDsaSecretKey(algorithm, new ReadOnlySpan(source)); + } + /// /// Imports an ML-DSA private key from its private seed value. /// @@ -1444,6 +1542,17 @@ public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpa return MLDsaImplementation.ImportSeed(algorithm, source); } + /// + /// + /// or is . + /// + public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, byte[] source) + { + ArgumentNullException.ThrowIfNull(source); + + return ImportMLDsaPrivateSeed(algorithm, new ReadOnlySpan(source)); + } + /// /// Called by the Dispose() and Finalize() methods to release the managed and unmanaged /// resources used by the current instance of the class. diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestHelpers.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestHelpers.cs index 5800ef812b767d..a03ec55bca7eed 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestHelpers.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestHelpers.cs @@ -64,9 +64,11 @@ internal static void AssertImportPublicKey(Action> testDirectCall, A { testDirectCall(() => MLDsa.ImportMLDsaPublicKey(algorithm, Array.Empty().AsSpan())); testDirectCall(() => MLDsa.ImportMLDsaPublicKey(algorithm, ReadOnlySpan.Empty)); + testDirectCall(() => MLDsa.ImportMLDsaPublicKey(algorithm, default(ReadOnlySpan))); } else { + testDirectCall(() => MLDsa.ImportMLDsaPublicKey(algorithm, publicKey)); testDirectCall(() => MLDsa.ImportMLDsaPublicKey(algorithm, publicKey.AsSpan())); } @@ -109,9 +111,11 @@ internal static void AssertImportSecretKey(Action> testDirectCall, A { testDirectCall(() => MLDsa.ImportMLDsaSecretKey(algorithm, Array.Empty().AsSpan())); testDirectCall(() => MLDsa.ImportMLDsaSecretKey(algorithm, ReadOnlySpan.Empty)); + testDirectCall(() => MLDsa.ImportMLDsaSecretKey(algorithm, default(ReadOnlySpan))); } else { + testDirectCall(() => MLDsa.ImportMLDsaSecretKey(algorithm, secretKey)); testDirectCall(() => MLDsa.ImportMLDsaSecretKey(algorithm, secretKey.AsSpan())); } @@ -147,9 +151,11 @@ internal static void AssertImportPrivateSeed(Action> testDirectCall, { testDirectCall(() => MLDsa.ImportMLDsaPrivateSeed(algorithm, Array.Empty().AsSpan())); testDirectCall(() => MLDsa.ImportMLDsaPrivateSeed(algorithm, ReadOnlySpan.Empty)); + testDirectCall(() => MLDsa.ImportMLDsaPrivateSeed(algorithm, default(ReadOnlySpan))); } else { + testDirectCall(() => MLDsa.ImportMLDsaPrivateSeed(algorithm, privateSeed)); testDirectCall(() => MLDsa.ImportMLDsaPrivateSeed(algorithm, privateSeed.AsSpan())); } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs index 9b31180db2fc46..8c6dbe6db869d9 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs @@ -74,6 +74,10 @@ public static void NullArgumentValidation(MLDsaAlgorithm algorithm, bool shouldD PbeParameters pbeParameters = new PbeParameters(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 42); + AssertExtensions.Throws("data", () => mldsa.SignData(null)); + AssertExtensions.Throws("data", () => mldsa.VerifyData(null, null)); + AssertExtensions.Throws("signature", () => mldsa.VerifyData(Array.Empty(), null)); + AssertExtensions.Throws("password", () => mldsa.ExportEncryptedPkcs8PrivateKey((string)null, pbeParameters)); AssertExtensions.Throws("password", () => mldsa.ExportEncryptedPkcs8PrivateKeyPem((string)null, pbeParameters)); AssertExtensions.Throws("password", () => mldsa.TryExportEncryptedPkcs8PrivateKey((string)null, pbeParameters, Span.Empty, out _)); @@ -107,9 +111,13 @@ public static void ArgumentValidation(MLDsaAlgorithm algorithm, bool shouldDispo } AssertExtensions.Throws("destination", () => mldsa.ExportMLDsaPublicKey(new byte[publicKeySize - 1])); + AssertExtensions.Throws("destination", () => mldsa.ExportMLDsaPublicKey(new byte[publicKeySize + 1])); AssertExtensions.Throws("destination", () => mldsa.ExportMLDsaSecretKey(new byte[secretKeySize - 1])); + AssertExtensions.Throws("destination", () => mldsa.ExportMLDsaSecretKey(new byte[secretKeySize + 1])); AssertExtensions.Throws("destination", () => mldsa.ExportMLDsaPrivateSeed(new byte[privateSeedSize - 1])); + AssertExtensions.Throws("destination", () => mldsa.ExportMLDsaPrivateSeed(new byte[privateSeedSize + 1])); AssertExtensions.Throws("destination", () => mldsa.SignData(ReadOnlySpan.Empty, new byte[signatureSize - 1], ReadOnlySpan.Empty)); + AssertExtensions.Throws("destination", () => mldsa.SignData(ReadOnlySpan.Empty, new byte[signatureSize + 1], ReadOnlySpan.Empty)); // Context length must be less than 256 AssertExtensions.Throws("context", () => mldsa.SignData(ReadOnlySpan.Empty, new byte[signatureSize], new byte[256])); @@ -165,6 +173,14 @@ public static void ExportMLDsaPublicKey_CallsCore(MLDsaAlgorithm algorithm) mldsa.AddFillDestination(1); int publicKeySize = algorithm.PublicKeySizeInBytes; + + // Array overload + byte[] exported = mldsa.ExportMLDsaPublicKey(); + Assert.Equal(1, mldsa.ExportMLDsaPublicKeyCoreCallCount); + Assert.Equal(publicKeySize, exported.Length); + AssertExpectedFill(exported, fillElement: 1); + + // Span overload byte[] publicKey = CreatePaddedFilledArray(publicKeySize, 42); // Extra bytes in destination buffer should not be touched @@ -172,7 +188,7 @@ public static void ExportMLDsaPublicKey_CallsCore(MLDsaAlgorithm algorithm) mldsa.AddDestinationBufferIsSameAssertion(destination); mldsa.ExportMLDsaPublicKey(destination.Span); - Assert.Equal(1, mldsa.ExportMLDsaPublicKeyCoreCallCount); + Assert.Equal(2, mldsa.ExportMLDsaPublicKeyCoreCallCount); AssertExpectedFill(publicKey, fillElement: 1, paddingElement: 42, PaddingSize, publicKeySize); } @@ -185,6 +201,14 @@ public static void ExportMLDsaSecretKey_CallsCore(MLDsaAlgorithm algorithm) mldsa.AddFillDestination(1); int secretKeySize = algorithm.SecretKeySizeInBytes; + + // Array overload + byte[] exported = mldsa.ExportMLDsaSecretKey(); + Assert.Equal(1, mldsa.ExportMLDsaSecretKeyCoreCallCount); + Assert.Equal(secretKeySize, exported.Length); + AssertExpectedFill(exported, fillElement: 1); + + // Span overload byte[] secretKey = CreatePaddedFilledArray(secretKeySize, 42); // Extra bytes in destination buffer should not be touched @@ -192,10 +216,38 @@ public static void ExportMLDsaSecretKey_CallsCore(MLDsaAlgorithm algorithm) mldsa.AddDestinationBufferIsSameAssertion(destination); mldsa.ExportMLDsaSecretKey(destination.Span); - Assert.Equal(1, mldsa.ExportMLDsaSecretKeyCoreCallCount); + Assert.Equal(2, mldsa.ExportMLDsaSecretKeyCoreCallCount); AssertExpectedFill(secretKey, fillElement: 1, paddingElement: 42, PaddingSize, secretKeySize); } + [Theory] + [MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))] + public static void ExportMLDsaPrivateSeed_CallsCore(MLDsaAlgorithm algorithm) + { + using MLDsaTestImplementation mldsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(algorithm); + mldsa.ExportMLDsaPrivateSeedHook = _ => { }; + mldsa.AddFillDestination(1); + + int privateSeedSize = algorithm.PrivateSeedSizeInBytes; + + // Array overload + byte[] exported = mldsa.ExportMLDsaPrivateSeed(); + Assert.Equal(1, mldsa.ExportMLDsaPrivateSeedCoreCallCount); + Assert.Equal(privateSeedSize, exported.Length); + AssertExpectedFill(exported, fillElement: 1); + + // Span overload + byte[] secretKey = CreatePaddedFilledArray(privateSeedSize, 42); + + // Extra bytes in destination buffer should not be touched + Memory destination = secretKey.AsMemory(PaddingSize, privateSeedSize); + mldsa.AddDestinationBufferIsSameAssertion(destination); + + mldsa.ExportMLDsaPrivateSeed(destination.Span); + Assert.Equal(2, mldsa.ExportMLDsaPrivateSeedCoreCallCount); + AssertExpectedFill(secretKey, fillElement: 1, paddingElement: 42, PaddingSize, privateSeedSize); + } + [Theory] [MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))] public static void SignData_CallsCore(MLDsaAlgorithm algorithm) @@ -210,6 +262,14 @@ public static void SignData_CallsCore(MLDsaAlgorithm algorithm) mldsa.AddFillDestination(1); int signatureSize = algorithm.SignatureSizeInBytes; + + // Array overload + byte[] exported = mldsa.SignData(testData, testContext); + Assert.Equal(1, mldsa.SignDataCoreCallCount); + Assert.Equal(signatureSize, exported.Length); + AssertExpectedFill(exported, fillElement: 1); + + // Span overload byte[] signature = CreatePaddedFilledArray(signatureSize, 42); // Extra bytes in destination buffer should not be touched @@ -217,7 +277,7 @@ public static void SignData_CallsCore(MLDsaAlgorithm algorithm) mldsa.AddDestinationBufferIsSameAssertion(destination); mldsa.SignData(testData, destination.Span, testContext); - Assert.Equal(1, mldsa.SignDataCoreCallCount); + Assert.Equal(2, mldsa.SignDataCoreCallCount); AssertExpectedFill(signature, fillElement: 1, paddingElement: 42, PaddingSize, signatureSize); } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs index 9d511eee563872..51575607083561 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs @@ -30,9 +30,7 @@ public void GenerateSignVerifyNoContext(MLDsaAlgorithm algorithm) { using MLDsa mldsa = GenerateKey(algorithm); byte[] data = [ 1, 2, 3, 4, 5 ]; - byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsa.SignData(data, signature)); - + byte[] signature = mldsa.SignData(data); ExerciseSuccessfulVerify(mldsa, data, signature, []); } @@ -43,8 +41,7 @@ public void GenerateSignVerifyWithContext(MLDsaAlgorithm algorithm) using MLDsa mldsa = GenerateKey(algorithm); byte[] context = [ 1, 1, 3, 5, 6 ]; byte[] data = [ 1, 2, 3, 4, 5 ]; - byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsa.SignData(data, signature, context)); + byte[] signature = mldsa.SignData(data, context); ExerciseSuccessfulVerify(mldsa, data, signature, context); } @@ -55,9 +52,7 @@ public void GenerateSignVerifyWithContext(MLDsaAlgorithm algorithm) public void GenerateSignVerifyEmptyMessageNoContext(MLDsaAlgorithm algorithm) { using MLDsa mldsa = GenerateKey(algorithm); - byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsa.SignData([], signature)); - + byte[] signature = mldsa.SignData([]); ExerciseSuccessfulVerify(mldsa, [], signature, []); } @@ -68,9 +63,7 @@ public void GenerateSignVerifyEmptyMessageWithContext(MLDsaAlgorithm algorithm) { using MLDsa mldsa = GenerateKey(algorithm); byte[] context = [1, 1, 3, 5, 6]; - byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsa.SignData([], signature, context)); - + byte[] signature = mldsa.SignData([], context); ExerciseSuccessfulVerify(mldsa, [], signature, context); } @@ -84,8 +77,7 @@ public void GenerateSignExportPublicVerifyWithPublicOnly(MLDsaAlgorithm algorith using (MLDsa mldsa = GenerateKey(algorithm)) { - signature = new byte[algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + signature = mldsa.SignData(data); AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); publicKey = mldsa.ExportMLDsaPublicKey(); @@ -107,9 +99,7 @@ public void GenerateExportSecretKeySignAndVerify(MLDsaAlgorithm algorithm) using (MLDsa mldsaTmp = GenerateKey(algorithm)) { - signature = new byte[algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature)); - + signature = mldsaTmp.SignData(data); secretKey = mldsaTmp.ExportMLDsaSecretKey(); } @@ -118,7 +108,7 @@ public void GenerateExportSecretKeySignAndVerify(MLDsaAlgorithm algorithm) AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); signature.AsSpan().Fill(0); - Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + mldsa.SignData(data, signature); AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); data[0] ^= 1; @@ -136,9 +126,7 @@ public void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm) using (MLDsa mldsaTmp = GenerateKey(algorithm)) { - signature = new byte[algorithm.SignatureSizeInBytes]; - Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature)); - + signature = mldsaTmp.SignData(data); privateSeed = mldsaTmp.ExportMLDsaPrivateSeed(); } @@ -147,7 +135,7 @@ public void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm) AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); signature.AsSpan().Fill(0); - Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + mldsa.SignData(data, signature); ExerciseSuccessfulVerify(mldsa, data, signature, []); } diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index f3bfc940b1dbe0..6bc45dacf77113 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -1842,14 +1842,18 @@ public void ExportMLDsaSecretKey(System.Span destination) { } public static System.Security.Cryptography.MLDsa ImportFromEncryptedPem(string source, string password) { throw null; } public static System.Security.Cryptography.MLDsa ImportFromPem(System.ReadOnlySpan source) { throw null; } public static System.Security.Cryptography.MLDsa ImportFromPem(string source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportMLDsaPrivateSeed(System.Security.Cryptography.MLDsaAlgorithm algorithm, byte[] source) { throw null; } public static System.Security.Cryptography.MLDsa ImportMLDsaPrivateSeed(System.Security.Cryptography.MLDsaAlgorithm algorithm, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportMLDsaPublicKey(System.Security.Cryptography.MLDsaAlgorithm algorithm, byte[] source) { throw null; } public static System.Security.Cryptography.MLDsa ImportMLDsaPublicKey(System.Security.Cryptography.MLDsaAlgorithm algorithm, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportMLDsaSecretKey(System.Security.Cryptography.MLDsaAlgorithm algorithm, byte[] source) { throw null; } public static System.Security.Cryptography.MLDsa ImportMLDsaSecretKey(System.Security.Cryptography.MLDsaAlgorithm algorithm, System.ReadOnlySpan source) { throw null; } public static System.Security.Cryptography.MLDsa ImportPkcs8PrivateKey(byte[] source) { throw null; } public static System.Security.Cryptography.MLDsa ImportPkcs8PrivateKey(System.ReadOnlySpan source) { throw null; } public static System.Security.Cryptography.MLDsa ImportSubjectPublicKeyInfo(byte[] source) { throw null; } public static System.Security.Cryptography.MLDsa ImportSubjectPublicKeyInfo(System.ReadOnlySpan source) { throw null; } - public int SignData(System.ReadOnlySpan data, System.Span destination, System.ReadOnlySpan context = default(System.ReadOnlySpan)) { throw null; } + public byte[] SignData(byte[] data, byte[]? context = null) { throw null; } + public void SignData(System.ReadOnlySpan data, System.Span destination, System.ReadOnlySpan context = default(System.ReadOnlySpan)) { throw null; } protected abstract void SignDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.Span destination); public bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } @@ -1857,6 +1861,7 @@ public void ExportMLDsaSecretKey(System.Span destination) { } public bool TryExportPkcs8PrivateKey(System.Span destination, out int bytesWritten) { throw null; } protected abstract bool TryExportPkcs8PrivateKeyCore(System.Span destination, out int bytesWritten); public bool TryExportSubjectPublicKeyInfo(System.Span destination, out int bytesWritten) { throw null; } + public bool VerifyData(byte[] data, byte[] signature, byte[]? context = null) { throw null; } public bool VerifyData(System.ReadOnlySpan data, System.ReadOnlySpan signature, System.ReadOnlySpan context = default(System.ReadOnlySpan)) { throw null; } protected abstract bool VerifyDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.ReadOnlySpan signature); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs index d40cb4b541a32b..80fad02ad48b15 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs @@ -37,16 +37,11 @@ public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlg public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) { - ArgumentNullException.ThrowIfNull(data); - // Ignore the hashAlgorithm parameter. // This generator only supports ML-DSA "Pure" signatures, but the overall design of // CertificateRequest makes it easy for a hashAlgorithm value to get here. - byte[] signature = new byte[_key.Algorithm.SignatureSizeInBytes]; - int written = _key.SignData(data, signature); - Debug.Assert(written == signature.Length); - return signature; + return _key.SignData(data); } protected override PublicKey BuildPublicKey() diff --git a/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs b/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs index d46cc47c17fa6a..6808be94541fac 100644 --- a/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs +++ b/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs @@ -94,11 +94,11 @@ public void MLDsaOpenSsl_DuplicateKeyHandleLifetime() { byte[] data = [ 1, 1, 2, 3, 5, 8 ]; byte[] context = [ 13, 21 ]; - byte[] oneSignature = new byte[MLDsaAlgorithm.MLDsa44.SignatureSizeInBytes]; + byte[] oneSignature; using (one) { - Assert.Equal(oneSignature.Length, one.SignData(data, oneSignature, context)); + oneSignature = one.SignData(data, context); VerifyInstanceIsUsable(one); VerifyInstanceIsUsable(two); } @@ -116,11 +116,11 @@ public void MLDsaOpenSsl_DuplicateKeyHandleLifetime() private static void VerifyInstanceIsUsable(MLDsaOpenSsl mldsa) { - byte[] seed = new byte[mldsa.Algorithm.PrivateSeedSizeInBytes]; - Assert.Equal(mldsa.Algorithm.PrivateSeedSizeInBytes, mldsa.ExportMLDsaPrivateSeed(seed)); // does not throw + byte[] seed = mldsa.ExportMLDsaPrivateSeed(); + Assert.NotEqual(0, seed.Length); // does not throw - byte[] secretKey = new byte[mldsa.Algorithm.SecretKeySizeInBytes]; - Assert.Equal(mldsa.Algorithm.SecretKeySizeInBytes, mldsa.ExportMLDsaSecretKey(secretKey)); // does not throw + byte[] secretKey = mldsa.ExportMLDsaSecretKey(); + Assert.NotEqual(0, secretKey.Length); // does not throw // usable byte[] data = [ 1, 2, 3 ]; diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs index 110436984ffbab..ee55e9818fabe5 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs @@ -800,9 +800,7 @@ public static void CheckCopyWithPrivateKey_MLDSA() byte[] data = new byte[RandomNumberGenerator.GetInt32(97)]; RandomNumberGenerator.Fill(data); - byte[] signature = new byte[pub.Algorithm.SignatureSizeInBytes]; - int written = priv.SignData(data, signature); - Assert.Equal(signature.Length, written); + byte[] signature = priv.SignData(data); Assert.True(pub.VerifyData(data, signature)); }); } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/X509Certificate2PemTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/X509Certificate2PemTests.cs index d08a420c20b779..a1e35ee0df9cc0 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/X509Certificate2PemTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/X509Certificate2PemTests.cs @@ -916,8 +916,7 @@ private static void AssertKeysMatch(string keyPem, Func keyLoader, string AssertExtensions.SequenceEqual(sharedSecret1, sharedSecret2); break; case (MLDsa mldsa, MLDsa mldsaPem): - byte[] mldsaSignature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; - Assert.Equal(mldsaSignature.Length, mldsa.SignData(data, mldsaSignature)); + byte[] mldsaSignature = mldsa.SignData(data); Assert.True(mldsaPem.VerifyData(data, mldsaSignature)); break; case (SlhDsa slhDsa, SlhDsa slhDsaPem): From e8e61cba27919cf47c7e50f77992805b10c31cea Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Wed, 25 Jun 2025 23:20:09 +0200 Subject: [PATCH 3/8] Fix DuplicatePrivateKey --- .../src/System/Security/Cryptography/MLDsaImplementation.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs index de5788f12d515e..466af1346cccbd 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs @@ -32,8 +32,9 @@ internal static MLDsaImplementation DuplicatePrivateKey(MLDsa key) try { - key.ExportMLDsaPrivateSeed(rented); - return ImportSeed(alg, rented); + Span seedSpan = rented.AsSpan(0, alg.SeedSizeInBytes); + key.ExportMLDsaPrivateSeed(seedSpan); + return ImportSeed(alg, seedSpan); } catch (CryptographicException) { From 0b29e509baeb0048f7c4ea4385d99d789019c75e Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 26 Jun 2025 12:03:27 +0200 Subject: [PATCH 4/8] Fix failures/tests failures --- .../src/System/Security/Cryptography/MLDsa.cs | 3 +++ .../Cryptography/MLDsaImplementation.cs | 9 ++++++--- .../MLDsa/MLDsaImplementationTests.cs | 18 +++++++++++++++--- .../MLDsa/MLDsaTestsBase.cs | 10 ++++++---- .../PrivateKeyAssociationTests.cs | 6 ++---- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs index 21eef437af030b..5b42f723c4db78 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs @@ -1450,6 +1450,7 @@ public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan< /// public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, byte[] source) { + ArgumentNullException.ThrowIfNull(algorithm); ArgumentNullException.ThrowIfNull(source); return ImportMLDsaPublicKey(algorithm, new ReadOnlySpan(source)); @@ -1499,6 +1500,7 @@ public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan< /// public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, byte[] source) { + ArgumentNullException.ThrowIfNull(algorithm); ArgumentNullException.ThrowIfNull(source); return ImportMLDsaSecretKey(algorithm, new ReadOnlySpan(source)); @@ -1548,6 +1550,7 @@ public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpa /// public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, byte[] source) { + ArgumentNullException.ThrowIfNull(algorithm); ArgumentNullException.ThrowIfNull(source); return ImportMLDsaPrivateSeed(algorithm, new ReadOnlySpan(source)); diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs index 466af1346cccbd..b3ca249c345ea9 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs @@ -28,18 +28,21 @@ internal static MLDsaImplementation DuplicatePrivateKey(MLDsa key) Debug.Assert(key is not MLDsaImplementation); MLDsaAlgorithm alg = key.Algorithm; + Debug.Assert(alg.SecretKeySizeInBytes > alg.PrivateSeedSizeInBytes); byte[] rented = CryptoPool.Rent(alg.SecretKeySizeInBytes); try { - Span seedSpan = rented.AsSpan(0, alg.SeedSizeInBytes); + Span seedSpan = rented.AsSpan(0, alg.PrivateSeedSizeInBytes); key.ExportMLDsaPrivateSeed(seedSpan); return ImportSeed(alg, seedSpan); } catch (CryptographicException) { - key.ExportMLDsaSecretKey(rented); - return ImportSecretKey(alg, rented); + // Rented array may still be larger but we expect exact length + Span skSpan = rented.AsSpan(0, alg.SecretKeySizeInBytes); + key.ExportMLDsaSecretKey(skSpan); + return ImportSecretKey(alg, skSpan); } finally { diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaImplementationTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaImplementationTests.cs index 2c66bae65052d6..eabcc2f37801a3 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaImplementationTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaImplementationTests.cs @@ -20,9 +20,21 @@ public class MLDsaImplementationTests : MLDsaTestsBase public static void GenerateImport_NullAlgorithm() { AssertExtensions.Throws("algorithm", static () => MLDsa.GenerateKey(null)); - AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaPrivateSeed(null, default)); - AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaPublicKey(null, default)); - AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaSecretKey(null, default)); + AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaPrivateSeed(null, default(ReadOnlySpan))); + AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaPublicKey(null, default(ReadOnlySpan))); + AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaSecretKey(null, default(ReadOnlySpan))); + + AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaPrivateSeed(null, (byte[]?)null)); + AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaPublicKey(null, (byte[]?)null)); + AssertExtensions.Throws("algorithm", static () => MLDsa.ImportMLDsaSecretKey(null, (byte[]?)null)); + } + + [Fact] + public static void Import_NullSource() + { + AssertExtensions.Throws("source", static () => MLDsa.ImportMLDsaPrivateSeed(MLDsaAlgorithm.MLDsa44, (byte[]?)null)); + AssertExtensions.Throws("source", static () => MLDsa.ImportMLDsaPublicKey(MLDsaAlgorithm.MLDsa44, (byte[]?)null)); + AssertExtensions.Throws("source", static () => MLDsa.ImportMLDsaSecretKey(MLDsaAlgorithm.MLDsa44, (byte[]?)null)); } [Theory] diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs index 51575607083561..00b2b49e327d8c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestsBase.cs @@ -107,8 +107,9 @@ public void GenerateExportSecretKeySignAndVerify(MLDsaAlgorithm algorithm) { AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); - signature.AsSpan().Fill(0); - mldsa.SignData(data, signature); + Span signatureSpan = signature.AsSpan(); + signatureSpan.Fill(0); + mldsa.SignData(data, signatureSpan); AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); data[0] ^= 1; @@ -134,8 +135,9 @@ public void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm) { AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature)); - signature.AsSpan().Fill(0); - mldsa.SignData(data, signature); + Span signatureSpan = signature.AsSpan(); + signatureSpan.Fill(0); + mldsa.SignData(data, signatureSpan); ExerciseSuccessfulVerify(mldsa, data, signature, []); } diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs index ee55e9818fabe5..cb32411d214250 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs @@ -719,8 +719,7 @@ public static void GetMLDsaPublicKeyTest() AssertExtensions.SequenceEqual(MLDsaTestsData.IetfMLDsa44.PublicKey, publicKey); // Verify the key is not actually private - byte[] signature = new byte[certKey.Algorithm.SignatureSizeInBytes]; - Assert.ThrowsAny(() => certKey.SignData([1, 2, 3], signature)); + Assert.ThrowsAny(() => certKey.SignData([1, 2, 3])); } // Cert with private key @@ -732,8 +731,7 @@ public static void GetMLDsaPublicKeyTest() AssertExtensions.SequenceEqual(MLDsaTestsData.IetfMLDsa44.PublicKey, publicKey); // Verify the key is not actually private - byte[] signature = new byte[certKey.Algorithm.SignatureSizeInBytes]; - Assert.ThrowsAny(() => certKey.SignData([1, 2, 3], signature)); + Assert.ThrowsAny(() => certKey.SignData([1, 2, 3])); } } From a6c0ae74b632dfb60de78e62ee730988b9d7144d Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 26 Jun 2025 14:37:44 +0200 Subject: [PATCH 5/8] Fix CoseKey --- .../src/System/Security/Cryptography/Cose/CoseKey.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs index 368b10144d239f..fc277572851cbd 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseKey.cs @@ -207,7 +207,9 @@ internal int Sign(ReadOnlySpan toBeSigned, Span destination) #pragma warning disable SYSLIB5006 case KeyType.MLDsa: Debug.Assert(_mldsaKey != null); - return _mldsaKey.SignData(toBeSigned, destination); + Span mldsaSignature = destination.Slice(0, _mldsaKey.Algorithm.SignatureSizeInBytes); + _mldsaKey.SignData(toBeSigned, mldsaSignature); + return mldsaSignature.Length; #pragma warning restore SYSLIB5006 default: Debug.Fail("Unknown key type"); From 9ff0c1247c5f9286d58935feb7499db42e41f0c6 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 26 Jun 2025 15:38:53 +0200 Subject: [PATCH 6/8] Cose test fix --- .../tests/CoseTestHelpers.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs index 30f8b4e9a088f0..8ec09289007a3f 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseTestHelpers.cs @@ -868,9 +868,7 @@ private static byte[] GetSignature(IDisposable key, HashAlgorithmName? hash, byt else if (key is MLDsa mldsa) { Assert.Null(hash); - byte[] sig = new byte[mldsa.Algorithm.SignatureSizeInBytes]; - Assert.Equal(sig.Length, mldsa.SignData(toBeSigned, sig)); - return sig; + return mldsa.SignData(toBeSigned); } throw new NotImplementedException($"Unhandled key type: {key.GetType()}"); From 80f39dee787a909c45797779d2dc0260864cd74f Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 27 Jun 2025 14:21:14 +0200 Subject: [PATCH 7/8] Address feedback --- .../Common/src/System/Security/Cryptography/MLDsa.cs | 3 +++ .../AlgorithmImplementations/MLDsa/MLDsaTests.cs | 6 +++--- .../X509Certificates/MLDsaX509SignatureGenerator.cs | 2 ++ .../tests/MLDsaOpenSslTests.Unix.cs | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs index 5b42f723c4db78..cfc3dd1d5682cd 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs @@ -140,6 +140,9 @@ public void SignData(ReadOnlySpan data, Span destination, ReadOnlySp /// An optional context-specific value to limit the scope of the signature. /// The default value is . /// + /// + /// ML-DSA signature for the specified data. + /// /// /// is . /// diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs index 8c6dbe6db869d9..6f276d089d798e 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs @@ -237,15 +237,15 @@ public static void ExportMLDsaPrivateSeed_CallsCore(MLDsaAlgorithm algorithm) AssertExpectedFill(exported, fillElement: 1); // Span overload - byte[] secretKey = CreatePaddedFilledArray(privateSeedSize, 42); + byte[] privateSeed = CreatePaddedFilledArray(privateSeedSize, 42); // Extra bytes in destination buffer should not be touched - Memory destination = secretKey.AsMemory(PaddingSize, privateSeedSize); + Memory destination = privateSeed.AsMemory(PaddingSize, privateSeedSize); mldsa.AddDestinationBufferIsSameAssertion(destination); mldsa.ExportMLDsaPrivateSeed(destination.Span); Assert.Equal(2, mldsa.ExportMLDsaPrivateSeedCoreCallCount); - AssertExpectedFill(secretKey, fillElement: 1, paddingElement: 42, PaddingSize, privateSeedSize); + AssertExpectedFill(privateSeed, fillElement: 1, paddingElement: 42, PaddingSize, privateSeedSize); } [Theory] diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs index 80fad02ad48b15..0dcfc3967c2672 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/MLDsaX509SignatureGenerator.cs @@ -37,6 +37,8 @@ public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlg public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) { + ArgumentNullException.ThrowIfNull(data); + // Ignore the hashAlgorithm parameter. // This generator only supports ML-DSA "Pure" signatures, but the overall design of // CertificateRequest makes it easy for a hashAlgorithm value to get here. diff --git a/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs b/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs index 6808be94541fac..b65624f12d7465 100644 --- a/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs +++ b/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs @@ -117,10 +117,10 @@ public void MLDsaOpenSsl_DuplicateKeyHandleLifetime() private static void VerifyInstanceIsUsable(MLDsaOpenSsl mldsa) { byte[] seed = mldsa.ExportMLDsaPrivateSeed(); - Assert.NotEqual(0, seed.Length); // does not throw + Assert.Equal(mldsa.Algorithm.PrivateSeedSizeInBytes, seed.Length); // does not throw byte[] secretKey = mldsa.ExportMLDsaSecretKey(); - Assert.NotEqual(0, secretKey.Length); // does not throw + Assert.Equal(mldsa.Algorithm.SecretKeySizeInBytes, secretKey.Length); // does not throw // usable byte[] data = [ 1, 2, 3 ]; From 88913c6d5a57e9c239d8e69339905c4b63940401 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Mon, 30 Jun 2025 08:40:10 +0200 Subject: [PATCH 8/8] remove comment in tests --- .../tests/MLDsaOpenSslTests.Unix.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs b/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs index b65624f12d7465..8ad67d9a8963dc 100644 --- a/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs +++ b/src/libraries/System.Security.Cryptography/tests/MLDsaOpenSslTests.Unix.cs @@ -117,10 +117,10 @@ public void MLDsaOpenSsl_DuplicateKeyHandleLifetime() private static void VerifyInstanceIsUsable(MLDsaOpenSsl mldsa) { byte[] seed = mldsa.ExportMLDsaPrivateSeed(); - Assert.Equal(mldsa.Algorithm.PrivateSeedSizeInBytes, seed.Length); // does not throw + Assert.Equal(mldsa.Algorithm.PrivateSeedSizeInBytes, seed.Length); byte[] secretKey = mldsa.ExportMLDsaSecretKey(); - Assert.Equal(mldsa.Algorithm.SecretKeySizeInBytes, secretKey.Length); // does not throw + Assert.Equal(mldsa.Algorithm.SecretKeySizeInBytes, secretKey.Length); // usable byte[] data = [ 1, 2, 3 ];