diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs index 0e23b6c4663b33..6b7cd722e3ecd5 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs @@ -143,7 +143,7 @@ public byte[] SignData(byte[] data, byte[]? context = default) ThrowIfDisposed(); - if (Algorithm.SignatureSize.IsExact) + if (Algorithm.MinSignatureSizeInBytes == Algorithm.MaxSignatureSizeInBytes) { byte[] signature = new byte[Algorithm.MaxSignatureSizeInBytes]; int bytesWritten = SignDataCore(new ReadOnlySpan(data), new ReadOnlySpan(context), signature); @@ -163,7 +163,7 @@ public byte[] SignData(byte[] data, byte[]? context = default) new ReadOnlySpan(context), lease.Span); - if (!Algorithm.SignatureSize.IsValidSize(bytesWritten)) + if (!Algorithm.IsValidSignatureSize(bytesWritten)) { throw new CryptographicException(); } @@ -222,8 +222,7 @@ public int SignData(ReadOnlySpan data, Span destination, ReadOnlySpa int bytesWritten = SignDataCore(data, context, destination.Slice(0, Algorithm.MaxSignatureSizeInBytes)); - // Make sure minimum size is also satisfied. - if (!Algorithm.SignatureSize.IsValidSize(bytesWritten)) + if (!Algorithm.IsValidSignatureSize(bytesWritten)) { CryptographicOperations.ZeroMemory(destination); @@ -331,7 +330,7 @@ public bool VerifyData(ReadOnlySpan data, ReadOnlySpan signature, Re ThrowIfDisposed(); - if (!Algorithm.SignatureSize.IsValidSize(signature.Length)) + if (!Algorithm.IsValidSignatureSize(signature.Length)) { return false; } @@ -675,7 +674,7 @@ static void SubjectPublicKeyReader(ReadOnlyMemory key, in AlgorithmIdentif { CompositeMLDsaAlgorithm algorithm = GetAlgorithmIdentifier(in identifier); - if (!algorithm.PublicKeySize.IsValidSize(key.Length)) + if (!algorithm.IsValidPublicKeySize(key.Length)) { throw new CryptographicException(SR.Argument_PublicKeyWrongSizeForAlgorithm); } @@ -874,7 +873,7 @@ static void PrivateKeyReader( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - if (!algorithm.PrivateKeySize.IsValidSize(key.Length)) + if (!algorithm.IsValidPrivateKeySize(key.Length)) { throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm); } @@ -909,7 +908,7 @@ public static CompositeMLDsa ImportCompositeMLDsaPublicKey(CompositeMLDsaAlgorit /// /// /// - /// length is too small for the specified algorithm. + /// length is the wrong size for the specified algorithm. /// /// -or- /// @@ -925,7 +924,7 @@ public static CompositeMLDsa ImportCompositeMLDsaPublicKey(CompositeMLDsaAlgorit ArgumentNullException.ThrowIfNull(algorithm); ThrowIfNotSupported(algorithm); - if (!algorithm.PublicKeySize.IsValidSize(source.Length)) + if (!algorithm.IsValidPublicKeySize(source.Length)) { throw new CryptographicException(SR.Argument_PublicKeyWrongSizeForAlgorithm); } @@ -959,7 +958,7 @@ public static CompositeMLDsa ImportCompositeMLDsaPrivateKey(CompositeMLDsaAlgori /// /// /// - /// length is too small for the specified algorithm. + /// length is the wrong size for the specified algorithm. /// /// -or- /// @@ -975,7 +974,7 @@ public static CompositeMLDsa ImportCompositeMLDsaPrivateKey(CompositeMLDsaAlgori ArgumentNullException.ThrowIfNull(algorithm); ThrowIfNotSupported(algorithm); - if (!algorithm.PrivateKeySize.IsValidSize(source.Length)) + if (!algorithm.IsValidPrivateKeySize(source.Length)) { throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm); } @@ -1386,9 +1385,9 @@ public bool TryExportPkcs8PrivateKey(Span destination, out int bytesWritte { ThrowIfDisposed(); - // The bound can be tightened but private key length of some traditional algorithms, + // The bound can be tightened but private key length of some traditional algorithms // can vary and aren't worth the complex calculation. - int minimumPossiblePkcs8Key = Algorithm.PrivateKeySize.MinimumSizeInBytes; + int minimumPossiblePkcs8Key = Algorithm.MinPrivateKeySizeInBytes; if (destination.Length < minimumPossiblePkcs8Key) { @@ -1507,14 +1506,54 @@ public byte[] ExportCompositeMLDsaPublicKey() { ThrowIfDisposed(); - if (Algorithm.PublicKeySize.IsExact) + byte[] publicKey = new byte[Algorithm.MaxPublicKeySizeInBytes]; + + if (!TryExportCompositeMLDsaPublicKey(publicKey, out int bytesWritten)) + { + Debug.Fail("Max sized buffer was not large enough."); + throw new CryptographicException(); + } + + if (bytesWritten < publicKey.Length) { - return ExportExactSize( - Algorithm.PublicKeySize.MinimumSizeInBytes, - static (key, dest, out written) => key.TryExportCompositeMLDsaPublicKey(dest, out written)); + Array.Resize(ref publicKey, bytesWritten); } - return ExportPublicKeyCallback(static publicKey => publicKey.ToArray()); + return publicKey; + } + + /// + /// Exports the public-key portion of the current key into the provided buffer. + /// + /// + /// The buffer to receive the Composite ML-DSA public key value. + /// + /// + /// The number of bytes written to the buffer. + /// + /// + /// This instance has been disposed. + /// + /// + /// was too not large enough to hold the result. + /// -or- + /// An error occurred while exporting the key. + /// + public int ExportCompositeMLDsaPublicKey(Span destination) + { + ThrowIfDisposed(); + + if (destination.Length < Algorithm.MinPublicKeySizeInBytes) + { + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + if (!TryExportCompositeMLDsaPublicKey(destination, out int bytesWritten)) + { + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + return bytesWritten; } /// @@ -1541,28 +1580,36 @@ public bool TryExportCompositeMLDsaPublicKey(Span destination, out int byt { ThrowIfDisposed(); - if (Algorithm.PublicKeySize.IsAlwaysLargerThan(destination.Length)) + if (destination.Length < Algorithm.MinPublicKeySizeInBytes) { bytesWritten = 0; return false; } - if (TryExportCompositeMLDsaPublicKeyCore(destination, out int written)) + using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally(Algorithm.MaxPublicKeySizeInBytes, destination, skipClear: true)) { - if (!Algorithm.PublicKeySize.IsValidSize(written)) - { - CryptographicOperations.ZeroMemory(destination); + int localBytesWritten = ExportCompositeMLDsaPublicKeyCore(lease.Span); + if (!Algorithm.IsValidPublicKeySize(localBytesWritten)) + { bytesWritten = 0; throw new CryptographicException(); } - bytesWritten = written; + if (lease.IsRented) + { + if (localBytesWritten > destination.Length) + { + bytesWritten = 0; + return false; + } + + lease.Span.Slice(0, localBytesWritten).CopyTo(destination); + } + + bytesWritten = localBytesWritten; return true; } - - bytesWritten = 0; - return false; } /// @@ -1581,17 +1628,57 @@ public byte[] ExportCompositeMLDsaPrivateKey() { ThrowIfDisposed(); - if (Algorithm.PrivateKeySize.IsExact) + byte[] privateKey = new byte[Algorithm.MaxPrivateKeySizeInBytes]; + + if (!TryExportCompositeMLDsaPrivateKey(privateKey, out int bytesWritten)) + { + Debug.Fail("Max sized buffer was not large enough."); + throw new CryptographicException(); + } + + if (bytesWritten < privateKey.Length) { - return ExportExactSize( - Algorithm.PrivateKeySize.MinimumSizeInBytes, - static (key, dest, out written) => key.TryExportCompositeMLDsaPrivateKey(dest, out written)); + byte[] temp = new byte[bytesWritten]; + Array.Copy(privateKey, temp, bytesWritten); + CryptographicOperations.ZeroMemory(privateKey); + privateKey = temp; } - return ExportWithCallback( - Algorithm.PrivateKeySize.InitialExportBufferSizeInBytes, - static (key, dest, out written) => key.TryExportCompositeMLDsaPrivateKey(dest, out written), - static privateKey => privateKey.ToArray()); + return privateKey; + } + + /// + /// Exports the private-key portion of the current key into the provided buffer. + /// + /// + /// The buffer to receive the Composite ML-DSA private key value. + /// + /// + /// The number of bytes written to the buffer. + /// + /// + /// This instance has been disposed. + /// + /// + /// was too not large enough to hold the result. + /// -or- + /// An error occurred while exporting the key. + /// + public int ExportCompositeMLDsaPrivateKey(Span destination) + { + ThrowIfDisposed(); + + if (destination.Length < Algorithm.MinPrivateKeySizeInBytes) + { + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + if (!TryExportCompositeMLDsaPrivateKey(destination, out int bytesWritten)) + { + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + return bytesWritten; } /// @@ -1618,42 +1705,51 @@ public bool TryExportCompositeMLDsaPrivateKey(Span destination, out int by { ThrowIfDisposed(); - if (Algorithm.PrivateKeySize.IsAlwaysLargerThan(destination.Length)) + if (destination.Length < Algorithm.MinPrivateKeySizeInBytes) { bytesWritten = 0; return false; } - if (TryExportCompositeMLDsaPrivateKeyCore(destination, out int written)) + using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally(Algorithm.MaxPrivateKeySizeInBytes, destination, skipClearIfNotRented: true)) { - if (!Algorithm.PrivateKeySize.IsValidSize(written)) + int localBytesWritten = ExportCompositeMLDsaPrivateKeyCore(lease.Span); + + if (!Algorithm.IsValidPrivateKeySize(localBytesWritten)) { - CryptographicOperations.ZeroMemory(destination); + if (!lease.IsRented) + { + CryptographicOperations.ZeroMemory(destination); + } bytesWritten = 0; throw new CryptographicException(); } - bytesWritten = written; + if (lease.IsRented) + { + if (localBytesWritten > destination.Length) + { + bytesWritten = 0; + return false; + } + + lease.Span.Slice(0, localBytesWritten).CopyTo(destination); + } + + bytesWritten = localBytesWritten; return true; } - - bytesWritten = 0; - return false; } /// - /// When overridden in a derived class, attempts to export the public key portion of the current key. + /// When overridden in a derived class, exports the public key portion of the current key. /// /// /// The buffer to receive the public key value. /// - /// - /// When this method returns, contains the number of bytes written to the buffer. - /// /// - /// if was large enough to hold the result; - /// otherwise, . + /// The number of bytes written to the buffer. /// /// /// This instance has been disposed. @@ -1661,20 +1757,16 @@ public bool TryExportCompositeMLDsaPrivateKey(Span destination, out int by /// /// An error occurred while exporting the key. /// - protected abstract bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten); + protected abstract int ExportCompositeMLDsaPublicKeyCore(Span destination); /// - /// When overridden in a derived class, attempts to export the private key portion of the current key. + /// When overridden in a derived class, exports the private key portion of the current key. /// /// /// The buffer to receive the private key value. /// - /// - /// When this method returns, contains the number of bytes written to the buffer. - /// /// - /// if was large enough to hold the result; - /// otherwise, . + /// The number of bytes written to the buffer. /// /// /// This instance has been disposed. @@ -1682,7 +1774,7 @@ public bool TryExportCompositeMLDsaPrivateKey(Span destination, out int by /// /// An error occurred while exporting the key. /// - protected abstract bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten); + protected abstract int ExportCompositeMLDsaPrivateKeyCore(Span destination); /// /// Releases all resources used by the class. @@ -1759,67 +1851,51 @@ private AsnWriter WritePkcs8ToAsnWriter() }); } - private TResult ExportPkcs8PrivateKeyCallback(ProcessExportedContent func) - { - int initialSize = Algorithm.PrivateKeySize.InitialExportBufferSizeInBytes; - - return ExportWithCallback( - initialSize, - static (key, dest, out written) => key.TryExportPkcs8PrivateKeyCore(dest, out written), - func); - } - private AsnWriter WriteSubjectPublicKeyToAsnWriter() { - return ExportPublicKeyCallback(publicKey => + byte[] buffer = new byte[Algorithm.MaxPublicKeySizeInBytes]; + int written = ExportCompositeMLDsaPublicKeyCore(buffer); + + if (!Algorithm.IsValidPublicKeySize(written)) { - // TODO verify overhead + throw new CryptographicException(); + } - // TODO: The ASN.1 overhead of a SubjectPublicKeyInfo encoding a public key is ___ bytes. - // Round it off to 32. This checked operation should never throw because the inputs are not - // user provided. - int capacity = checked(32 + publicKey.Length); - AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, capacity); + ReadOnlySpan publicKey = buffer.AsSpan(0, written); + // TODO verify overhead + + // TODO: The ASN.1 overhead of a SubjectPublicKeyInfo encoding a public key is ___ bytes. + // Round it off to 32. This checked operation should never throw because the inputs are not + // user provided. + int capacity = checked(32 + publicKey.Length); + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, capacity); + + using (writer.PushSequence()) + { using (writer.PushSequence()) { - using (writer.PushSequence()) - { - writer.WriteObjectIdentifier(Algorithm.Oid); - } - - writer.WriteBitString(publicKey); + writer.WriteObjectIdentifier(Algorithm.Oid); } - Debug.Assert(writer.GetEncodedLength() <= capacity); - return writer; - }); - } + writer.WriteBitString(publicKey); + } - private TResult ExportPublicKeyCallback(ProcessExportedContent func) - { - return ExportWithCallback( - Algorithm.PublicKeySize.InitialExportBufferSizeInBytes, - static (key, dest, out written) => key.TryExportCompositeMLDsaPublicKey(dest, out written), - func); + Debug.Assert(writer.GetEncodedLength() <= capacity); + return writer; } - private delegate bool TryExportFunc(CompositeMLDsa compositeMLDsa, Span destination, out int written); private delegate TResult ProcessExportedContent(ReadOnlySpan exportedContent); - private TResult ExportWithCallback( - int initialSize, - TryExportFunc tryExportFunc, - ProcessExportedContent callbackFunc) + private TResult ExportPkcs8PrivateKeyCallback(ProcessExportedContent func) { - Debug.Assert(initialSize > 0); - - int size = initialSize; + int size = Algorithm.MaxPrivateKeySizeInBytes; byte[] buffer = CryptoPool.Rent(size); int written; - while (!tryExportFunc(this, buffer, out written)) + while (!TryExportPkcs8PrivateKeyCore(buffer, out written)) { + size = buffer.Length; CryptoPool.Return(buffer); size = checked(size * 2); buffer = CryptoPool.Rent(size); @@ -1834,7 +1910,7 @@ private TResult ExportWithCallback( try { - return callbackFunc(buffer.AsSpan(0, written)); + return func(buffer.AsSpan(0, written)); } finally { @@ -1842,20 +1918,6 @@ private TResult ExportWithCallback( } } - private byte[] ExportExactSize(int exactSize, TryExportFunc tryExportFunc) - { - byte[] ret = new byte[exactSize]; - - if (!tryExportFunc(this, ret, out int written) || written != exactSize) - { - CryptographicOperations.ZeroMemory(ret); - - throw new CryptographicException(); - } - - return ret; - } - private static CompositeMLDsaAlgorithm GetAlgorithmIdentifier(ref readonly AlgorithmIdentifierAsn identifier) { CompositeMLDsaAlgorithm? algorithm = CompositeMLDsaAlgorithm.GetAlgorithmFromOid(identifier.Algorithm); diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaAlgorithm.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaAlgorithm.cs index f7b7239204b124..bd5d12f6d6cbdd 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaAlgorithm.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaAlgorithm.cs @@ -1,11 +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; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using Internal.Cryptography; namespace System.Security.Cryptography { @@ -31,30 +28,44 @@ public sealed class CompositeMLDsaAlgorithm : IEquatable /// The maximum signature size in bytes for the composite algorithm. /// - public int MaxSignatureSizeInBytes => SignatureSize.MaximumSizeInBytes!.Value; + public int MaxSignatureSizeInBytes { get; } - internal SizeRange SignatureSize { get; } - internal SizeRange PrivateKeySize { get; } - internal SizeRange PublicKeySize { get; } + internal int MinPrivateKeySizeInBytes { get; } + internal int MaxPrivateKeySizeInBytes { get; } + internal int MinPublicKeySizeInBytes { get; } + internal int MaxPublicKeySizeInBytes { get; } + internal int MinSignatureSizeInBytes { get; } internal string Oid { get; } private CompositeMLDsaAlgorithm( string name, - SizeRange signatureSize, - SizeRange privateKeySize, - SizeRange publicKeySize, + int minPrivateKeySizeInBytes, + int maxPrivateKeySizeInBytes, + int minPublicKeySizeInBytes, + int maxPublicKeySizeInBytes, + int minSignatureSize, + int maxSignatureSize, string oid) { - Debug.Assert(signatureSize.MaximumSizeInBytes is not null); + Debug.Assert(minPrivateKeySizeInBytes <= maxPrivateKeySizeInBytes); + Debug.Assert(minPublicKeySizeInBytes <= maxPublicKeySizeInBytes); + Debug.Assert(minSignatureSize <= maxSignatureSize); Name = name; + MinPrivateKeySizeInBytes = minPrivateKeySizeInBytes; + MaxPrivateKeySizeInBytes = maxPrivateKeySizeInBytes; + MinPublicKeySizeInBytes = minPublicKeySizeInBytes; + MaxPublicKeySizeInBytes = maxPublicKeySizeInBytes; + MinSignatureSizeInBytes = minSignatureSize; + MaxSignatureSizeInBytes = maxSignatureSize; Oid = oid; - SignatureSize = signatureSize; - PrivateKeySize = privateKeySize; - PublicKeySize = publicKeySize; } + internal bool IsValidPrivateKeySize(int size) => MinPrivateKeySizeInBytes <= size && size <= MaxPrivateKeySizeInBytes; + internal bool IsValidPublicKeySize(int size) => MinPublicKeySizeInBytes <= size && size <= MaxPublicKeySizeInBytes; + internal bool IsValidSignatureSize(int size) => MinSignatureSizeInBytes <= size && size <= MaxSignatureSizeInBytes; + /// /// Gets a Composite ML-DSA algorithm identifier for the ML-DSA-44 and 2048-bit RSASSA-PSS with SHA256 algorithm. /// @@ -373,138 +384,184 @@ private CompositeMLDsaAlgorithm( private static CompositeMLDsaAlgorithm CreateRsa( string name, - MLDsaAlgorithm algorithm, + MLDsaAlgorithm mldsaAlgorithm, int keySizeInBits, string oid) { Debug.Assert(keySizeInBits % 8 == 0); int keySizeInBytes = keySizeInBits / 8; - SizeRange signatureSize = SizeRange.CreateExact(RandomizerSizeInBytes + algorithm.SignatureSizeInBytes + keySizeInBytes); - - SizeRange privateKeySize = SizeRange.CreateUnbounded( - // n must be modulus length, but other parameters can vary. This is a weak lower bound. - minimumSize: algorithm.PrivateSeedSizeInBytes + keySizeInBytes, - // n and d are about modulus length, p, q, dP, dQ, qInv are about half modulus length. - // Estimate that version and e are usually small (65537 = 3 bytes) and 64 bytes for ASN.1 overhead. - initialExportBufferSize: algorithm.PrivateSeedSizeInBytes + keySizeInBytes * 2 + (keySizeInBytes / 2) * 5 + 64); - - SizeRange publicKeySize = SizeRange.CreateUnbounded( - // n must be modulus length, but other parameters can vary. This is a weak lower bound. - minimumSize: algorithm.PublicKeySizeInBytes + keySizeInBytes, - // Estimated that e is usually small (65537 = 3 bytes) and 16 bytes for ASN.1 overhead. - initialExportBufferSize: algorithm.PublicKeySizeInBytes + keySizeInBytes + 16); - - return new CompositeMLDsaAlgorithm(name, signatureSize, privateKeySize, publicKeySize, oid); + const int MaxUniversalTagLength = 1; + + // long form prefix and 4 bytes for length. CLR arrays and spans only support length up to int.MaxValue. + // Padding with leading zero bytes is allowed, but we still limit the length to 4 bytes since the only + // plausible scenario would be encoding a 4-byte numeric data type without trimming. + // Note this bound also covers indefinite length encodings which require only 1 + 2 bytes of overhead. + const int MaxLengthLength = 1 + 4; + + const int MaxPrefixLength = MaxUniversalTagLength + MaxLengthLength; + + const int PossibleLeadingZeroByte = 1; // ASN.1 INTEGER can have a leading zero byte. + int maxKeyEncodingLength = keySizeInBytes + PossibleLeadingZeroByte; + int maxHalfKeyEncodingLength = (keySizeInBytes + 1) / 2 + PossibleLeadingZeroByte; + int maxExponentEncodingLength = 256 / 8 + PossibleLeadingZeroByte; // FIPS 186-5, 5.4 (e): The exponent e shall be an odd, positive integer such that 2^16 < e < 2^256 + + // RFC 8017, A.1.1 + // RSAPublicKey::= SEQUENCE { + // modulus INTEGER, --n + // publicExponent INTEGER --e + // } + + int maxRsaPublicKeySizeInBytes = + MaxPrefixLength + + ( + MaxPrefixLength + maxKeyEncodingLength + + MaxPrefixLength + maxExponentEncodingLength + ); + + // RFC 8017, A.1.2 + // RSAPrivateKey::= SEQUENCE { + // version Version, + // modulus INTEGER, --n + // publicExponent INTEGER, --e + // privateExponent INTEGER, --d + // prime1 INTEGER, --p + // prime2 INTEGER, --q + // exponent1 INTEGER, --d mod(p - 1) + // exponent2 INTEGER, --d mod(q - 1) + // coefficient INTEGER, --(inverse of q) mod p + // otherPrimeInfos OtherPrimeInfos OPTIONAL + // } + + int maxRsaPrivateKeySizeInBytes = + MaxPrefixLength + + ( + MaxPrefixLength + 1 + // Version should always be 0 or 1 + MaxPrefixLength + maxKeyEncodingLength + + MaxPrefixLength + maxExponentEncodingLength + + MaxPrefixLength + maxKeyEncodingLength + + MaxPrefixLength + maxHalfKeyEncodingLength + + MaxPrefixLength + maxHalfKeyEncodingLength + + MaxPrefixLength + maxHalfKeyEncodingLength + + MaxPrefixLength + maxHalfKeyEncodingLength + + MaxPrefixLength + maxHalfKeyEncodingLength + // OtherPrimeInfos omitted since multi-prime is not supported + ); + + return new CompositeMLDsaAlgorithm( + name, + mldsaAlgorithm.PrivateSeedSizeInBytes + keySizeInBytes, // Private key contains at least n + mldsaAlgorithm.PrivateSeedSizeInBytes + maxRsaPrivateKeySizeInBytes, + mldsaAlgorithm.PublicKeySizeInBytes + keySizeInBytes, // Private key contains at least n + mldsaAlgorithm.PublicKeySizeInBytes + maxRsaPublicKeySizeInBytes, + RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + keySizeInBytes, + RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + keySizeInBytes, + oid); } private static CompositeMLDsaAlgorithm CreateECDsa( string name, - MLDsaAlgorithm algorithm, + MLDsaAlgorithm mldsaAlgorithm, int keySizeInBits, string oid) { - int keySizeInBytes = (keySizeInBits + 7) / 8; - - SizeRange signatureSize = SizeRange.CreateBounded( - RandomizerSizeInBytes + algorithm.SignatureSizeInBytes, - RandomizerSizeInBytes + algorithm.SignatureSizeInBytes + AsymmetricAlgorithmHelpers.GetMaxDerSignatureSize(keySizeInBits)); - - SizeRange privateKeySize = SizeRange.CreateUnbounded( - minimumSize: algorithm.PrivateSeedSizeInBytes + 1 + keySizeInBytes, - // Add optional uncompressed public key and estimate 32 bytes for version, optional ECParameters and ASN.1 overhead. - initialExportBufferSize: algorithm.PrivateSeedSizeInBytes + 1 + keySizeInBytes + 1 + 2 * keySizeInBytes + 32); + // The key size calculation depends on the size of the curve algorithm's OID, and only includes the curves + // supported at the time of writing this code. If more curves are added, ensure the OID length is accounted for in the calculation. + Debug.Assert(oid is + Oids.MLDsa44WithECDsaP256PreHashSha256 or + Oids.MLDsa65WithECDsaP256PreHashSha512 or + Oids.MLDsa65WithECDsaP384PreHashSha512 or + Oids.MLDsa87WithECDsaP384PreHashSha512 or + Oids.MLDsa87WithECDsaP521PreHashSha512 or + Oids.MLDsa65WithECDsaBrainpoolP256r1PreHashSha512 or + Oids.MLDsa87WithECDsaBrainpoolP384r1PreHashSha512); - SizeRange publicKeySize = SizeRange.CreateExact(algorithm.PublicKeySizeInBytes + 1 + 2 * keySizeInBytes); + int keySizeInBytes = (keySizeInBits + 7) / 8; - return new CompositeMLDsaAlgorithm(name, signatureSize, privateKeySize, publicKeySize, oid); + const int MaxUniversalTagLength = 1; + + // long form prefix and 4 bytes for length. CLR arrays and spans only support length up to int.MaxValue. + // Padding with leading zero bytes is allowed, but we still limit the length to 4 bytes since the only + // plausible scenario would be encoding a 4-byte numeric data type without trimming. + // Note this bound also covers indefinite length encodings which require only 1 + 2 bytes of overhead. + const int MaxLengthLength = 1 + 4; + + const int MaxPrefixLength = MaxUniversalTagLength + MaxLengthLength; + + // RFC 5915, Section 3 + // ECPrivateKey ::= SEQUENCE { + // version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + // privateKey OCTET STRING, + // parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + // publicKey [1] BIT STRING OPTIONAL + // } + + int maxPrivateKeySizeInBytes = + MaxPrefixLength + + ( + // version + MaxPrefixLength + 1 + // Version should always be 1 + + // privateKey + MaxPrefixLength + keySizeInBytes + + + // parameters + 1 + MaxLengthLength + // Explicit tag + ( + // RFC5480, Section 2.1.1 + // ECParameters ::= CHOICE { + // namedCurve OBJECT IDENTIFIER + // -- implicitCurve NULL + // -- specifiedCurve SpecifiedECDomain + // } + // -- implicitCurve and specifiedCurve MUST NOT be used in PKIX. + // + // So this is a CHOICE with with the only option being a namedCurve OBJECT IDENTIFIER. + // + // Curve | OID | DER Encoding with prefix | Length without prefix + // ----------------|-----------------------|----------------------------------|---------------------- + // secp256r1 | 1.2.840.10045.3.1.7 | 06 08 2A 86 48 CE 3D 03 01 07 | 8 + // secp384r1 | 1.3.132.0.34 | 06 05 2B 81 04 00 22 | 5 + // secp521r1 | 1.3.132.0.35 | 06 05 2B 81 04 00 23 | 5 + // brainpoolP256r1 | 1.3.36.3.3.2.8.1.1.7 | 06 09 2B 24 03 03 02 08 01 01 07 | 9 + // brainpoolP384r1 | 1.3.36.3.3.2.8.1.1.11 | 06 09 2B 24 03 03 02 08 01 01 0B | 9 + MaxPrefixLength + 9 // This doesn't need to be exact, but it does need to consider all supported curves. + ) + + + // publicKey + 1 + MaxLengthLength + // Explicit tag + 1 + 2 * keySizeInBytes + ); + + return new CompositeMLDsaAlgorithm( + name, + mldsaAlgorithm.PrivateSeedSizeInBytes + keySizeInBytes, // ECPrivateKey has at least the private key + mldsaAlgorithm.PrivateSeedSizeInBytes + maxPrivateKeySizeInBytes, + mldsaAlgorithm.PublicKeySizeInBytes + 1 + 2 * keySizeInBytes, + mldsaAlgorithm.PublicKeySizeInBytes + 1 + 2 * keySizeInBytes, + RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + 2 + 3 * 2, // 2 non-zero INTEGERS and overhead for 3 ASN.1 values + RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + AsymmetricAlgorithmHelpers.GetMaxDerSignatureSize(keySizeInBits), + oid); } private static CompositeMLDsaAlgorithm CreateEdDsa( string name, - MLDsaAlgorithm algorithm, + MLDsaAlgorithm mldsaAlgorithm, int keySizeInBits, string oid) { Debug.Assert(keySizeInBits % 8 == 0); int keySizeInBytes = keySizeInBits / 8; - SizeRange signatureSize = SizeRange.CreateExact(RandomizerSizeInBytes + algorithm.SignatureSizeInBytes + 2 * keySizeInBytes); - SizeRange privateKeySize = SizeRange.CreateExact(algorithm.PrivateSeedSizeInBytes + keySizeInBytes); - SizeRange publicKeySize = SizeRange.CreateExact(algorithm.PublicKeySizeInBytes + keySizeInBytes); - - return new CompositeMLDsaAlgorithm(name, signatureSize, privateKeySize, publicKeySize, oid); - } - - internal abstract class SizeRange - { - internal abstract bool IsExact { get; } - internal abstract int MinimumSizeInBytes { get; } - internal abstract int? MaximumSizeInBytes { get; } - internal abstract int InitialExportBufferSizeInBytes { get; } - - internal static SizeRange CreateExact(int size) - { - Debug.Assert(size >= 0); - - return new ExactSize(size); - } - - internal static SizeRange CreateBounded(int minimumSize, int maximumSize) - { - Debug.Assert(minimumSize >= 0); - Debug.Assert(maximumSize >= minimumSize); - - return minimumSize == maximumSize ? new ExactSize(minimumSize) : new VariableSize(minimumSize, maximumSize, maximumSize); - } - - internal static SizeRange CreateUnbounded(int minimumSize, int initialExportBufferSize) - { - Debug.Assert(minimumSize >= 0); - Debug.Assert(initialExportBufferSize >= minimumSize); - - return new VariableSize(minimumSize, null, initialExportBufferSize); - } - - internal bool IsValidSize(int size) - { - return size >= MinimumSizeInBytes && size <= MaximumSizeInBytes.GetValueOrDefault(int.MaxValue); - } - - internal bool IsAlwaysLargerThan(int size) - { - return size < MinimumSizeInBytes; - } - - private sealed class ExactSize : SizeRange - { - private readonly int _size; - - internal ExactSize(int size) - { - _size = size; - } - - internal override bool IsExact => true; - internal override int MinimumSizeInBytes => _size; - internal override int? MaximumSizeInBytes => _size; - internal override int InitialExportBufferSizeInBytes => _size; - } - - private sealed class VariableSize : SizeRange - { - internal VariableSize(int minimumSize, int? maximumSize, int initialExportBufferSize) - { - MinimumSizeInBytes = minimumSize; - MaximumSizeInBytes = maximumSize; - InitialExportBufferSizeInBytes = initialExportBufferSize; - } - - internal override bool IsExact => false; - internal override int MinimumSizeInBytes { get; } - internal override int? MaximumSizeInBytes { get; } - internal override int InitialExportBufferSizeInBytes { get; } - } + return new CompositeMLDsaAlgorithm( + name, + mldsaAlgorithm.PrivateSeedSizeInBytes + keySizeInBytes, + mldsaAlgorithm.PrivateSeedSizeInBytes + keySizeInBytes, + mldsaAlgorithm.PublicKeySizeInBytes + keySizeInBytes, + mldsaAlgorithm.PublicKeySizeInBytes + keySizeInBytes, + RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + 2 * keySizeInBytes, + RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + 2 * keySizeInBytes, + oid); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaCng.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaCng.Windows.cs index a4ae67443b2c5c..4b1165677d5c63 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaCng.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaCng.Windows.cs @@ -13,11 +13,11 @@ protected override int SignDataCore(ReadOnlySpan data, ReadOnlySpan throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa))); /// - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) => throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa))); /// - protected override bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) => throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa))); /// diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.NotSupported.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.NotSupported.cs index 7c7210100aa1f2..0c4b3461d90bc6 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.NotSupported.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.NotSupported.cs @@ -37,10 +37,10 @@ protected override bool VerifyDataCore(ReadOnlySpan data, ReadOnlySpan destination, out int bytesWritten) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) => throw new PlatformNotSupportedException(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.Windows.cs index a76d9e2893864a..dc0fa563d9f7be 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaImplementation.Windows.cs @@ -72,10 +72,10 @@ protected override bool VerifyDataCore(ReadOnlySpan data, ReadOnlySpan destination, out int bytesWritten) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) => throw new PlatformNotSupportedException(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs index 2491364550cd7b..5bb63453387841 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs @@ -370,26 +370,29 @@ protected override bool VerifyDataCore(ReadOnlySpan data, ReadOnlySpan destination, out int bytesWritten) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten) + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) { // draft-ietf-lamps-pq-composite-sigs-latest (June 20, 2025), 5.1 // 1. Combine and output the encoded public key // // output mldsaPK || tradPK + int bytesWritten = 0; + _mldsa.ExportMLDsaPublicKey(destination.Slice(0, AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes)); + bytesWritten += AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes; - if (_componentAlgorithm.TryExportPublicKey(destination.Slice(AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes), out int componentBytesWritten)) + if (!_componentAlgorithm.TryExportPublicKey(destination.Slice(AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes), out int componentBytesWritten)) { - bytesWritten = AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes + componentBytesWritten; - return true; + throw new CryptographicException(); } - bytesWritten = 0; - return false; + bytesWritten += componentBytesWritten; + + return bytesWritten; } - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) { // draft-ietf-lamps-pq-composite-sigs-latest (June 20, 2025), 5.2 // 1. Combine and output the encoded private key @@ -398,16 +401,19 @@ protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destina try { + int bytesWritten = 0; + _mldsa.ExportMLDsaPrivateSeed(destination.Slice(0, AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes)); + bytesWritten += AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes; - if (_componentAlgorithm.TryExportPrivateKey(destination.Slice(AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes), out int componentBytesWritten)) + if (!_componentAlgorithm.TryExportPrivateKey(destination.Slice(AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes), out int componentBytesWritten)) { - bytesWritten = AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes + componentBytesWritten; - return true; + throw new CryptographicException(); } - bytesWritten = 0; - return false; + bytesWritten += componentBytesWritten; + + return bytesWritten; } catch (CryptographicException) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs b/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs index ed2d5d7ad73dde..1857fd43e63b7b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs @@ -45,6 +45,8 @@ internal static void Return(byte[] array, int clearSize = ClearAll) internal Span Span { get; private set; } + internal readonly bool IsRented => _rented is not null; + public void Dispose() { Return(); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaContractTests.cs index c4c0f754db9aba..5e1dd854f41660 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaContractTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaContractTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using Xunit; -using Xunit.Sdk; using CompositeMLDsaTestVector = System.Security.Cryptography.Tests.CompositeMLDsaTestData.CompositeMLDsaTestVector; @@ -62,89 +61,199 @@ public static void ArgumentValidation(CompositeMLDsaAlgorithm algorithm, bool sh AssertExtensions.Throws("context", () => dsa.VerifyData(ReadOnlySpan.Empty, new byte[maxSignatureSize], new byte[256])); AssertExtensions.Throws("context", () => dsa.VerifyData(Array.Empty(), new byte[maxSignatureSize], new byte[256])); } + [Theory] [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] public static void TryExportCompositeMLDsaPublicKey_LowerBound(CompositeMLDsaAlgorithm algorithm) { using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); - int lowerBound = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => rsa.KeySizeInBits / 8, - ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int lowerBound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); - AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[lowerBound - 1], out int bytesWritten)); + // Buffer is too small + byte[] bytes = new byte[lowerBound - 1]; + AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPublicKey(bytes, out int bytesWritten)); Assert.Equal(0, bytesWritten); - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (destination, out bytesWritten) => + // Buffer meets the lower bound + bytes = new byte[lowerBound]; + + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => { - AssertExtensions.LessThanOrEqualTo(lowerBound, destination.Length); - bytesWritten = lowerBound; - return true; + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + return lowerBound; }; - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[lowerBound], out bytesWritten)); + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(bytes, out bytesWritten)); Assert.Equal(lowerBound, bytesWritten); - - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[lowerBound + 1], out bytesWritten)); + AssertExtensions.FilledWith(1, bytes); Assert.Equal(lowerBound, bytesWritten); - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (destination, out bytesWritten) => + // Buffer meets the lower bound, but returned value is too small + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => { + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + // Writing less than lower bound isn't allowed. - bytesWritten = lowerBound - 1; - return true; + return lowerBound - 1; }; - Assert.Throws(() => dsa.TryExportCompositeMLDsaPublicKey(new byte[lowerBound], out bytesWritten)); + Assert.Throws(() => dsa.TryExportCompositeMLDsaPublicKey(bytes, out bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); Assert.Equal(0, bytesWritten); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPublicKey_Span_LowerBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int lowerBound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); + + byte[] bytes = new byte[lowerBound]; + + // Buffer is too small + Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey(bytes.AsSpan(0, lowerBound - 1))); + AssertExtensions.FilledWith(0, bytes); + + // Buffer meets the lower bound + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + return lowerBound; + }; + + int bytesWritten = dsa.ExportCompositeMLDsaPublicKey(bytes.AsSpan(0, lowerBound)); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + AssertExtensions.FilledWith(1, bytes); + Assert.Equal(lowerBound, bytesWritten); + + // Buffer meets the lower bound, but returned value is too small + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + // Writing less than lower bound isn't allowed. + return lowerBound - 1; + }; - Assert.Throws(dsa.ExportCompositeMLDsaPublicKey); + Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey(bytes.AsSpan(0, lowerBound))); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); } [Theory] [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportCompositeMLDsaPublicKey_UpperBound(CompositeMLDsaAlgorithm algorithm) + public static void ExportCompositeMLDsaPublicKey_Array_LowerBound(CompositeMLDsaAlgorithm algorithm) { using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); - int? upperBoundOrNull = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => default(int?), - ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int lowerBound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); - if (upperBoundOrNull is null) + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => { - return; - } + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); - int upperBound = upperBoundOrNull.Value; + // Writing less than lower bound isn't allowed. + return lowerBound - 1; + }; - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (destination, out bytesWritten) => + Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey()); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void TryExportCompositeMLDsaPublicKey_UpperBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int upperBound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); + + // Buffer is the max size + byte[] bytes = new byte[upperBound]; + + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => { - bytesWritten = upperBound; - return true; + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + + return upperBound; }; - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[upperBound], out int bytesWritten)); - Assert.Equal(upperBound, bytesWritten); + dsa.AddDestinationBufferIsSameAssertion(bytes); - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[upperBound + 1], out bytesWritten)); + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(bytes, out int bytesWritten)); Assert.Equal(upperBound, bytesWritten); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + AssertExtensions.FilledWith(1, bytes); - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (destination, out bytesWritten) => + // Buffer is the max size, but returned value is too big + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => { // Writing more than upper bound isn't allowed. - bytesWritten = upperBound + 1; - return true; + return upperBound + 1; }; - Assert.Throws(() => dsa.TryExportCompositeMLDsaPublicKey(new byte[upperBound + 1], out bytesWritten)); + dsa.AddDestinationBufferIsSameAssertion(bytes); + + Assert.Throws(() => dsa.TryExportCompositeMLDsaPublicKey(bytes, out bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); Assert.Equal(0, bytesWritten); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPublicKey_Span_UpperBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int upperBound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); + + byte[] bytes = new byte[upperBound]; + + // Buffer is the max size + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + return upperBound; + }; + + dsa.AddDestinationBufferIsSameAssertion(bytes); + + int bytesWritten = dsa.ExportCompositeMLDsaPublicKey(bytes); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + AssertExtensions.FilledWith(1, bytes); + Assert.Equal(upperBound, bytesWritten); + + // Buffer is the max size, but returned value is too big + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + // Writing more than upper bound isn't allowed. + return upperBound + 1; + }; + + dsa.AddDestinationBufferIsSameAssertion(bytes); - Assert.Throws(dsa.ExportCompositeMLDsaPublicKey); + Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey(bytes.AsSpan(0, upperBound))); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPublicKey_Array_UpperBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int upperBound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); + + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + // Writing more than upper bound isn't allowed. + return upperBound + 1; + }; + + Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey()); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); } [Theory] @@ -152,84 +261,216 @@ public static void TryExportCompositeMLDsaPublicKey_UpperBound(CompositeMLDsaAlg public static void TryExportCompositeMLDsaPrivateKey_LowerBound(CompositeMLDsaAlgorithm algorithm) { using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); - int lowerBound = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => rsa.KeySizeInBits / 8, - ecdsa => 1 + ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int lowerBound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); - AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[lowerBound - 1], out int bytesWritten)); + // Buffer is too small + byte[] bytes = new byte[lowerBound - 1]; + AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPrivateKey(bytes, out int bytesWritten)); Assert.Equal(0, bytesWritten); - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (destination, out bytesWritten) => + // Buffer meets the lower bound + bytes = new byte[lowerBound]; + + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => { - AssertExtensions.LessThanOrEqualTo(lowerBound, destination.Length); - bytesWritten = lowerBound; - return true; + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + return lowerBound; }; - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[lowerBound], out bytesWritten)); + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(bytes, out bytesWritten)); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(1, bytes); Assert.Equal(lowerBound, bytesWritten); - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[lowerBound + 1], out bytesWritten)); + // Buffer meets the lower bound, but returned value is too small + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + + // Writing less than lower bound isn't allowed. + return lowerBound - 1; + }; + + bytes.AsSpan().Clear(); + + Assert.Throws(() => dsa.TryExportCompositeMLDsaPrivateKey(bytes, out bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(0, bytes); + Assert.Equal(0, bytesWritten); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPrivateKey_Span_LowerBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int lowerBound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); + + byte[] bytes = new byte[lowerBound]; + + // Buffer is too small + Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey(bytes.AsSpan(0, lowerBound - 1))); + AssertExtensions.FilledWith(0, bytes); + + // Buffer meets the lower bound + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + return lowerBound; + }; + + int bytesWritten = dsa.ExportCompositeMLDsaPrivateKey(bytes.AsSpan(0, lowerBound)); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(1, bytes); Assert.Equal(lowerBound, bytesWritten); - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (destination, out bytesWritten) => + // Buffer meets the lower bound, but returned value is too small + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => { + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); + // Writing less than lower bound isn't allowed. - bytesWritten = lowerBound - 1; - return true; + return lowerBound - 1; }; - Assert.Throws(() => dsa.TryExportCompositeMLDsaPrivateKey(new byte[lowerBound], out bytesWritten)); - Assert.Equal(0, bytesWritten); + bytes.AsSpan().Clear(); - Assert.Throws(dsa.ExportCompositeMLDsaPrivateKey); + Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey(bytes.AsSpan(0, lowerBound))); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(0, bytes); } [Theory] [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportCompositeMLDsaPrivateKey_UpperBound(CompositeMLDsaAlgorithm algorithm) + public static void ExportCompositeMLDsaPrivateKey_Array_LowerBound(CompositeMLDsaAlgorithm algorithm) { using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); - int? upperBoundOrNull = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => default(int?), - ecdsa => default(int?), - eddsa => eddsa.KeySizeInBits / 8); + int lowerBound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); - if (upperBoundOrNull is null) + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => { - return; - } + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); + destination.Fill(1); - int upperBound = upperBoundOrNull.Value; + // Writing less than lower bound isn't allowed. + return lowerBound - 1; + }; - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (destination, out bytesWritten) => + Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey()); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void TryExportCompositeMLDsaPrivateKey_UpperBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int upperBound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); + + // Buffer is the max size + byte[] bytes = new byte[upperBound]; + + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => { - bytesWritten = upperBound; - return true; + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + + return upperBound; }; - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[upperBound], out int bytesWritten)); - Assert.Equal(upperBound, bytesWritten); + dsa.AddDestinationBufferIsSameAssertion(bytes); - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[upperBound + 1], out bytesWritten)); + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(bytes, out int bytesWritten)); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); Assert.Equal(upperBound, bytesWritten); + AssertExtensions.FilledWith(1, bytes); - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (destination, out bytesWritten) => + // Buffer is the max size, but returned value is too big + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => { + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + // Writing more than upper bound isn't allowed. - bytesWritten = upperBound + 1; - return true; + return upperBound + 1; }; - Assert.Throws(() => dsa.TryExportCompositeMLDsaPrivateKey(new byte[upperBound + 1], out bytesWritten)); + dsa.AddDestinationBufferIsSameAssertion(bytes); + + bytes.AsSpan().Clear(); + + Assert.Throws(() => dsa.TryExportCompositeMLDsaPrivateKey(bytes, out bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); Assert.Equal(0, bytesWritten); + AssertExtensions.FilledWith(0, bytes); + } - Assert.Throws(dsa.ExportCompositeMLDsaPrivateKey); + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPrivateKey_Span_UpperBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int upperBound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); + + byte[] bytes = new byte[upperBound]; + + // Buffer is the max size + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + + return upperBound; + }; + + dsa.AddDestinationBufferIsSameAssertion(bytes); + + int bytesWritten = dsa.ExportCompositeMLDsaPrivateKey(bytes); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(1, bytes); + Assert.Equal(upperBound, bytesWritten); + + // Buffer is the max size, but returned value is too big + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + + // Writing more than upper bound isn't allowed. + return upperBound + 1; + }; + + dsa.AddDestinationBufferIsSameAssertion(bytes); + + bytes.AsSpan().Clear(); + + Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey(bytes.AsSpan(0, upperBound))); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(0, bytes); + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPrivateKey_Array_UpperBound(CompositeMLDsaAlgorithm algorithm) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int upperBound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); + + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + Assert.Equal(upperBound, destination.Length); + destination.Fill(1); + + // Writing more than upper bound isn't allowed. + return upperBound + 1; + }; + + Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey()); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); } [Theory] @@ -241,7 +482,7 @@ public static void SignData_LowerBound(CompositeMLDsaAlgorithm algorithm) CompositeMLDsaTestHelpers.ExecuteComponentFunc( algorithm, rsa => rsa.KeySizeInBits / 8, - ecdsa => 0, + ecdsa => 8, eddsa => 2 * eddsa.KeySizeInBits / 8); Assert.Throws(() => dsa.SignData(ReadOnlySpan.Empty, new byte[lowerBound - 1])); @@ -249,7 +490,7 @@ public static void SignData_LowerBound(CompositeMLDsaAlgorithm algorithm) dsa.SignDataCoreHook = (data, context, destination) => { - AssertExtensions.LessThanOrEqualTo(lowerBound, destination.Length); + AssertExtensions.GreaterThanOrEqualTo(destination.Length, lowerBound); return lowerBound; }; @@ -303,9 +544,9 @@ public static void VerifyData_Threshold(CompositeMLDsaAlgorithm algorithm) int threshold = CompositeMLDsaTestHelpers.ExecuteComponentFunc( algorithm, - _ => algorithm.MaxSignatureSizeInBytes, - _ => 32 + CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].SignatureSizeInBytes, - _ => algorithm.MaxSignatureSizeInBytes); + rsa => algorithm.MaxSignatureSizeInBytes, + ecdsa => 32 + CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].SignatureSizeInBytes + 8, + eddsa => algorithm.MaxSignatureSizeInBytes); AssertExtensions.FalseExpression(dsa.VerifyData(ReadOnlySpan.Empty, new byte[threshold - 1])); @@ -315,61 +556,87 @@ public static void VerifyData_Threshold(CompositeMLDsaAlgorithm algorithm) } [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void ExportCompositeMLDsaPublicKey_InitialBuffer(CompositeMLDsaTestVector vector) + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPublicKey_BufferSize(CompositeMLDsaAlgorithm algorithm) { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - int initialBufferSize = -1; + int maxPublicKeySize = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); + int minPublicKeySize = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (destination, out bytesWritten) => - { - // Buffer is always big enough, but it may bee too big for a valid key, so bound it with an actual key. - bytesWritten = Math.Min(vector.PublicKey.Length, destination.Length); - initialBufferSize = destination.Length; - return true; - }; + TestWithMockKeySize(algorithm, minPublicKeySize, minPublicKeySize); + TestWithMockKeySize(algorithm, minPublicKeySize, (minPublicKeySize + maxPublicKeySize) / 2); + TestWithMockKeySize(algorithm, minPublicKeySize, maxPublicKeySize); + TestWithMockKeySize(algorithm, minPublicKeySize, maxPublicKeySize + 1); + + TestWithMockKeySize(algorithm, maxPublicKeySize, maxPublicKeySize); + TestWithMockKeySize(algorithm, maxPublicKeySize, maxPublicKeySize + 1); - _ = dsa.ExportCompositeMLDsaPublicKey(); + TestWithMockKeySize(algorithm, (minPublicKeySize + maxPublicKeySize) / 2, (minPublicKeySize + maxPublicKeySize) / 2); + TestWithMockKeySize(algorithm, (minPublicKeySize + maxPublicKeySize) / 2, maxPublicKeySize); + TestWithMockKeySize(algorithm, (minPublicKeySize + maxPublicKeySize) / 2, maxPublicKeySize + 1); + + static void TestWithMockKeySize(CompositeMLDsaAlgorithm algorithm, int mockKeySize, int inputBufferSize) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + // Buffer size must always be upper bound for the key length. + Assert.Equal(CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm), destination.Length); + return mockKeySize; + }; - int mldsaKeySize = CompositeMLDsaTestHelpers.MLDsaAlgorithms[vector.Algorithm].PublicKeySizeInBytes; + byte[] bytes = dsa.ExportCompositeMLDsaPublicKey(); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + Assert.Equal(mockKeySize, bytes.Length); - CompositeMLDsaTestHelpers.ExecuteComponentAction( - vector.Algorithm, - // RSA doesn't have an exact size, so it will use pooled buffers. Their sizes are powers of two. - rsa => AssertExtensions.LessThanOrEqualTo(mldsaKeySize + (rsa.KeySizeInBits / 8) * 2 + 16, initialBufferSize), - ecdsa => Assert.Equal(mldsaKeySize + 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), initialBufferSize), - eddsa => Assert.Equal(mldsaKeySize + eddsa.KeySizeInBits / 8, initialBufferSize)); + Assert.Equal(mockKeySize, dsa.ExportCompositeMLDsaPublicKey(new byte[inputBufferSize])); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); - AssertExtensions.Equal(1, dsa.TryExportCompositeMLDsaPublicKeyCoreCallCount); + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[inputBufferSize], out int bytesWritten)); + Assert.Equal(3, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + Assert.Equal(mockKeySize, bytesWritten); + } } [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void ExportCompositeMLDsaPrivateKey_InitialBuffer(CompositeMLDsaTestVector vector) + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void ExportCompositeMLDsaPrivateKey_BufferSize(CompositeMLDsaAlgorithm algorithm) { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - int initialBufferSize = -1; + int maxPrivateKeySize = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); + int minPrivateKeySize = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (destination, out bytesWritten) => - { - // Buffer is always big enough, but it may be too big for a valid key, so bound it with an actual key. - bytesWritten = Math.Min(vector.SecretKey.Length, destination.Length); - initialBufferSize = destination.Length; - return true; - }; + TestWithMockKeySize(algorithm, minPrivateKeySize, minPrivateKeySize); + TestWithMockKeySize(algorithm, minPrivateKeySize, (minPrivateKeySize + maxPrivateKeySize) / 2); + TestWithMockKeySize(algorithm, minPrivateKeySize, maxPrivateKeySize); + TestWithMockKeySize(algorithm, minPrivateKeySize, maxPrivateKeySize + 1); - _ = dsa.ExportCompositeMLDsaPrivateKey(); + TestWithMockKeySize(algorithm, maxPrivateKeySize, maxPrivateKeySize); + TestWithMockKeySize(algorithm, maxPrivateKeySize, maxPrivateKeySize + 1); - int mldsaKeySize = CompositeMLDsaTestHelpers.MLDsaAlgorithms[vector.Algorithm].PrivateSeedSizeInBytes; + TestWithMockKeySize(algorithm, (minPrivateKeySize + maxPrivateKeySize) / 2, (minPrivateKeySize + maxPrivateKeySize) / 2); + TestWithMockKeySize(algorithm, (minPrivateKeySize + maxPrivateKeySize) / 2, maxPrivateKeySize); + TestWithMockKeySize(algorithm, (minPrivateKeySize + maxPrivateKeySize) / 2, maxPrivateKeySize + 1); - CompositeMLDsaTestHelpers.ExecuteComponentAction( - vector.Algorithm, - // RSA and ECDSA don't have an exact size, so it will use pooled buffers. Their sizes are powers of two. - rsa => AssertExtensions.LessThanOrEqualTo(mldsaKeySize + (rsa.KeySizeInBits / 8) * 2 + (rsa.KeySizeInBits / 8) / 2 * 5 + 64, initialBufferSize), - ecdsa => AssertExtensions.LessThanOrEqualTo(mldsaKeySize + 1 + ((ecdsa.KeySizeInBits + 7) / 8) + 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8) + 64, initialBufferSize), - eddsa => Assert.Equal(mldsaKeySize + eddsa.KeySizeInBits / 8, initialBufferSize)); + static void TestWithMockKeySize(CompositeMLDsaAlgorithm algorithm, int mockKeySize, int inputBufferSize) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + // Buffer size must always be upper bound for the key length. + Assert.Equal(CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm), destination.Length); + return mockKeySize; + }; + + byte[] bytes = dsa.ExportCompositeMLDsaPrivateKey(); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(mockKeySize, bytes.Length); - AssertExtensions.Equal(1, dsa.TryExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(mockKeySize, dsa.ExportCompositeMLDsaPrivateKey(new byte[inputBufferSize])); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[inputBufferSize], out int bytesWritten)); + Assert.Equal(3, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(mockKeySize, bytesWritten); + } } [Theory] @@ -405,53 +672,89 @@ public static void SignData_BufferSize(CompositeMLDsaTestVector vector) private const int PaddingSize = 10; [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportCompositeMLDsaPublicKey_CallsCore(CompositeMLDsaTestVector vector) + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void TryExportCompositeMLDsaPublicKey_CallsCore(CompositeMLDsaAlgorithm algorithm) { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (_, out x) => { x = 42; return true; }; - dsa.AddFillDestination(vector.PublicKey); + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int maxPublicKeySize = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); + int minPublicKeySize = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); + + int keySize = (minPublicKeySize + maxPublicKeySize) / 2; + + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + // Filling past the expected size is allowed, but ignored. + destination.Fill(1); + return keySize; + }; byte[] exported = dsa.ExportCompositeMLDsaPublicKey(); - AssertExtensions.LessThan(0, dsa.TryExportCompositeMLDsaPublicKeyCoreCallCount); - AssertExtensions.SequenceEqual(exported, vector.PublicKey); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + AssertExtensions.FilledWith(1, exported); + Assert.Equal(keySize, exported.Length); - byte[] publicKey = CreatePaddedFilledArray(vector.PublicKey.Length, 42); + byte[] publicKey = CreatePaddedFilledArray(keySize, 42); - // Extra bytes in destination buffer should not be touched - Memory destination = publicKey.AsMemory(PaddingSize, vector.PublicKey.Length); - dsa.AddDestinationBufferIsSameAssertion(destination); - dsa.TryExportCompositeMLDsaPublicKeyCoreCallCount = 0; + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(publicKey.AsSpan(PaddingSize, keySize), out int bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + Assert.Equal(keySize, bytesWritten); + + // Padding should not be touched + AssertExtensions.FilledWith(42, publicKey.AsSpan(0, PaddingSize)); + AssertExtensions.FilledWith(1, publicKey.AsSpan(PaddingSize, keySize)); + AssertExtensions.FilledWith(42, publicKey.AsSpan(PaddingSize + keySize)); + + publicKey = CreatePaddedFilledArray(keySize, 42); - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(destination.Span, out int bytesWritten)); - Assert.Equal(vector.PublicKey.Length, bytesWritten); - Assert.Equal(1, dsa.TryExportCompositeMLDsaPublicKeyCoreCallCount); - AssertExpectedFill(publicKey, vector.PublicKey, PaddingSize, 42); + Assert.Equal(keySize, dsa.ExportCompositeMLDsaPublicKey(publicKey.AsSpan(PaddingSize, keySize))); + Assert.Equal(3, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + + AssertExtensions.FilledWith(42, publicKey.AsSpan(0, PaddingSize)); + AssertExtensions.FilledWith(1, publicKey.AsSpan(PaddingSize, keySize)); + AssertExtensions.FilledWith(42, publicKey.AsSpan(PaddingSize + keySize)); } [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportCompositeMLDsaPrivateKey_CallsCore(CompositeMLDsaTestVector vector) + [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void TryExportCompositeMLDsaPrivateKey_CallsCore(CompositeMLDsaAlgorithm algorithm) { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (_, out x) => { x = 42; return true; }; - dsa.AddFillDestination(vector.SecretKey); + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); + int maxPrivateKeySize = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); + int minPrivateKeySize = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); + + int keySize = (minPrivateKeySize + maxPrivateKeySize) / 2; + + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + // Filling past the expected size is allowed, but ignored. + destination.Fill(1); + return keySize; + }; byte[] exported = dsa.ExportCompositeMLDsaPrivateKey(); - AssertExtensions.LessThan(0, dsa.TryExportCompositeMLDsaPrivateKeyCoreCallCount); - AssertExtensions.SequenceEqual(exported, vector.SecretKey); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(1, exported); + Assert.Equal(keySize, exported.Length); - byte[] secretKey = CreatePaddedFilledArray(vector.SecretKey.Length, 42); + byte[] privateKey = CreatePaddedFilledArray(keySize, 42); - // Extra bytes in destination buffer should not be touched - Memory destination = secretKey.AsMemory(PaddingSize, vector.SecretKey.Length); - dsa.AddDestinationBufferIsSameAssertion(destination); - dsa.TryExportCompositeMLDsaPrivateKeyCoreCallCount = 0; + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(privateKey.AsSpan(PaddingSize, keySize), out int bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(keySize, bytesWritten); + + // Padding should not be touched + AssertExtensions.FilledWith(42, privateKey.AsSpan(0, PaddingSize)); + AssertExtensions.FilledWith(1, privateKey.AsSpan(PaddingSize, keySize)); + AssertExtensions.FilledWith(42, privateKey.AsSpan(PaddingSize + keySize)); + + privateKey = CreatePaddedFilledArray(keySize, 42); + + Assert.Equal(keySize, dsa.ExportCompositeMLDsaPrivateKey(privateKey.AsSpan(PaddingSize, keySize))); + Assert.Equal(3, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); - AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(destination.Span, out int bytesWritten)); - Assert.Equal(vector.SecretKey.Length, bytesWritten); - Assert.Equal(1, dsa.TryExportCompositeMLDsaPrivateKeyCoreCallCount); - AssertExpectedFill(secretKey, vector.SecretKey, PaddingSize, 42); + AssertExtensions.FilledWith(42, privateKey.AsSpan(0, PaddingSize)); + AssertExtensions.FilledWith(1, privateKey.AsSpan(PaddingSize, keySize)); + AssertExtensions.FilledWith(42, privateKey.AsSpan(PaddingSize + keySize)); } [Theory] @@ -498,93 +801,103 @@ public static void VerifyData_CallsCore(CompositeMLDsaTestVector vector) AssertExtensions.Equal(2, dsa.VerifyDataCoreCallCount); } - [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportCompositeMLDsaPublicKey_CoreReturnsFals(CompositeMLDsaTestVector vector) - { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = (_, out w) => { w = 0; return false; }; - AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPublicKey(new byte[vector.PublicKey.Length], out int bytesWritten)); - Assert.Equal(0, bytesWritten); - } - - [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportCompositeMLDsaPrivateKey_CoreReturnsFalse(CompositeMLDsaTestVector vector) - { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = (_, out w) => { w = 0; return false; }; - AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPrivateKey(new byte[vector.SecretKey.Length], out int bytesWritten)); - Assert.Equal(0, bytesWritten); - } - - [Theory] - [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void VerifyData_CoreReturnsFalse(CompositeMLDsaTestVector vector) - { - using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); - dsa.VerifyDataCoreHook = (_, _, _) => false; - AssertExtensions.FalseExpression(dsa.VerifyData(vector.Message, vector.Signature, Array.Empty())); - } - [Theory] [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportPublicKeyCore_ExactSize_ReturnFalse(CompositeMLDsaAlgorithm algorithm) + public static void ExportCompositeMLDsaPublicKey_MaxSizeKey_MinSizeDestination(CompositeMLDsaAlgorithm algorithm) { using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); - int? exactPublicKeySize = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => default(int?), - ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int maxKeyLength = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); + int minKeyLength = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); + byte[] minSizeKey = new byte[minKeyLength]; - if (exactPublicKeySize is null) - return; + dsa.ExportCompositeMLDsaPublicKeyCoreHook = destination => + { + Assert.Equal(maxKeyLength, destination.Length); + destination.Fill(1); + return maxKeyLength; + }; - dsa.TryExportCompositeMLDsaPublicKeyCoreHook = - (destination, out w) => - { - int expectedSize = exactPublicKeySize.Value; - Assert.Equal(expectedSize, destination.Length); + if (minKeyLength != maxKeyLength) + { + Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey(minSizeKey)); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + AssertExtensions.FilledWith(0, minSizeKey); + + AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPublicKey(minSizeKey, out int bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + Assert.Equal(0, bytesWritten); + AssertExtensions.FilledWith(0, minSizeKey); + } + else + { + dsa.AddDestinationBufferIsSameAssertion(minSizeKey); - // Destination is exactly sized, so this should never return false. - // Caller should validate and throw. - w = 0; - return false; - }; + int bytesWritten = dsa.ExportCompositeMLDsaPublicKey(minSizeKey); + Assert.Equal(1, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + Assert.Equal(minKeyLength, bytesWritten); + AssertExtensions.FilledWith(1, minSizeKey); - Assert.Throws(() => dsa.ExportCompositeMLDsaPublicKey()); + minSizeKey.AsSpan().Clear(); + + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPublicKey(minSizeKey, out bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPublicKeyCoreCallCount); + Assert.Equal(minKeyLength, bytesWritten); + AssertExtensions.FilledWith(1, minSizeKey); + } } [Theory] [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] - public static void TryExportPrivateKeyCore_ExactSize_ReturnFalse(CompositeMLDsaAlgorithm algorithm) + public static void ExportCompositeMLDsaPrivateKey_MaxSizeKey_MinSizeDestination(CompositeMLDsaAlgorithm algorithm) { using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(algorithm); - int? exactPrivateKeySize = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => default(int?), - ecdsa => default(int?), - eddsa => eddsa.KeySizeInBits / 8); + int maxKeyLength = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); + int minKeyLength = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); + byte[] minSizeKey = new byte[minKeyLength]; - if (exactPrivateKeySize is null) - return; + dsa.ExportCompositeMLDsaPrivateKeyCoreHook = destination => + { + Assert.Equal(maxKeyLength, destination.Length); + destination.Fill(1); + return maxKeyLength; + }; - dsa.TryExportCompositeMLDsaPrivateKeyCoreHook = - (destination, out w) => - { - int expectedSize = exactPrivateKeySize.Value; - Assert.Equal(expectedSize, destination.Length); + if (minKeyLength != maxKeyLength) + { + Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey(minSizeKey)); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + AssertExtensions.FilledWith(0, minSizeKey); + + AssertExtensions.FalseExpression(dsa.TryExportCompositeMLDsaPrivateKey(minSizeKey, out int bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(0, bytesWritten); + AssertExtensions.FilledWith(0, minSizeKey); + } + else + { + dsa.AddDestinationBufferIsSameAssertion(minSizeKey); - // Destination is exactly sized, so this should never return false. - // Caller should validate and throw. - w = 0; - return false; - }; + int bytesWritten = dsa.ExportCompositeMLDsaPrivateKey(minSizeKey); + Assert.Equal(1, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(minKeyLength, bytesWritten); + AssertExtensions.FilledWith(1, minSizeKey); - Assert.Throws(() => dsa.ExportCompositeMLDsaPrivateKey()); + minSizeKey.AsSpan().Clear(); + + AssertExtensions.TrueExpression(dsa.TryExportCompositeMLDsaPrivateKey(minSizeKey, out bytesWritten)); + Assert.Equal(2, dsa.ExportCompositeMLDsaPrivateKeyCoreCallCount); + Assert.Equal(minKeyLength, bytesWritten); + AssertExtensions.FilledWith(1, minSizeKey); + } + } + + [Theory] + [MemberData(nameof(CompositeMLDsaTestData.AllIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))] + public static void VerifyData_CoreReturnsFalse(CompositeMLDsaTestVector vector) + { + using CompositeMLDsaMockImplementation dsa = CompositeMLDsaMockImplementation.Create(vector.Algorithm); + dsa.VerifyDataCoreHook = (_, _, _) => false; + AssertExtensions.FalseExpression(dsa.VerifyData(vector.Message, vector.Signature, Array.Empty())); } [Theory] diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs index 84e6cca5aee07a..24617d110df62c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs @@ -107,12 +107,7 @@ public static void ImportBadPrivateKey_ECDsa_WrongAlgorithm() [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] public static void ImportPrivateKey_LowerBound(CompositeMLDsaAlgorithm algorithm) { - int bound = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => rsa.KeySizeInBits / 8, - ecdsa => 1 + ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int bound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeLowerBound(algorithm); AssertImportBadPrivateKey(algorithm, new byte[bound - 1]); } @@ -121,15 +116,9 @@ public static void ImportPrivateKey_LowerBound(CompositeMLDsaAlgorithm algorithm [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] public static void ImportPrivateKey_UpperBound(CompositeMLDsaAlgorithm algorithm) { - int? bound = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => default(int?), - ecdsa => default(int?), - eddsa => eddsa.KeySizeInBits / 8); + int bound = CompositeMLDsaTestHelpers.ExpectedPrivateKeySizeUpperBound(algorithm); - if (bound.HasValue) - AssertImportBadPrivateKey(algorithm, new byte[bound.Value + 1]); + AssertImportBadPrivateKey(algorithm, new byte[bound + 1]); } [Fact] @@ -392,12 +381,7 @@ public static void ImportBadPublicKey_ECDsa_WrongAlgorithm() [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] public static void ImportPublicKey_LowerBound(CompositeMLDsaAlgorithm algorithm) { - int bound = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => rsa.KeySizeInBits / 8, - ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int bound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeLowerBound(algorithm); AssertImportBadPublicKey(algorithm, new byte[bound - 1]); } @@ -406,15 +390,9 @@ public static void ImportPublicKey_LowerBound(CompositeMLDsaAlgorithm algorithm) [MemberData(nameof(CompositeMLDsaTestData.AllAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))] public static void ImportPublicKey_UpperBound(CompositeMLDsaAlgorithm algorithm) { - int? bound = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + - CompositeMLDsaTestHelpers.ExecuteComponentFunc( - algorithm, - rsa => default(int?), - ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), - eddsa => eddsa.KeySizeInBits / 8); + int bound = CompositeMLDsaTestHelpers.ExpectedPublicKeySizeUpperBound(algorithm); - if (bound.HasValue) - AssertImportBadPublicKey(algorithm, new byte[bound.Value + 1]); + AssertImportBadPublicKey(algorithm, new byte[bound + 1]); } private static void AssertImportBadPublicKey(CompositeMLDsaAlgorithm algorithm, byte[] key) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaMockImplementation.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaMockImplementation.cs index ba41d527b80c0e..b78e08e5b3ca4b 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaMockImplementation.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaMockImplementation.cs @@ -17,19 +17,19 @@ public CompositeMLDsaMockImplementation(CompositeMLDsaAlgorithm algorithm) internal delegate int SignDataFunc(ReadOnlySpan data, ReadOnlySpan context, Span destination); internal delegate bool VerifyDataFunc(ReadOnlySpan data, ReadOnlySpan context, ReadOnlySpan signature); - internal delegate bool TryExportFunc(Span destination, out int bytesWritten); + internal delegate int ExportFunc(Span destination); internal delegate void DisposeAction(bool disposing); public int SignDataCoreCallCount = 0; public int VerifyDataCoreCallCount = 0; - public int TryExportCompositeMLDsaPublicKeyCoreCallCount = 0; - public int TryExportCompositeMLDsaPrivateKeyCoreCallCount = 0; + public int ExportCompositeMLDsaPublicKeyCoreCallCount = 0; + public int ExportCompositeMLDsaPrivateKeyCoreCallCount = 0; public int DisposeCallCount = 0; public SignDataFunc SignDataCoreHook { get; set; } = (_, _, _) => { Assert.Fail(); return 0; }; public VerifyDataFunc VerifyDataCoreHook { get; set; } = (_, _, _) => { Assert.Fail(); return false; }; - public TryExportFunc TryExportCompositeMLDsaPublicKeyCoreHook { get; set; } = (_, out bytesWritten) => { Assert.Fail(); bytesWritten = 0; return false; }; - public TryExportFunc TryExportCompositeMLDsaPrivateKeyCoreHook { get; set; } = (_, out bytesWritten) => { Assert.Fail(); bytesWritten = 0; return false; }; + public ExportFunc ExportCompositeMLDsaPublicKeyCoreHook { get; set; } = _ => { Assert.Fail(); return 0; }; + public ExportFunc ExportCompositeMLDsaPrivateKeyCoreHook { get; set; } = _ => { Assert.Fail(); return 0; }; public DisposeAction DisposeHook { get; set; } = _ => { }; protected override int SignDataCore(ReadOnlySpan data, ReadOnlySpan context, Span destination) @@ -44,16 +44,16 @@ protected override bool VerifyDataCore(ReadOnlySpan data, ReadOnlySpan destination, out int bytesWritten) + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) { - TryExportCompositeMLDsaPublicKeyCoreCallCount++; - return TryExportCompositeMLDsaPublicKeyCoreHook(destination, out bytesWritten); + ExportCompositeMLDsaPublicKeyCoreCallCount++; + return ExportCompositeMLDsaPublicKeyCoreHook(destination); } - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) { - TryExportCompositeMLDsaPrivateKeyCoreCallCount++; - return TryExportCompositeMLDsaPrivateKeyCoreHook(destination, out bytesWritten); + ExportCompositeMLDsaPrivateKeyCoreCallCount++; + return ExportCompositeMLDsaPrivateKeyCoreHook(destination); } protected override void Dispose(bool disposing) @@ -89,20 +89,20 @@ public void AddLengthAssertion() return ret; }; - TryExportFunc oldTryExportCompositeMLDsaPublicKeyCoreHook = TryExportCompositeMLDsaPublicKeyCoreHook; - TryExportCompositeMLDsaPublicKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPublicKeyCoreHook = ExportCompositeMLDsaPublicKeyCoreHook; + ExportCompositeMLDsaPublicKeyCoreHook = (Span destination) => { - bool ret = oldTryExportCompositeMLDsaPublicKeyCoreHook(destination, out bytesWritten); + int ret = oldExportCompositeMLDsaPublicKeyCoreHook(destination); AssertExtensions.LessThanOrEqualTo( CompositeMLDsaTestHelpers.MLDsaAlgorithms[Algorithm].PublicKeySizeInBytes, destination.Length); return ret; }; - TryExportFunc oldTryExportCompositeMLDsaPrivateKeyCoreHook = TryExportCompositeMLDsaPrivateKeyCoreHook; - TryExportCompositeMLDsaPrivateKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPrivateKeyCoreHook = ExportCompositeMLDsaPrivateKeyCoreHook; + ExportCompositeMLDsaPrivateKeyCoreHook = (Span destination) => { - bool ret = oldTryExportCompositeMLDsaPrivateKeyCoreHook(destination, out bytesWritten); + int ret = oldExportCompositeMLDsaPrivateKeyCoreHook(destination); AssertExtensions.LessThanOrEqualTo( CompositeMLDsaTestHelpers.MLDsaAlgorithms[Algorithm].PrivateSeedSizeInBytes, destination.Length); @@ -120,18 +120,18 @@ public void AddDestinationBufferIsSameAssertion(ReadOnlyMemory buffer) return ret; }; - TryExportFunc oldTryExportCompositeMLDsaPublicKeyCoreHook = TryExportCompositeMLDsaPublicKeyCoreHook; - TryExportCompositeMLDsaPublicKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPublicKeyCoreHook = ExportCompositeMLDsaPublicKeyCoreHook; + ExportCompositeMLDsaPublicKeyCoreHook = (Span destination) => { - bool ret = oldTryExportCompositeMLDsaPublicKeyCoreHook(destination, out bytesWritten); + int ret = oldExportCompositeMLDsaPublicKeyCoreHook(destination); AssertExtensions.Same(buffer.Span, destination); return ret; }; - TryExportFunc oldTryExportCompositeMLDsaPrivateKeyCoreHook = TryExportCompositeMLDsaPrivateKeyCoreHook; - TryExportCompositeMLDsaPrivateKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPrivateKeyCoreHook = ExportCompositeMLDsaPrivateKeyCoreHook; + ExportCompositeMLDsaPrivateKeyCoreHook = (Span destination) => { - bool ret = oldTryExportCompositeMLDsaPrivateKeyCoreHook(destination, out bytesWritten); + int ret = oldExportCompositeMLDsaPrivateKeyCoreHook(destination); AssertExtensions.Same(buffer.Span, destination); return ret; }; @@ -196,22 +196,20 @@ public void AddFillDestination(byte b) return destination.Length; }; - TryExportFunc oldExportCompositeMLDsaPublicKeyCoreHook = TryExportCompositeMLDsaPublicKeyCoreHook; - TryExportCompositeMLDsaPublicKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPublicKeyCoreHook = ExportCompositeMLDsaPublicKeyCoreHook; + ExportCompositeMLDsaPublicKeyCoreHook = (Span destination) => { - _ = oldExportCompositeMLDsaPublicKeyCoreHook(destination, out _); + _ = oldExportCompositeMLDsaPublicKeyCoreHook(destination); destination.Fill(b); - bytesWritten = destination.Length; - return true; + return destination.Length; }; - TryExportFunc oldExportCompositeMLDsaPrivateKeyCoreHook = TryExportCompositeMLDsaPrivateKeyCoreHook; - TryExportCompositeMLDsaPrivateKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPrivateKeyCoreHook = ExportCompositeMLDsaPrivateKeyCoreHook; + ExportCompositeMLDsaPrivateKeyCoreHook = (Span destination) => { - _ = oldExportCompositeMLDsaPrivateKeyCoreHook(destination, out _); + _ = oldExportCompositeMLDsaPrivateKeyCoreHook(destination); destination.Fill(b); - bytesWritten = destination.Length; - return true; + return destination.Length; }; } @@ -230,34 +228,30 @@ public void AddFillDestination(byte[] fillContents) return 0; }; - TryExportFunc oldExportCompositeMLDsaPublicKeyCoreHook = TryExportCompositeMLDsaPublicKeyCoreHook; - TryExportCompositeMLDsaPublicKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPublicKeyCoreHook = ExportCompositeMLDsaPublicKeyCoreHook; + ExportCompositeMLDsaPublicKeyCoreHook = (Span destination) => { - _ = oldExportCompositeMLDsaPublicKeyCoreHook(destination, out _); + _ = oldExportCompositeMLDsaPublicKeyCoreHook(destination); if (fillContents.AsSpan().TryCopyTo(destination)) { - bytesWritten = fillContents.Length; - return true; + return fillContents.Length; } - bytesWritten = 0; - return false; + return 0; }; - TryExportFunc oldExportCompositeMLDsaPrivateKeyCoreHook = TryExportCompositeMLDsaPrivateKeyCoreHook; - TryExportCompositeMLDsaPrivateKeyCoreHook = (Span destination, out int bytesWritten) => + ExportFunc oldExportCompositeMLDsaPrivateKeyCoreHook = ExportCompositeMLDsaPrivateKeyCoreHook; + ExportCompositeMLDsaPrivateKeyCoreHook = (Span destination) => { - _ = oldExportCompositeMLDsaPrivateKeyCoreHook(destination, out _); + _ = oldExportCompositeMLDsaPrivateKeyCoreHook(destination); if (fillContents.AsSpan().TryCopyTo(destination)) { - bytesWritten = fillContents.Length; - return true; + return fillContents.Length; } - bytesWritten = 0; - return false; + return 0; }; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaTestHelpers.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaTestHelpers.cs index bc7dda2e2103a4..8df9cb261f0503 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaTestHelpers.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaTestHelpers.cs @@ -154,6 +154,46 @@ internal static T ExecuteComponentFunc( } } + internal static int ExpectedPublicKeySizeLowerBound(CompositeMLDsaAlgorithm algorithm) + { + return MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + + ExecuteComponentFunc( + algorithm, + rsa => rsa.KeySizeInBits / 8, + ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), + eddsa => eddsa.KeySizeInBits / 8); + } + + internal static int ExpectedPublicKeySizeUpperBound(CompositeMLDsaAlgorithm algorithm) + { + return MLDsaAlgorithms[algorithm].PublicKeySizeInBytes + + ExecuteComponentFunc( + algorithm, + rsa => (rsa.KeySizeInBits / 8) + 52, // Add max ASN.1 overhead + ecdsa => 1 + 2 * ((ecdsa.KeySizeInBits + 7) / 8), + eddsa => eddsa.KeySizeInBits / 8); + } + + internal static int ExpectedPrivateKeySizeLowerBound(CompositeMLDsaAlgorithm algorithm) + { + return MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + + ExecuteComponentFunc( + algorithm, + rsa => rsa.KeySizeInBits / 8, + ecdsa => (ecdsa.KeySizeInBits + 7) / 8, + eddsa => eddsa.KeySizeInBits / 8); + } + + internal static int ExpectedPrivateKeySizeUpperBound(CompositeMLDsaAlgorithm algorithm) + { + return MLDsaAlgorithms[algorithm].PrivateSeedSizeInBytes + + ExecuteComponentFunc( + algorithm, + rsa => (rsa.KeySizeInBits / 8) * 9 / 2 + 101, // Add max ASN.1 overhead + ecdsa => 3 * ((ecdsa.KeySizeInBits + 7) / 8) + 47, // Add max ASN.1 overhead + eddsa => eddsa.KeySizeInBits / 8); + } + internal static bool IsECDsa(CompositeMLDsaAlgorithm algorithm) => ExecuteComponentFunc(algorithm, rsa => false, ecdsa => true, eddsa => false); internal static void AssertExportPublicKey(Action> callback) 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 60e3360c64741a..9ce2245d0d13e5 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -568,7 +568,11 @@ protected CompositeMLDsa(System.Security.Cryptography.CompositeMLDsaAlgorithm al public void Dispose() { } protected virtual void Dispose(bool disposing) { } public byte[] ExportCompositeMLDsaPrivateKey() { throw null; } + public int ExportCompositeMLDsaPrivateKey(System.Span destination) { throw null; } + protected abstract int ExportCompositeMLDsaPrivateKeyCore(System.Span destination); public byte[] ExportCompositeMLDsaPublicKey() { throw null; } + public int ExportCompositeMLDsaPublicKey(System.Span destination) { throw null; } + protected abstract int ExportCompositeMLDsaPublicKeyCore(System.Span destination); public byte[] ExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } public byte[] ExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } public byte[] ExportEncryptedPkcs8PrivateKey(string password, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } @@ -602,9 +606,7 @@ protected virtual void Dispose(bool disposing) { } public int SignData(System.ReadOnlySpan data, System.Span destination, System.ReadOnlySpan context = default(System.ReadOnlySpan)) { throw null; } protected abstract int SignDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.Span destination); public bool TryExportCompositeMLDsaPrivateKey(System.Span destination, out int bytesWritten) { throw null; } - protected abstract bool TryExportCompositeMLDsaPrivateKeyCore(System.Span destination, out int bytesWritten); public bool TryExportCompositeMLDsaPublicKey(System.Span destination, out int bytesWritten) { throw null; } - protected abstract bool TryExportCompositeMLDsaPublicKeyCore(System.Span destination, out int bytesWritten); 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; } public bool TryExportEncryptedPkcs8PrivateKey(string password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } @@ -651,10 +653,10 @@ public sealed partial class CompositeMLDsaCng : System.Security.Cryptography.Com { [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] public CompositeMLDsaCng(System.Security.Cryptography.CngKey key) : base (default(System.Security.Cryptography.CompositeMLDsaAlgorithm)) { } + protected override int ExportCompositeMLDsaPrivateKeyCore(System.Span destination) { throw null; } + protected override int ExportCompositeMLDsaPublicKeyCore(System.Span destination) { throw null; } public System.Security.Cryptography.CngKey GetKey() { throw null; } protected override int SignDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.Span destination) { throw null; } - protected override bool TryExportCompositeMLDsaPrivateKeyCore(System.Span destination, out int bytesWritten) { throw null; } - protected override bool TryExportCompositeMLDsaPublicKeyCore(System.Span destination, out int bytesWritten) { throw null; } protected override bool TryExportPkcs8PrivateKeyCore(System.Span destination, out int bytesWritten) { throw null; } protected override bool VerifyDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.ReadOnlySpan signature) { throw null; } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs index 0fd85457c51caa..7148d243f1c220 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs @@ -475,10 +475,10 @@ public partial CngKey GetKey() => protected override int SignDataCore(ReadOnlySpan data, ReadOnlySpan context, Span destination) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyCng); - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyCng); - protected override bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyCng); protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) => diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CompositeMLDsaImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CompositeMLDsaImplementation.OpenSsl.cs index 92fd2990ca0cc9..7d6c5fe0cac78a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CompositeMLDsaImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CompositeMLDsaImplementation.OpenSsl.cs @@ -34,10 +34,10 @@ protected override bool VerifyDataCore(ReadOnlySpan data, ReadOnlySpan destination, out int bytesWritten) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPublicKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPublicKeyCore(Span destination) => throw new PlatformNotSupportedException(); - protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span destination, out int bytesWritten) => + protected override int ExportCompositeMLDsaPrivateKeyCore(Span destination) => throw new PlatformNotSupportedException(); } }