This commit is contained in:
Jeremy Barton 2025-07-30 06:54:02 -07:00 committed by GitHub
commit 1fe2d953b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 2999 additions and 70 deletions

View File

@ -184,6 +184,65 @@ internal static partial class Interop
}
}
[LibraryImport(Libraries.CryptoNative)]
private static partial int CryptoNative_MLDsaSignExternalMu(
SafeEvpPKeyHandle pkey, IntPtr extraHandle,
ReadOnlySpan<byte> mu, int muLength,
Span<byte> destination, int destinationLength);
internal static void MLDsaSignExternalMu(
SafeEvpPKeyHandle pkey,
ReadOnlySpan<byte> mu,
Span<byte> destination)
{
const int Success = 1;
const int SigningFailure = 0;
int ret = CryptoNative_MLDsaSignExternalMu(
pkey, GetExtraHandle(pkey),
mu, mu.Length,
destination, destination.Length);
if (ret != Success)
{
Debug.Assert(ret == SigningFailure, $"Unexpected return value {ret} from {nameof(CryptoNative_MLDsaSignExternalMu)}.");
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
}
[LibraryImport(Libraries.CryptoNative)]
private static partial int CryptoNative_MLDsaVerifyExternalMu(
SafeEvpPKeyHandle pkey, IntPtr extraHandle,
ReadOnlySpan<byte> mu, int muLength,
ReadOnlySpan<byte> signature, int signatureLength);
internal static bool MLDsaVerifyExternalMu(
SafeEvpPKeyHandle pkey,
ReadOnlySpan<byte> mu,
ReadOnlySpan<byte> signature)
{
const int ValidSignature = 1;
const int InvalidSignature = 0;
int ret = CryptoNative_MLDsaVerifyExternalMu(
pkey, GetExtraHandle(pkey),
mu, mu.Length,
signature, signature.Length);
if (ret == ValidSignature)
{
return true;
}
else if (ret == InvalidSignature)
{
return false;
}
else
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
}
[LibraryImport(Libraries.CryptoNative)]
private static partial int CryptoNative_MLDsaExportSecretKey(SafeEvpPKeyHandle pkey, Span<byte> destination, int destinationLength);

View File

@ -264,5 +264,18 @@ namespace Internal.Cryptography
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}
#if !BUILDING_PKCS
internal static void ThrowIfDestinationWrongLength(
Span<byte> destination,
int expectedLength,
[System.Runtime.CompilerServices.CallerArgumentExpression(nameof(destination))] string? paramName = null)
{
if (destination.Length != expectedLength)
{
throw new ArgumentException(SR.Format(SR.Argument_DestinationImprecise, expectedLength), paramName);
}
}
#endif
}
}

View File

@ -120,14 +120,7 @@ namespace System.Security.Cryptography
/// </exception>
public void SignData(ReadOnlySpan<byte> data, Span<byte> destination, ReadOnlySpan<byte> context = default)
{
int signatureSizeInBytes = Algorithm.SignatureSizeInBytes;
if (destination.Length != signatureSizeInBytes)
{
throw new ArgumentException(
SR.Format(SR.Argument_DestinationImprecise, signatureSizeInBytes),
nameof(destination));
}
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SignatureSizeInBytes);
if (context.Length > MaxContextLength)
{
@ -309,13 +302,7 @@ namespace System.Security.Cryptography
public void SignPreHash(ReadOnlySpan<byte> hash, Span<byte> destination, string hashAlgorithmOid, ReadOnlySpan<byte> context = default)
{
ArgumentNullException.ThrowIfNull(hashAlgorithmOid);
if (destination.Length != Algorithm.SignatureSizeInBytes)
{
throw new ArgumentException(
SR.Format(SR.Argument_DestinationImprecise, Algorithm.SignatureSizeInBytes),
nameof(destination));
}
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SignatureSizeInBytes);
if (context.Length > MaxContextLength)
{
@ -507,6 +494,155 @@ namespace System.Security.Cryptography
new ReadOnlySpan<byte>(context));
}
/// <inheritdoc cref="SignMu(ReadOnlySpan{byte})"/>
/// <exception cref="ArgumentNullException"><paramref name="externalMu"/> is <see langword="null"/>.</exception>
public byte[] SignMu(byte[] externalMu)
{
ArgumentNullException.ThrowIfNull(externalMu);
return SignMu(new ReadOnlySpan<byte>(externalMu));
}
/// <summary>
/// Signs the specified externally computed signature mu (&#x3BC;) value.
/// </summary>
/// <param name="externalMu">
/// The signature mu value to sign.
/// </param>
/// <returns>
/// ML-DSA signature for the specified mu value.
/// </returns>
/// <exception cref="ArgumentException">
/// The buffer in <paramref name="externalMu"/> is the incorrect length for the signature mu value.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance has been disposed.
/// </exception>
/// <exception cref="CryptographicException">
/// <para>The instance represents only a public key.</para>
/// <para>-or-</para>
/// <para>An error occurred while signing the hash.</para>
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// The current platform does not support signing with an externally computed mu value.
/// </exception>
/// <seealso cref="VerifyMu(byte[], byte[])"/>
public byte[] SignMu(ReadOnlySpan<byte> externalMu)
{
byte[] destination = new byte[Algorithm.SignatureSizeInBytes];
SignMu(externalMu, destination.AsSpan());
return destination;
}
/// <summary>
/// Signs the specified externally computed signature mu (&#x3BC;) value,
/// writing the signature into the provided buffer.
/// </summary>
/// <param name="externalMu">
/// The signature mu value to sign.
/// </param>
/// <param name="destination">
/// The buffer to receive the signature. Its length must be exactly
/// <see cref="MLDsaAlgorithm.SignatureSizeInBytes"/>.
/// </param>
/// <exception cref="ArgumentException">
/// <para>
/// The buffer in <paramref name="externalMu"/> is the incorrect length for the signature mu value.
/// </para>
/// <para>-or-</para>
/// <para>
/// The buffer in <paramref name="destination"/> is the incorrect length to receive the signature.
/// </para>
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This instance has been disposed.
/// </exception>
/// <exception cref="CryptographicException">
/// <para>The instance represents only a public key.</para>
/// <para>-or-</para>
/// <para>An error occurred while signing the hash.</para>
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// The current platform does not support signing with an externally computed mu value.
/// </exception>
/// <seealso cref="VerifyMu(ReadOnlySpan{byte}, ReadOnlySpan{byte})"/>
public void SignMu(ReadOnlySpan<byte> externalMu, Span<byte> destination)
{
if (externalMu.Length != Algorithm.MuSizeInBytes)
throw new ArgumentException(SR.Argument_MLDsaMuInvalidLength, nameof(externalMu));
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SignatureSizeInBytes);
ThrowIfDisposed();
SignMuCore(externalMu, destination);
}
/// <summary>
/// When overridden in a derived class, computes the remainder of the signature from the
/// precomputed mu (&#x3BC;) value, writing it into the provided buffer.
/// </summary>
/// <param name="externalMu">
/// The signature mu value to sign.
/// </param>
/// <param name="destination">
/// The buffer to receive the signature, which will always be the exactly correct size for the algorithm.
/// </param>
/// <exception cref="CryptographicException">
/// An error occurred while computing the signature.
/// </exception>
protected abstract void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination);
/// <inheritdoc cref="VerifyMu(ReadOnlySpan{byte}, ReadOnlySpan{byte})"/>
/// <exception cref="ArgumentNullException">
/// <paramref name="externalMu"/> or <paramref name="signature"/> is <see langword="null"/>.
/// </exception>
public bool VerifyMu(byte[] externalMu, byte[] signature)
{
ArgumentNullException.ThrowIfNull(externalMu);
ArgumentNullException.ThrowIfNull(signature);
return VerifyMu(new ReadOnlySpan<byte>(externalMu), new ReadOnlySpan<byte>(signature));
}
/// <summary>
/// Verifies that a digital signature is valid for the provided externally computed signature mu (&#x3BC;) value.
/// </summary>
/// <param name="externalMu">The signature mu value.</param>
/// <param name="signature">The signature to verify.</param>
/// <returns>
/// <see langword="true"/> if the digital signature is valid for the provided mu value;
/// otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ObjectDisposedException">
/// This instance has been disposed.
/// </exception>
/// <exception cref="CryptographicException">An error occurred while verifying the mu value.</exception>
/// <exception cref="PlatformNotSupportedException">
/// The current platform does not support verification with an externally computed mu value.
/// </exception>
public bool VerifyMu(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature)
{
if (externalMu.Length != Algorithm.MuSizeInBytes || signature.Length != Algorithm.SignatureSizeInBytes)
{
return false;
}
ThrowIfDisposed();
return VerifyMuCore(externalMu, signature);
}
/// <summary>
/// When overridden in a derived class,
/// verifies that a digital signature is valid for the provided externally computed signature mu (&#x3BC;) value.
/// </summary>
/// <param name="externalMu">The signature mu value.</param>
/// <param name="signature">The signature to verify.</param>
/// <returns>
/// <see langword="true"/> if the mu value is valid; otherwise, <see langword="false"/>.
/// </returns>
protected abstract bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature);
/// <summary>
/// Exports the public-key portion of the current key in the X.509 SubjectPublicKeyInfo format.
/// </summary>
@ -1063,15 +1199,7 @@ namespace System.Security.Cryptography
/// </remarks>
public void ExportMLDsaPublicKey(Span<byte> destination)
{
int publicKeySizeInBytes = Algorithm.PublicKeySizeInBytes;
if (destination.Length != publicKeySizeInBytes)
{
throw new ArgumentException(
SR.Format(SR.Argument_DestinationImprecise, publicKeySizeInBytes),
nameof(destination));
}
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.PublicKeySizeInBytes);
ThrowIfDisposed();
ExportMLDsaPublicKeyCore(destination);
@ -1113,15 +1241,7 @@ namespace System.Security.Cryptography
/// </exception>
public void ExportMLDsaSecretKey(Span<byte> destination)
{
int secretKeySizeInBytes = Algorithm.SecretKeySizeInBytes;
if (destination.Length != secretKeySizeInBytes)
{
throw new ArgumentException(
SR.Format(SR.Argument_DestinationImprecise, secretKeySizeInBytes),
nameof(destination));
}
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SecretKeySizeInBytes);
ThrowIfDisposed();
ExportMLDsaSecretKeyCore(destination);
@ -1161,14 +1281,7 @@ namespace System.Security.Cryptography
/// </exception>
public void ExportMLDsaPrivateSeed(Span<byte> destination)
{
int privateSeedSizeInBytes = Algorithm.PrivateSeedSizeInBytes;
if (destination.Length != privateSeedSizeInBytes)
{
throw new ArgumentException(
SR.Format(SR.Argument_DestinationImprecise, privateSeedSizeInBytes),
nameof(destination));
}
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.PrivateSeedSizeInBytes);
ThrowIfDisposed();
ExportMLDsaPrivateSeedCore(destination);

View File

@ -53,6 +53,14 @@ namespace System.Security.Cryptography
/// </value>
public int SignatureSizeInBytes { get; }
/// <summary>
/// Gets the size, in bytes, of the mu (&#x3BC;) value for the current ML-DSA algorithm.
/// </summary>
/// <value>
/// The size, in bytes, of the mu (&#x3BC;) value for the current ML-DSA algorithm.
/// </value>
public int MuSizeInBytes => 64;
internal string Oid { get; }
internal int LambdaCollisionStrength { get; }

View File

@ -48,6 +48,14 @@ namespace System.Security.Cryptography
_key = duplicateKey;
}
/// <inheritdoc />
protected override void SignMuCore(ReadOnlySpan<byte> mu, Span<byte> destination) =>
throw new PlatformNotSupportedException();
/// <inheritdoc />
protected override bool VerifyMuCore(ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature) =>
throw new PlatformNotSupportedException();
private static MLDsaAlgorithm AlgorithmFromHandleWithPlatformCheck(CngKey key, out CngKey duplicateKey)
{
if (!Helpers.IsOSPlatformWindows)

View File

@ -31,6 +31,12 @@ namespace System.Security.Cryptography
protected override bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature) =>
throw new PlatformNotSupportedException();
protected override void SignMuCore(ReadOnlySpan<byte> mu, Span<byte> destination) =>
throw new PlatformNotSupportedException();
protected override bool VerifyMuCore(ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature) =>
throw new PlatformNotSupportedException();
protected override void ExportMLDsaPublicKeyCore(Span<byte> destination) =>
throw new PlatformNotSupportedException();

View File

@ -91,6 +91,12 @@ namespace System.Security.Cryptography
return Interop.BCrypt.BCryptVerifySignaturePqcPreHash(_key, hash, hashAlgorithmIdentifier, context, signature);
}
protected override void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination) =>
throw new PlatformNotSupportedException();
protected override bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature) =>
throw new PlatformNotSupportedException();
internal static partial MLDsaImplementation GenerateKeyImpl(MLDsaAlgorithm algorithm)
{
Debug.Assert(SupportsAny());

View File

@ -20,6 +20,8 @@ namespace System.Security.Cryptography.Tests
// TODO: Windows does not support signing empty data. Remove this and use MLDsa.IsSupported (or remove condition) when it does.
internal static bool SigningEmptyDataIsSupported => MLDsa.IsSupported && !PlatformDetection.IsWindows;
internal static bool ExternalMuIsSupported => MLDsa.IsSupported && !PlatformDetection.IsWindows;
// DER encoding of ASN.1 BitString "foo"
internal static readonly ReadOnlyMemory<byte> s_derBitStringFoo = new byte[] { 0x03, 0x04, 0x00, 0x66, 0x6f, 0x6f };
@ -28,29 +30,48 @@ namespace System.Security.Cryptography.Tests
internal static void VerifyDisposed(MLDsa mldsa)
{
PbeParameters pbeParams = new PbeParameters(PbeEncryptionAlgorithm.Aes128Cbc, HashAlgorithmName.SHA256, 10);
byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes];
byte[] bigBuffer = new byte[10000];
byte[] mu = new byte[64];
Assert.Throws<ObjectDisposedException>(() => mldsa.SignData(ReadOnlySpan<byte>.Empty, new byte[mldsa.Algorithm.SignatureSizeInBytes]));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyData(ReadOnlySpan<byte>.Empty, new byte[mldsa.Algorithm.SignatureSizeInBytes]));
Assert.Throws<ObjectDisposedException>(() => mldsa.SignData(Array.Empty<byte>()));
Assert.Throws<ObjectDisposedException>(() => mldsa.SignData(ReadOnlySpan<byte>.Empty, signature));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyData(Array.Empty<byte>(), signature));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyData(ReadOnlySpan<byte>.Empty, signature));
Assert.Throws<ObjectDisposedException>(() => mldsa.SignPreHash(mu, HashInfo.Sha512.Oid));
Assert.Throws<ObjectDisposedException>(() => mldsa.SignPreHash(mu, signature, HashInfo.Sha512.Oid));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyPreHash(mu, signature, HashInfo.Sha512.Oid));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyPreHash(new ReadOnlySpan<byte>(mu), signature, HashInfo.Sha512.Oid));
Assert.Throws<ObjectDisposedException>(() => mldsa.SignMu(mu));
Assert.Throws<ObjectDisposedException>(() => mldsa.SignMu(new ReadOnlySpan<byte>(mu)));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyMu(mu, signature));
Assert.Throws<ObjectDisposedException>(() => mldsa.VerifyMu(new ReadOnlySpan<byte>(mu), signature));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaPrivateSeed());
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaPrivateSeed(new byte[mldsa.Algorithm.PrivateSeedSizeInBytes]));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaPublicKey());
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaPublicKey(new byte[mldsa.Algorithm.PublicKeySizeInBytes]));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaSecretKey());
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportMLDsaSecretKey(new byte[mldsa.Algorithm.SecretKeySizeInBytes]));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportPkcs8PrivateKey());
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportPkcs8PrivateKey(new byte[10000], out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportPkcs8PrivateKey(bigBuffer, out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportPkcs8PrivateKeyPem());
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKey([1, 2, 3], pbeParams));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKey("123", pbeParams));
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportEncryptedPkcs8PrivateKey([1, 2, 3], pbeParams, new byte[10000], out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportEncryptedPkcs8PrivateKey("123", pbeParams, new byte[10000], out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportEncryptedPkcs8PrivateKey([1, 2, 3], pbeParams, bigBuffer, out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportEncryptedPkcs8PrivateKey("123", pbeParams, bigBuffer, out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKeyPem([1, 2, 3], pbeParams));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportEncryptedPkcs8PrivateKeyPem("123", pbeParams));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportSubjectPublicKeyInfo());
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportSubjectPublicKeyInfo(new byte[10000], out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.TryExportSubjectPublicKeyInfo(bigBuffer, out _));
Assert.Throws<ObjectDisposedException>(() => mldsa.ExportSubjectPublicKeyInfoPem());
// Doesn't throw:
Assert.NotNull(mldsa.Algorithm);
}
internal static void AssertImportPublicKey(Action<Func<MLDsa>> test, MLDsaAlgorithm algorithm, byte[] publicKey) =>

View File

@ -13,11 +13,16 @@ namespace System.Security.Cryptography.Tests
internal delegate bool VerifyFunc(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
internal delegate void SignPreHashAction(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, Span<byte> destination);
internal delegate bool VerifyPreHashFunc(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature);
internal delegate void SignMuAction(ReadOnlySpan<byte> mu, Span<byte> destination);
internal delegate bool VerifyMuFunc(ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature);
internal int VerifyDataCoreCallCount = 0;
internal int SignDataCoreCallCount = 0;
internal int SignPreHashCoreCallCount = 0;
internal int VerifyPreHashCoreCallCount = 0;
internal int OpenExternalMuHashCoreCallCount = 0;
internal int SignMuCoreCallCount = 0;
internal int VerifyMuCoreCallCount = 0;
internal int ExportMLDsaPrivateSeedCoreCallCount = 0;
internal int ExportMLDsaPublicKeyCoreCallCount = 0;
internal int ExportMLDsaSecretKeyCoreCallCount = 0;
@ -32,6 +37,8 @@ namespace System.Security.Cryptography.Tests
internal VerifyFunc VerifyDataHook { get; set; }
internal SignPreHashAction SignPreHashHook { get; set; }
internal VerifyPreHashFunc VerifyPreHashHook { get; set; }
internal SignMuAction SignMuHook { get; set; }
internal VerifyMuFunc VerifyMuHook { get; set; }
internal Action<bool> DisposeHook { get; set; }
private MLDsaTestImplementation(MLDsaAlgorithm algorithm) : base(algorithm)
@ -85,12 +92,25 @@ namespace System.Security.Cryptography.Tests
SignPreHashCoreCallCount++;
SignPreHashHook(hash, context, hashAlgorithmOid, destination);
}
protected override bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature)
{
VerifyPreHashCoreCallCount++;
return VerifyPreHashHook(hash, context, hashAlgorithmOid, signature);
}
protected override void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination)
{
SignMuCoreCallCount++;
SignMuHook(externalMu, destination);
}
protected override bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature)
{
VerifyMuCoreCallCount++;
return VerifyMuHook(externalMu, signature);
}
internal static MLDsaTestImplementation CreateOverriddenCoreMethodsFail(MLDsaAlgorithm algorithm)
{
return new MLDsaTestImplementation(algorithm)
@ -100,8 +120,10 @@ namespace System.Security.Cryptography.Tests
ExportMLDsaSecretKeyHook = _ => Assert.Fail(),
SignDataHook = (_, _, _) => Assert.Fail(),
SignPreHashHook = delegate { Assert.Fail(); },
SignMuHook = (_, _) => Assert.Fail(),
VerifyDataHook = (_, _, _) => { Assert.Fail(); return false; },
VerifyPreHashHook = delegate { Assert.Fail(); return false; },
VerifyPreHashHook = (_, _, _, _) => { Assert.Fail(); return false; },
VerifyMuHook = (_, _) => { Assert.Fail(); return false; },
DisposeHook = _ => { },
TryExportPkcs8PrivateKeyHook = (_, out bytesWritten) =>
@ -122,6 +144,10 @@ namespace System.Security.Cryptography.Tests
ExportMLDsaSecretKeyHook = d => d.Clear(),
SignDataHook = (data, context, destination) => destination.Clear(),
VerifyDataHook = (data, context, signature) => false,
SignPreHashHook = (hash, context, hashAlgorithmOid, destination) => destination.Clear(),
VerifyPreHashHook = (hash, context, hashAlgorithmOid, signature) => false,
SignMuHook = (mu, destination) => destination.Clear(),
VerifyMuHook = (mu, signature) => false,
DisposeHook = _ => { },
TryExportPkcs8PrivateKeyHook = (Span<byte> destination, out int bytesWritten) =>
@ -202,6 +228,23 @@ namespace System.Security.Cryptography.Tests
Assert.Equal(Algorithm.SignatureSizeInBytes, signature.Length);
return ret;
};
SignMuAction oldSignExternalMuHook = SignMuHook;
SignMuHook = (ReadOnlySpan<byte> mu, Span<byte> destination) =>
{
oldSignExternalMuHook(mu, destination);
Assert.Equal(64, mu.Length);
Assert.Equal(Algorithm.SignatureSizeInBytes, destination.Length);
};
VerifyMuFunc oldVerifyExternalMuHook = VerifyMuHook;
VerifyMuHook = (ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature) =>
{
bool ret = oldVerifyExternalMuHook(mu, signature);
Assert.Equal(64, mu.Length);
Assert.Equal(Algorithm.SignatureSizeInBytes, signature.Length);
return ret;
};
}
public void AddDestinationBufferIsSameAssertion(ReadOnlyMemory<byte> buffer)
@ -333,6 +376,21 @@ namespace System.Security.Cryptography.Tests
AssertExtensions.Same(buffer.Span, hash);
return ret;
};
SignMuAction oldSignExternalMuHook = SignMuHook;
SignMuHook = (ReadOnlySpan<byte> mu, Span<byte> destination) =>
{
oldSignExternalMuHook(mu, destination);
AssertExtensions.Same(buffer.Span, mu);
};
VerifyMuFunc oldVerifyExternalMuHook = VerifyMuHook;
VerifyMuHook = (ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature) =>
{
bool ret = oldVerifyExternalMuHook(mu, signature);
AssertExtensions.Same(buffer.Span, mu);
return ret;
};
}
public void AddHashAlgorithmIsSameAssertion(ReadOnlyMemory<char> buffer)

View File

@ -6,7 +6,6 @@ using System.Formats.Asn1;
using System.Linq;
using System.Security.Cryptography.Asn1;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.DotNet.XUnitExtensions;
using Test.Cryptography;
using Xunit;
@ -86,6 +85,10 @@ namespace System.Security.Cryptography.Tests
AssertExtensions.Throws<ArgumentNullException>("signature", () => mldsa.VerifyPreHash(hash, null, null));
AssertExtensions.Throws<ArgumentNullException>("hashAlgorithmOid", () => mldsa.VerifyPreHash(hash, signature, null));
AssertExtensions.Throws<ArgumentNullException>("externalMu", () => mldsa.SignMu(null));
AssertExtensions.Throws<ArgumentNullException>("externalMu", () => mldsa.VerifyMu(null, null));
AssertExtensions.Throws<ArgumentNullException>("signature", () => mldsa.VerifyMu(Array.Empty<byte>(), null));
AssertExtensions.Throws<ArgumentNullException>("password", () => mldsa.ExportEncryptedPkcs8PrivateKey((string)null, pbeParameters));
AssertExtensions.Throws<ArgumentNullException>("password", () => mldsa.ExportEncryptedPkcs8PrivateKeyPem((string)null, pbeParameters));
AssertExtensions.Throws<ArgumentNullException>("password", () => mldsa.TryExportEncryptedPkcs8PrivateKey((string)null, pbeParameters, Span<byte>.Empty, out _));
@ -113,6 +116,11 @@ namespace System.Security.Cryptography.Tests
int signatureSize = algorithm.SignatureSizeInBytes;
byte[] signature = new byte[signatureSize];
byte[] hash = new byte[HashInfo.Sha256.OutputSize];
byte[] mu = new byte[64];
byte[] shortMu = new byte[mu.Length - 1];
byte[] longMu = new byte[mu.Length + 1];
byte[] shortSignature = new byte[signatureSize - 1];
byte[] longSignature = new byte[signatureSize + 1];
if (shouldDispose)
{
@ -126,10 +134,12 @@ namespace System.Security.Cryptography.Tests
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.ExportMLDsaSecretKey(new byte[secretKeySize + 1]));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.ExportMLDsaPrivateSeed(new byte[privateSeedSize - 1]));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.ExportMLDsaPrivateSeed(new byte[privateSeedSize + 1]));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignData(ReadOnlySpan<byte>.Empty, new byte[signatureSize - 1], ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignData(ReadOnlySpan<byte>.Empty, new byte[signatureSize + 1], ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignPreHash(new byte[HashInfo.Sha512.OutputSize], new byte[signatureSize - 1], HashInfo.Sha512.Oid, ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignPreHash(new byte[HashInfo.Sha512.OutputSize], new byte[signatureSize + 1], HashInfo.Sha512.Oid, ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignData(ReadOnlySpan<byte>.Empty, shortSignature, ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignData(ReadOnlySpan<byte>.Empty, longSignature, ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignMu(mu, shortSignature));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignMu(mu, longSignature));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignPreHash(new byte[HashInfo.Sha512.OutputSize], shortSignature, HashInfo.Sha512.Oid, ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<ArgumentException>("destination", () => mldsa.SignPreHash(new byte[HashInfo.Sha512.OutputSize], longSignature, HashInfo.Sha512.Oid, ReadOnlySpan<byte>.Empty));
// Context length must be less than 256
AssertExtensions.Throws<ArgumentOutOfRangeException>("context", () => mldsa.SignData(ReadOnlySpan<byte>.Empty, signature, new byte[256]));
@ -139,6 +149,12 @@ namespace System.Security.Cryptography.Tests
AssertExtensions.Throws<ArgumentOutOfRangeException>("context", () => mldsa.SignPreHash(hash.AsSpan(), signature, HashInfo.Sha256.Oid, new byte[256]));
AssertExtensions.Throws<ArgumentOutOfRangeException>("context", () => mldsa.SignPreHash(hash, HashInfo.Sha256.Oid, new byte[256]));
// Mu must be the correct size
AssertExtensions.Throws<ArgumentException>("externalMu", () => mldsa.SignMu(shortMu));
AssertExtensions.Throws<ArgumentException>("externalMu", () => mldsa.SignMu(longMu));
AssertExtensions.Throws<ArgumentException>("externalMu", () => mldsa.SignMu(shortMu, signature));
AssertExtensions.Throws<ArgumentException>("externalMu", () => mldsa.SignMu(longMu, signature));
// Hash length of known OID hash algorithms must be correct
AssertExtensions.Throws<CryptographicException>(() => mldsa.SignPreHash(new byte[HashInfo.Sha512.OutputSize - 1], new byte[signatureSize], HashInfo.Sha512.Oid, ReadOnlySpan<byte>.Empty));
AssertExtensions.Throws<CryptographicException>(() => mldsa.SignPreHash(new byte[HashInfo.Sha512.OutputSize + 1], new byte[signatureSize], HashInfo.Sha512.Oid, ReadOnlySpan<byte>.Empty));
@ -915,6 +931,82 @@ namespace System.Security.Cryptography.Tests
});
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public static void SignExternalMu_GetsMuAndDestination(MLDsaAlgorithm algorithm)
{
using MLDsaTestImplementation mldsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(algorithm);
int signatureSize = algorithm.SignatureSizeInBytes;
byte[] buffer = CreatePaddedFilledArray(signatureSize, 0x42);
Memory<byte> signature = buffer.AsMemory(PaddingSize, signatureSize);
byte[] mu = new byte[64];
mldsa.SignMuHook = (mu, destination) => destination.Fill(0xAB);
mldsa.AddLengthAssertion();
mldsa.AddDataBufferIsSameAssertion(mu);
mldsa.AddDestinationBufferIsSameAssertion(signature);
mldsa.SignMu(mu, signature.Span);
Assert.Equal(1, mldsa.SignMuCoreCallCount);
AssertExpectedFill(buffer, fillElement: 0xAB, paddingElement: 0x42, PaddingSize, signatureSize);
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public static void VerifyExternalMu_GetsMuAndSignature(MLDsaAlgorithm algorithm)
{
using MLDsaTestImplementation mldsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(algorithm);
byte[] buffer = CreatePaddedFilledArray(algorithm.SignatureSizeInBytes, 0x42);
Memory<byte> signature = buffer.AsMemory(PaddingSize, algorithm.SignatureSizeInBytes);
byte[] mu = new byte[64];
mldsa.VerifyMuHook = (mu, signature) => true;
mldsa.AddLengthAssertion();
mldsa.AddDataBufferIsSameAssertion(mu);
mldsa.AddDestinationBufferIsSameAssertion(signature);
mldsa.VerifyMu(mu, signature.Span);
Assert.Equal(1, mldsa.VerifyMuCoreCallCount);
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public static void VerifyExternalMu_EarlyFalseForWrongSizeMu(MLDsaAlgorithm algorithm)
{
using MLDsaTestImplementation mldsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(algorithm);
byte[] mu = new byte[100];
byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes];
const int CorrectMuLength = 64;
for (int i = 0; i < mu.Length; i++)
{
// Don't check with the correct length, since the callback is Assert.Fail.
if (i == CorrectMuLength)
{
continue;
}
AssertExtensions.FalseExpression(mldsa.VerifyMu(mu.AsSpan(0, i), signature));
}
Assert.Equal(0, mldsa.VerifyMuCoreCallCount);
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public static void VerifyExternalMu_EarlyFalseForWrongSizeSignature(MLDsaAlgorithm algorithm)
{
using MLDsaTestImplementation mldsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(algorithm);
byte[] mu = new byte[64];
byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes + 1];
AssertExtensions.FalseExpression(mldsa.VerifyMu(mu, signature));
AssertExtensions.FalseExpression(mldsa.VerifyMu(mu, signature.AsSpan(2)));
Assert.Equal(0, mldsa.VerifyMuCoreCallCount);
}
private static void AssertExpectedFill(ReadOnlySpan<byte> source, byte fillElement) =>
AssertExpectedFill(source, fillElement, 255, 0, source.Length);

View File

@ -45,6 +45,15 @@ namespace System.Security.Cryptography.Tests
ExerciseSuccessfulVerifyPreHash(mldsa, HashInfo.Sha512.Oid, hash, signature, []);
}
[ConditionalTheory(typeof(MLDsaTestHelpers), nameof(MLDsaTestHelpers.ExternalMuIsSupported))]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public void GenerateSignVerifyExternalMuNoContext(MLDsaAlgorithm algorithm)
{
byte[] data = [1, 2, 3, 4, 5];
using MLDsa mldsa = GenerateKey(algorithm);
SignAndVerifyExternalMu(mldsa, data, []);
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public void GenerateSignVerifyWithContext(MLDsaAlgorithm algorithm)
@ -70,6 +79,16 @@ namespace System.Security.Cryptography.Tests
ExerciseSuccessfulVerifyPreHash(mldsa, HashInfo.Sha512.Oid, hash, signature, context);
}
[ConditionalTheory(typeof(MLDsaTestHelpers), nameof(MLDsaTestHelpers.ExternalMuIsSupported))]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public void GenerateSignVerifyExternalMuWithContext(MLDsaAlgorithm algorithm)
{
byte[] data = [1, 2, 3, 4, 5];
byte[] context = [1, 1, 3, 5, 6];
using MLDsa mldsa = GenerateKey(algorithm);
SignAndVerifyExternalMu(mldsa, data, context);
}
[ConditionalTheory(typeof(MLDsaTestHelpers), nameof(MLDsaTestHelpers.SigningEmptyDataIsSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/116461", TestPlatforms.Windows)]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
@ -91,6 +110,27 @@ namespace System.Security.Cryptography.Tests
ExerciseSuccessfulVerify(mldsa, [], signature, context);
}
[ConditionalTheory(
typeof(MLDsaTestHelpers),
[nameof(MLDsaTestHelpers.SigningEmptyDataIsSupported), nameof(MLDsaTestHelpers.ExternalMuIsSupported)])]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public void GenerateSignVerifyEmptyMessageExternalMuNoContext(MLDsaAlgorithm algorithm)
{
using MLDsa mldsa = GenerateKey(algorithm);
SignAndVerifyExternalMu(mldsa, [], []);
}
[ConditionalTheory(
typeof(MLDsaTestHelpers),
[nameof(MLDsaTestHelpers.SigningEmptyDataIsSupported), nameof(MLDsaTestHelpers.ExternalMuIsSupported)])]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public void GenerateSignVerifyEmptyMessageExternalMuWithContext(MLDsaAlgorithm algorithm)
{
using MLDsa mldsa = GenerateKey(algorithm);
byte[] context = [1, 1, 3, 5, 6];
SignAndVerifyExternalMu(mldsa, [], context);
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllMLDsaAlgorithms), MemberType = typeof(MLDsaTestsData))]
public void GenerateSignExportPublicVerifyWithPublicOnly(MLDsaAlgorithm algorithm)
@ -100,6 +140,8 @@ namespace System.Security.Cryptography.Tests
byte[] signature;
byte[] hash = HashInfo.Sha512.GetHash(data);
byte[] signaturePreHash;
byte[]? mu = null;
byte[] muSignature = null;
using (MLDsa mldsa = GenerateKey(algorithm))
{
@ -110,13 +152,25 @@ namespace System.Security.Cryptography.Tests
AssertExtensions.TrueExpression(mldsa.VerifyPreHash(hash, signaturePreHash, HashInfo.Sha512.Oid));
publicKey = mldsa.ExportMLDsaPublicKey();
mu = CalculateMu(mldsa, data);
if (mu is not null)
{
muSignature = mldsa.SignMu(mu);
}
}
using (MLDsa mldsaPub = ImportPublicKey(algorithm, publicKey))
{
ExerciseSuccessfulVerify(mldsaPub, data, signature, []);
ExerciseSuccessfulVerify(mldsaPub, data, signature, [], mu);
ExerciseSuccessfulVerifyPreHash(mldsaPub, HashInfo.Sha512.Oid, hash, signaturePreHash, []);
AssertExtensions.FalseExpression(mldsaPub.VerifyPreHash(hash, signature, HashInfo.Sha512.Oid));
if (muSignature is not null)
{
ExerciseSuccessfulVerify(mldsaPub, data, muSignature, [], mu);
}
}
}
@ -233,6 +287,14 @@ namespace System.Security.Cryptography.Tests
Assert.Equal(testCase.ShouldPass, mldsa.VerifyPreHash(hash, testCase.Signature, testCase.HashAlgOid, testCase.Context));
}
[ConditionalTheory(typeof(MLDsaTestHelpers), nameof(MLDsaTestHelpers.ExternalMuIsSupported))]
[MemberData(nameof(MLDsaTestsData.AllExternalMuMLDsaNistTestCases), MemberType = typeof(MLDsaTestsData))]
public void NistImportPublicKeyVerifyExternalMu(MLDsaNistTestCase testCase)
{
using MLDsa mldsa = ImportPublicKey(testCase.Algorithm, testCase.PublicKey);
Assert.Equal(testCase.ShouldPass, mldsa.VerifyMu(testCase.Mu, testCase.Signature));
}
[Theory]
[MemberData(nameof(MLDsaTestsData.AllPureMLDsaNistTestCases), MemberType = typeof(MLDsaTestsData))]
public void NistImportSecretKeyVerifyExportsAndSignature(MLDsaNistTestCase testCase)
@ -330,7 +392,63 @@ namespace System.Security.Cryptography.Tests
Assert.Contains(hashInfo.Name.Name, ce.Message);
}
protected static void ExerciseSuccessfulVerify(MLDsa mldsa, byte[] data, byte[] signature, byte[] context)
protected static byte[]? CalculateMu(MLDsa mldsa, byte[] data, byte[]? context = null)
{
#if NET8_0_OR_GREATER
if (MLDsaTestHelpers.ExternalMuIsSupported)
{
byte[] mu = new byte[mldsa.Algorithm.MuSizeInBytes];
Span<byte> trSpan = mu.AsSpan(0, 64);
using (Shake256 shake = new Shake256())
{
shake.AppendData(mldsa.ExportMLDsaPublicKey());
shake.GetHashAndReset(trSpan);
shake.AppendData(trSpan);
Span<byte> delimOrContextLength = [ 0 ];
shake.AppendData(delimOrContextLength);
delimOrContextLength[0] = checked((byte)(context?.Length ?? 0));
shake.AppendData(delimOrContextLength);
if (context is not null)
{
shake.AppendData(context);
}
if (data is not null)
{
shake.AppendData(data);
}
shake.GetHashAndReset(mu);
}
return mu;
}
#endif
return null;
}
protected static void SignAndVerifyExternalMu(MLDsa mldsa, byte[] data, byte[] context)
{
byte[]? mu = CalculateMu(mldsa, data, context);
byte[] signature;
if (mu is not null)
{
signature = mldsa.SignMu(mu);
ExerciseSuccessfulVerify(mldsa, data, signature, context, mu);
}
signature = mldsa.SignData(data, context);
ExerciseSuccessfulVerify(mldsa, data, signature, context, mu);
}
protected static void ExerciseSuccessfulVerify(MLDsa mldsa, byte[] data, byte[] signature, byte[]? context, byte[]? mu = null)
{
ReadOnlySpan<byte> buffer = [0, 1, 2, 3];
@ -354,11 +472,23 @@ namespace System.Security.Cryptography.Tests
AssertExtensions.FalseExpression(mldsa.VerifyData(buffer.Slice(1, 3), signature, context));
}
if (mu is not null)
{
AssertExtensions.TrueExpression(mldsa.VerifyMu(mu, signature));
}
signature[0] ^= 1;
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, context));
{
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, context));
if (mu is not null)
{
AssertExtensions.FalseExpression(mldsa.VerifyMu(mu, signature));
}
}
signature[0] ^= 1;
if (context.Length > 0)
if (context?.Length > 0)
{
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, Array.Empty<byte>()));
AssertExtensions.FalseExpression(mldsa.VerifyData(data, signature, ReadOnlySpan<byte>.Empty));
@ -377,6 +507,11 @@ namespace System.Security.Cryptography.Tests
}
AssertExtensions.TrueExpression(mldsa.VerifyData(data, signature, context));
if (mu is not null)
{
AssertExtensions.TrueExpression(mldsa.VerifyMu(mu, signature));
}
}
protected static void ExerciseSuccessfulVerifyPreHash(MLDsa mldsa, string hashAlgorithmOid, byte[] hash, byte[] signature, byte[] context)

View File

@ -87,6 +87,9 @@
<data name="Argument_KemInvalidSeedLength" xml:space="preserve">
<value>The specified private seed is not the correct length for the ML-KEM algorithm.</value>
</data>
<data name="Argument_MLDsaMuInvalidLength" xml:space="preserve">
<value>The specified mu value is not the correct length for the ML-DSA algorithm.</value>
</data>
<data name="Argument_PemEncoding_InvalidLabel" xml:space="preserve">
<value>The specified label is not valid.</value>
</data>
@ -108,15 +111,15 @@
<data name="Argument_PrivateKeyWrongSizeForAlgorithm" xml:space="preserve">
<value>The private key is not the correct size for the indicated algorithm.</value>
</data>
<data name="Argument_PrivateSeedWrongSizeForAlgorithm" xml:space="preserve">
<value>The private seed is not the correct size for the indicated algorithm.</value>
</data>
<data name="Argument_PublicKeyWrongSizeForAlgorithm" xml:space="preserve">
<value>The public key is not the correct size for the indicated algorithm.</value>
</data>
<data name="Argument_SecretKeyWrongSizeForAlgorithm" xml:space="preserve">
<value>The secret key is not the correct size for the indicated algorithm.</value>
</data>
<data name="Argument_PrivateSeedWrongSizeForAlgorithm" xml:space="preserve">
<value>The private seed is not the correct size for the indicated algorithm.</value>
</data>
<data name="Argument_SignatureContextTooLong255" xml:space="preserve">
<value>The specified signature context exceeds the maximum length of 255 bytes.</value>
</data>

View File

@ -1961,8 +1961,12 @@ namespace System.Security.Cryptography
public static System.Security.Cryptography.MLDsa ImportSubjectPublicKeyInfo(byte[] source) { throw null; }
public static System.Security.Cryptography.MLDsa ImportSubjectPublicKeyInfo(System.ReadOnlySpan<byte> source) { throw null; }
public byte[] SignData(byte[] data, byte[]? context = null) { throw null; }
public void SignData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.ReadOnlySpan<byte> context = default(System.ReadOnlySpan<byte>)) { throw null; }
public void SignData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.ReadOnlySpan<byte> context = default(System.ReadOnlySpan<byte>)) { }
protected abstract void SignDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.Span<byte> destination);
public byte[] SignMu(byte[] externalMu) { throw null; }
public byte[] SignMu(System.ReadOnlySpan<byte> externalMu) { throw null; }
public void SignMu(System.ReadOnlySpan<byte> externalMu, System.Span<byte> destination) { }
protected abstract void SignMuCore(System.ReadOnlySpan<byte> externalMu, System.Span<byte> destination);
public byte[] SignPreHash(byte[] hash, string hashAlgorithmOid, byte[]? context = null) { throw null; }
public void SignPreHash(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, string hashAlgorithmOid, System.ReadOnlySpan<byte> context = default(System.ReadOnlySpan<byte>)) { }
protected abstract void SignPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.Span<byte> destination);
@ -1975,6 +1979,9 @@ namespace System.Security.Cryptography
public bool VerifyData(byte[] data, byte[] signature, byte[]? context = null) { throw null; }
public bool VerifyData(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.ReadOnlySpan<byte> context = default(System.ReadOnlySpan<byte>)) { throw null; }
protected abstract bool VerifyDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.ReadOnlySpan<byte> signature);
public bool VerifyMu(byte[] externalMu, byte[] signature) { throw null; }
public bool VerifyMu(System.ReadOnlySpan<byte> externalMu, System.ReadOnlySpan<byte> signature) { throw null; }
protected abstract bool VerifyMuCore(System.ReadOnlySpan<byte> externalMu, System.ReadOnlySpan<byte> signature);
public bool VerifyPreHash(byte[] hash, byte[] signature, string hashAlgorithmOid, byte[]? context = null) { throw null; }
public bool VerifyPreHash(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature, string hashAlgorithmOid, System.ReadOnlySpan<byte> context = default(System.ReadOnlySpan<byte>)) { throw null; }
protected abstract bool VerifyPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.ReadOnlySpan<byte> signature);
@ -1986,6 +1993,7 @@ namespace System.Security.Cryptography
public static System.Security.Cryptography.MLDsaAlgorithm MLDsa44 { get { throw null; } }
public static System.Security.Cryptography.MLDsaAlgorithm MLDsa65 { get { throw null; } }
public static System.Security.Cryptography.MLDsaAlgorithm MLDsa87 { get { throw null; } }
public int MuSizeInBytes { get { throw null; } }
public string Name { get { throw null; } }
public int PrivateSeedSizeInBytes { get { throw null; } }
public int PublicKeySizeInBytes { get { throw null; } }
@ -2009,9 +2017,11 @@ namespace System.Security.Cryptography
protected override void ExportMLDsaSecretKeyCore(System.Span<byte> destination) { }
public System.Security.Cryptography.CngKey GetKey() { throw null; }
protected override void SignDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
protected override void SignMuCore(System.ReadOnlySpan<byte> externalMu, System.Span<byte> destination) { }
protected override void SignPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.Span<byte> destination) { }
protected override bool TryExportPkcs8PrivateKeyCore(System.Span<byte> destination, out int bytesWritten) { throw null; }
protected override bool VerifyDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.ReadOnlySpan<byte> signature) { throw null; }
protected override bool VerifyMuCore(System.ReadOnlySpan<byte> externalMu, System.ReadOnlySpan<byte> signature) { throw null; }
protected override bool VerifyPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.ReadOnlySpan<byte> signature) { throw null; }
}
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
@ -2030,9 +2040,11 @@ namespace System.Security.Cryptography
protected override void ExportMLDsaPublicKeyCore(System.Span<byte> destination) { }
protected override void ExportMLDsaSecretKeyCore(System.Span<byte> destination) { }
protected override void SignDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
protected override void SignMuCore(System.ReadOnlySpan<byte> externalMu, System.Span<byte> destination) { }
protected override void SignPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.Span<byte> destination) { }
protected override bool TryExportPkcs8PrivateKeyCore(System.Span<byte> destination, out int bytesWritten) { throw null; }
protected override bool VerifyDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.ReadOnlySpan<byte> signature) { throw null; }
protected override bool VerifyMuCore(System.ReadOnlySpan<byte> externalMu, System.ReadOnlySpan<byte> signature) { throw null; }
protected override bool VerifyPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.ReadOnlySpan<byte> signature) { throw null; }
}
public abstract partial class MLKem : System.IDisposable
@ -3163,6 +3175,7 @@ namespace System.Security.Cryptography
protected override void ExportSlhDsaSecretKeyCore(System.Span<byte> destination) { }
protected override void SignDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
protected override void SignPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.Span<byte> destination) { }
protected override bool TryExportPkcs8PrivateKeyCore(System.Span<byte> destination, out int bytesWritten) { throw null; }
protected override bool VerifyDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> context, System.ReadOnlySpan<byte> signature) { throw null; }
protected override bool VerifyPreHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> context, string hashAlgorithmOid, System.ReadOnlySpan<byte> signature) { throw null; }
}

View File

@ -141,6 +141,9 @@
<data name="Argument_KemInvalidSeedLength" xml:space="preserve">
<value>The specified private seed is not the correct length for the ML-KEM algorithm.</value>
</data>
<data name="Argument_MLDsaMuInvalidLength" xml:space="preserve">
<value>The specified mu value is not the correct length for the ML-DSA algorithm.</value>
</data>
<data name="Argument_PrivateKeyWrongSizeForAlgorithm" xml:space="preserve">
<value>The private key is not the correct size for the indicated algorithm.</value>
</data>

View File

@ -100,6 +100,12 @@ namespace System.Security.Cryptography
signature,
static (key, encodedMessage, signature) => Interop.Crypto.MLDsaVerifyPreEncoded(key, encodedMessage, signature));
protected override void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination) =>
Interop.Crypto.MLDsaSignExternalMu(_key, externalMu, destination);
protected override bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature) =>
Interop.Crypto.MLDsaVerifyExternalMu(_key, externalMu, signature);
protected override void ExportMLDsaPublicKeyCore(Span<byte> destination) =>
Interop.Crypto.MLDsaExportPublicKey(_key, destination);

View File

@ -52,6 +52,18 @@ namespace System.Security.Cryptography
throw new PlatformNotSupportedException();
}
protected override void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination)
{
Debug.Fail("Caller should have checked platform availability.");
throw new PlatformNotSupportedException();
}
protected override bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature)
{
Debug.Fail("Caller should have checked platform availability.");
throw new PlatformNotSupportedException();
}
protected override void ExportMLDsaPublicKeyCore(Span<byte> destination)
{
Debug.Fail("Caller should have checked platform availability.");

View File

@ -101,6 +101,14 @@ namespace System.Security.Cryptography
signature,
static (key, encodedMessage, signature) => Interop.Crypto.MLDsaVerifyPreEncoded(key, encodedMessage, signature));
/// <inheritdoc />
protected override void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination) =>
Interop.Crypto.MLDsaSignExternalMu(_key, externalMu, destination);
/// <inheritdoc />
protected override bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature) =>
Interop.Crypto.MLDsaVerifyExternalMu(_key, externalMu, signature);
/// <inheritdoc />
protected override void ExportMLDsaPublicKeyCore(Span<byte> destination) =>
Interop.Crypto.MLDsaExportPublicKey(_key, destination);

View File

@ -256,10 +256,12 @@ static const Entry s_cryptoNative[] =
DllImportEntry(CryptoNative_MLDsaExportPublicKey)
DllImportEntry(CryptoNative_MLDsaGenerateKey)
DllImportEntry(CryptoNative_MLDsaGetPalId)
DllImportEntry(CryptoNative_MLDsaSignPure)
DllImportEntry(CryptoNative_MLDsaVerifyPure)
DllImportEntry(CryptoNative_MLDsaSignExternalMu)
DllImportEntry(CryptoNative_MLDsaSignPreEncoded)
DllImportEntry(CryptoNative_MLDsaSignPure)
DllImportEntry(CryptoNative_MLDsaVerifyExternalMu)
DllImportEntry(CryptoNative_MLDsaVerifyPreEncoded)
DllImportEntry(CryptoNative_MLDsaVerifyPure)
DllImportEntry(CryptoNative_NewX509Stack)
DllImportEntry(CryptoNative_ObjNid2Obj)
DllImportEntry(CryptoNative_ObjObj2Txt)

View File

@ -168,6 +168,10 @@ c_static_assert(EVP_PKEY_PUBLIC_KEY == 134);
#define OSSL_SIGNATURE_PARAM_CONTEXT_STRING "context-string"
#endif
#ifndef OSSL_SIGNATURE_PARAM_MU
#define OSSL_SIGNATURE_PARAM_MU "mu"
#endif
#if defined FEATURE_DISTRO_AGNOSTIC_SSL || OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM
#include "apibridge_30_rev.h"
#endif

View File

@ -152,6 +152,137 @@ int32_t CryptoNative_MLDsaVerifyPreEncoded(EVP_PKEY *pkey,
return CryptoNative_EvpPKeyVerifyPreEncoded(pkey, extraHandle, msg, msgLen, sig, sigLen);
}
int32_t CryptoNative_MLDsaSignExternalMu(EVP_PKEY* pKey,
void* extraHandle,
uint8_t* mu, int32_t muLen,
uint8_t* destination, int32_t destinationLen)
{
assert(pKey);
assert(muLen >= 0);
assert(destination);
#if defined(NEED_OPENSSL_3_0) && HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT
if (!API_EXISTS(EVP_PKEY_sign_message_init) ||
!API_EXISTS(EVP_PKEY_verify_message_init))
{
return -1;
}
ERR_clear_error();
EVP_PKEY_CTX* ctx = NULL;
int ret = -1;
ctx = EvpPKeyCtxCreateFromPKey(pKey, extraHandle);
if (!ctx)
{
goto done;
}
int muYes = 1;
OSSL_PARAM initParams[] =
{
OSSL_PARAM_construct_int(OSSL_SIGNATURE_PARAM_MU, &muYes),
OSSL_PARAM_construct_end(),
};
if (EVP_PKEY_sign_message_init(ctx, NULL, initParams) <= 0)
{
goto done;
}
size_t dstLen = Int32ToSizeT(destinationLen);
if (EVP_PKEY_sign(ctx, destination, &dstLen, mu, Int32ToSizeT(muLen)) == 1)
{
if (dstLen != Int32ToSizeT(destinationLen))
{
assert(false); // length mismatch
goto done;
}
ret = 1;
}
else
{
ret = 0;
}
done:
if (ctx != NULL) EVP_PKEY_CTX_free(ctx);
return ret;
#else
(void)pKey;
(void)extraHandle;
(void)mu;
(void)muLen;
(void)destination;
(void)destinationLen;
return -1;
#endif
}
int32_t CryptoNative_MLDsaVerifyExternalMu(EVP_PKEY* pKey,
void* extraHandle,
uint8_t* mu, int32_t muLen,
uint8_t* sig, int32_t sigLen)
{
assert(pKey);
assert(muLen >= 0);
assert(sig);
#if defined(NEED_OPENSSL_3_0) && HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT
if (!API_EXISTS(EVP_PKEY_sign_message_init) ||
!API_EXISTS(EVP_PKEY_verify_message_init))
{
return -1;
}
ERR_clear_error();
EVP_PKEY_CTX* ctx = NULL;
int ret = -1;
ctx = EvpPKeyCtxCreateFromPKey(pKey, extraHandle);
if (!ctx)
{
goto done;
}
int muYes = 1;
OSSL_PARAM initParams[] =
{
OSSL_PARAM_construct_int(OSSL_SIGNATURE_PARAM_MU, &muYes),
OSSL_PARAM_construct_end(),
};
if (EVP_PKEY_verify_message_init(ctx, NULL, initParams) <= 0)
{
goto done;
}
ret = EVP_PKEY_verify(ctx, sig, Int32ToSizeT(sigLen), mu, Int32ToSizeT(muLen)) == 1;
done:
if (ctx != NULL) EVP_PKEY_CTX_free(ctx);
return ret;
#else
(void)pKey;
(void)extraHandle;
(void)mu;
(void)muLen;
(void)sig;
(void)sigLen;
return -1;
#endif
}
int32_t CryptoNative_MLDsaExportSecretKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength)
{
return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PRIV_KEY, destination, destinationLength);

View File

@ -24,7 +24,7 @@ PALEXPORT EVP_PKEY* CryptoNative_MLDsaGenerateKey(const char* keyType, uint8_t*
/*
Sign a message using the provided ML-DSA key.
Returns 1 on success, 0 on a mismatched signature, -1 on error.
Returns 1 on success, 0 on signing failure, -1 on other error.
*/
PALEXPORT int32_t CryptoNative_MLDsaSignPure(EVP_PKEY *pkey,
void* extraHandle,
@ -46,7 +46,7 @@ PALEXPORT int32_t CryptoNative_MLDsaVerifyPure(EVP_PKEY *pkey,
/*
Sign an encoded message using the provided ML-DSA key.
Returns 1 on success, 0 on a mismatched signature, -1 on error.
Returns 1 on success, 0 on signing failure, -1 on other error.
*/
PALEXPORT int32_t CryptoNative_MLDsaSignPreEncoded(EVP_PKEY *pkey,
void* extraHandle,
@ -63,6 +63,26 @@ PALEXPORT int32_t CryptoNative_MLDsaVerifyPreEncoded(EVP_PKEY *pkey,
uint8_t* msg, int32_t msgLen,
uint8_t* sig, int32_t sigLen);
/*
Sign an externally produced signature mu with the provided ML-DSA key.
Returns 1 on success, 0 on signing failure, -1 on other error.
*/
PALEXPORT int32_t CryptoNative_MLDsaSignExternalMu(EVP_PKEY* pKey,
void* extraHandle,
uint8_t* mu, int32_t muLen,
uint8_t* destination, int32_t destinationLen);
/*
Verifies an externally produced signature mu with the provided ML-DSA key.
Returns 1 on success, 0 on mismatched signature, -1 on error.
*/
PALEXPORT int32_t CryptoNative_MLDsaVerifyExternalMu(EVP_PKEY* pKey,
void* extraHandle,
uint8_t* mu, int32_t muLen,
uint8_t* sig, int32_t sigLen);
/*
Export the secret key from the given ML-DSA key.
*/