Skip to content

Commit 73d13d9

Browse files
authored
Add external mu support to ML-DSA
This adds SignMu and VerifyMu to MLDsa, but does not provide an easy way for someone to obtain the mu value. Like with OpenSSL version 3.5, that is, as we say, "an exercise left to the reader." Although, a working example is present in the tests.
1 parent 46ead26 commit 73d13d9

File tree

22 files changed

+3001
-72
lines changed

22 files changed

+3001
-72
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.MLDsa.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,65 @@ internal static bool MLDsaVerifyPreEncoded(
184184
}
185185
}
186186

187+
[LibraryImport(Libraries.CryptoNative)]
188+
private static partial int CryptoNative_MLDsaSignExternalMu(
189+
SafeEvpPKeyHandle pkey, IntPtr extraHandle,
190+
ReadOnlySpan<byte> mu, int muLength,
191+
Span<byte> destination, int destinationLength);
192+
193+
internal static void MLDsaSignExternalMu(
194+
SafeEvpPKeyHandle pkey,
195+
ReadOnlySpan<byte> mu,
196+
Span<byte> destination)
197+
{
198+
const int Success = 1;
199+
const int SigningFailure = 0;
200+
201+
int ret = CryptoNative_MLDsaSignExternalMu(
202+
pkey, GetExtraHandle(pkey),
203+
mu, mu.Length,
204+
destination, destination.Length);
205+
206+
if (ret != Success)
207+
{
208+
Debug.Assert(ret == SigningFailure, $"Unexpected return value {ret} from {nameof(CryptoNative_MLDsaSignExternalMu)}.");
209+
throw Interop.Crypto.CreateOpenSslCryptographicException();
210+
}
211+
}
212+
213+
[LibraryImport(Libraries.CryptoNative)]
214+
private static partial int CryptoNative_MLDsaVerifyExternalMu(
215+
SafeEvpPKeyHandle pkey, IntPtr extraHandle,
216+
ReadOnlySpan<byte> mu, int muLength,
217+
ReadOnlySpan<byte> signature, int signatureLength);
218+
219+
internal static bool MLDsaVerifyExternalMu(
220+
SafeEvpPKeyHandle pkey,
221+
ReadOnlySpan<byte> mu,
222+
ReadOnlySpan<byte> signature)
223+
{
224+
const int ValidSignature = 1;
225+
const int InvalidSignature = 0;
226+
227+
int ret = CryptoNative_MLDsaVerifyExternalMu(
228+
pkey, GetExtraHandle(pkey),
229+
mu, mu.Length,
230+
signature, signature.Length);
231+
232+
if (ret == ValidSignature)
233+
{
234+
return true;
235+
}
236+
else if (ret == InvalidSignature)
237+
{
238+
return false;
239+
}
240+
else
241+
{
242+
throw Interop.Crypto.CreateOpenSslCryptographicException();
243+
}
244+
}
245+
187246
[LibraryImport(Libraries.CryptoNative)]
188247
private static partial int CryptoNative_MLDsaExportSecretKey(SafeEvpPKeyHandle pkey, Span<byte> destination, int destinationLength);
189248

src/libraries/Common/src/System/Security/Cryptography/Helpers.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,5 +264,18 @@ internal static void ThrowIfAsnInvalidLength(ReadOnlySpan<byte> data)
264264
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
265265
}
266266
}
267+
268+
#if !BUILDING_PKCS
269+
internal static void ThrowIfDestinationWrongLength(
270+
Span<byte> destination,
271+
int expectedLength,
272+
[System.Runtime.CompilerServices.CallerArgumentExpression(nameof(destination))] string? paramName = null)
273+
{
274+
if (destination.Length != expectedLength)
275+
{
276+
throw new ArgumentException(SR.Format(SR.Argument_DestinationImprecise, expectedLength), paramName);
277+
}
278+
}
279+
#endif
267280
}
268281
}

src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs

Lines changed: 154 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,7 @@ public void Dispose()
120120
/// </exception>
121121
public void SignData(ReadOnlySpan<byte> data, Span<byte> destination, ReadOnlySpan<byte> context = default)
122122
{
123-
int signatureSizeInBytes = Algorithm.SignatureSizeInBytes;
124-
125-
if (destination.Length != signatureSizeInBytes)
126-
{
127-
throw new ArgumentException(
128-
SR.Format(SR.Argument_DestinationImprecise, signatureSizeInBytes),
129-
nameof(destination));
130-
}
123+
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SignatureSizeInBytes);
131124

132125
if (context.Length > MaxContextLength)
133126
{
@@ -309,13 +302,7 @@ public bool VerifyData(byte[] data, byte[] signature, byte[]? context = default)
309302
public void SignPreHash(ReadOnlySpan<byte> hash, Span<byte> destination, string hashAlgorithmOid, ReadOnlySpan<byte> context = default)
310303
{
311304
ArgumentNullException.ThrowIfNull(hashAlgorithmOid);
312-
313-
if (destination.Length != Algorithm.SignatureSizeInBytes)
314-
{
315-
throw new ArgumentException(
316-
SR.Format(SR.Argument_DestinationImprecise, Algorithm.SignatureSizeInBytes),
317-
nameof(destination));
318-
}
305+
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SignatureSizeInBytes);
319306

320307
if (context.Length > MaxContextLength)
321308
{
@@ -507,6 +494,155 @@ public bool VerifyPreHash(byte[] hash, byte[] signature, string hashAlgorithmOid
507494
new ReadOnlySpan<byte>(context));
508495
}
509496

497+
/// <inheritdoc cref="SignMu(ReadOnlySpan{byte})"/>
498+
/// <exception cref="ArgumentNullException"><paramref name="externalMu"/> is <see langword="null"/>.</exception>
499+
public byte[] SignMu(byte[] externalMu)
500+
{
501+
ArgumentNullException.ThrowIfNull(externalMu);
502+
503+
return SignMu(new ReadOnlySpan<byte>(externalMu));
504+
}
505+
506+
/// <summary>
507+
/// Signs the specified externally computed signature mu (&#x3BC;) value.
508+
/// </summary>
509+
/// <param name="externalMu">
510+
/// The signature mu value to sign.
511+
/// </param>
512+
/// <returns>
513+
/// ML-DSA signature for the specified mu value.
514+
/// </returns>
515+
/// <exception cref="ArgumentException">
516+
/// The buffer in <paramref name="externalMu"/> is the incorrect length for the signature mu value.
517+
/// </exception>
518+
/// <exception cref="ObjectDisposedException">
519+
/// This instance has been disposed.
520+
/// </exception>
521+
/// <exception cref="CryptographicException">
522+
/// <para>The instance represents only a public key.</para>
523+
/// <para>-or-</para>
524+
/// <para>An error occurred while signing the hash.</para>
525+
/// </exception>
526+
/// <exception cref="PlatformNotSupportedException">
527+
/// The current platform does not support signing with an externally computed mu value.
528+
/// </exception>
529+
/// <seealso cref="VerifyMu(byte[], byte[])"/>
530+
public byte[] SignMu(ReadOnlySpan<byte> externalMu)
531+
{
532+
byte[] destination = new byte[Algorithm.SignatureSizeInBytes];
533+
SignMu(externalMu, destination.AsSpan());
534+
return destination;
535+
}
536+
537+
/// <summary>
538+
/// Signs the specified externally computed signature mu (&#x3BC;) value,
539+
/// writing the signature into the provided buffer.
540+
/// </summary>
541+
/// <param name="externalMu">
542+
/// The signature mu value to sign.
543+
/// </param>
544+
/// <param name="destination">
545+
/// The buffer to receive the signature. Its length must be exactly
546+
/// <see cref="MLDsaAlgorithm.SignatureSizeInBytes"/>.
547+
/// </param>
548+
/// <exception cref="ArgumentException">
549+
/// <para>
550+
/// The buffer in <paramref name="externalMu"/> is the incorrect length for the signature mu value.
551+
/// </para>
552+
/// <para>-or-</para>
553+
/// <para>
554+
/// The buffer in <paramref name="destination"/> is the incorrect length to receive the signature.
555+
/// </para>
556+
/// </exception>
557+
/// <exception cref="ObjectDisposedException">
558+
/// This instance has been disposed.
559+
/// </exception>
560+
/// <exception cref="CryptographicException">
561+
/// <para>The instance represents only a public key.</para>
562+
/// <para>-or-</para>
563+
/// <para>An error occurred while signing the hash.</para>
564+
/// </exception>
565+
/// <exception cref="PlatformNotSupportedException">
566+
/// The current platform does not support signing with an externally computed mu value.
567+
/// </exception>
568+
/// <seealso cref="VerifyMu(ReadOnlySpan{byte}, ReadOnlySpan{byte})"/>
569+
public void SignMu(ReadOnlySpan<byte> externalMu, Span<byte> destination)
570+
{
571+
if (externalMu.Length != Algorithm.MuSizeInBytes)
572+
throw new ArgumentException(SR.Argument_MLDsaMuInvalidLength, nameof(externalMu));
573+
574+
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SignatureSizeInBytes);
575+
ThrowIfDisposed();
576+
577+
SignMuCore(externalMu, destination);
578+
}
579+
580+
/// <summary>
581+
/// When overridden in a derived class, computes the remainder of the signature from the
582+
/// precomputed mu (&#x3BC;) value, writing it into the provided buffer.
583+
/// </summary>
584+
/// <param name="externalMu">
585+
/// The signature mu value to sign.
586+
/// </param>
587+
/// <param name="destination">
588+
/// The buffer to receive the signature, which will always be the exactly correct size for the algorithm.
589+
/// </param>
590+
/// <exception cref="CryptographicException">
591+
/// An error occurred while computing the signature.
592+
/// </exception>
593+
protected abstract void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination);
594+
595+
/// <inheritdoc cref="VerifyMu(ReadOnlySpan{byte}, ReadOnlySpan{byte})"/>
596+
/// <exception cref="ArgumentNullException">
597+
/// <paramref name="externalMu"/> or <paramref name="signature"/> is <see langword="null"/>.
598+
/// </exception>
599+
public bool VerifyMu(byte[] externalMu, byte[] signature)
600+
{
601+
ArgumentNullException.ThrowIfNull(externalMu);
602+
ArgumentNullException.ThrowIfNull(signature);
603+
604+
return VerifyMu(new ReadOnlySpan<byte>(externalMu), new ReadOnlySpan<byte>(signature));
605+
}
606+
607+
/// <summary>
608+
/// Verifies that a digital signature is valid for the provided externally computed signature mu (&#x3BC;) value.
609+
/// </summary>
610+
/// <param name="externalMu">The signature mu value.</param>
611+
/// <param name="signature">The signature to verify.</param>
612+
/// <returns>
613+
/// <see langword="true"/> if the digital signature is valid for the provided mu value;
614+
/// otherwise, <see langword="false"/>.
615+
/// </returns>
616+
/// <exception cref="ObjectDisposedException">
617+
/// This instance has been disposed.
618+
/// </exception>
619+
/// <exception cref="CryptographicException">An error occurred while verifying the mu value.</exception>
620+
/// <exception cref="PlatformNotSupportedException">
621+
/// The current platform does not support verification with an externally computed mu value.
622+
/// </exception>
623+
public bool VerifyMu(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature)
624+
{
625+
if (externalMu.Length != Algorithm.MuSizeInBytes || signature.Length != Algorithm.SignatureSizeInBytes)
626+
{
627+
return false;
628+
}
629+
630+
ThrowIfDisposed();
631+
632+
return VerifyMuCore(externalMu, signature);
633+
}
634+
635+
/// <summary>
636+
/// When overridden in a derived class,
637+
/// verifies that a digital signature is valid for the provided externally computed signature mu (&#x3BC;) value.
638+
/// </summary>
639+
/// <param name="externalMu">The signature mu value.</param>
640+
/// <param name="signature">The signature to verify.</param>
641+
/// <returns>
642+
/// <see langword="true"/> if the mu value is valid; otherwise, <see langword="false"/>.
643+
/// </returns>
644+
protected abstract bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature);
645+
510646
/// <summary>
511647
/// Exports the public-key portion of the current key in the X.509 SubjectPublicKeyInfo format.
512648
/// </summary>
@@ -1063,15 +1199,7 @@ public byte[] ExportMLDsaPublicKey()
10631199
/// </remarks>
10641200
public void ExportMLDsaPublicKey(Span<byte> destination)
10651201
{
1066-
int publicKeySizeInBytes = Algorithm.PublicKeySizeInBytes;
1067-
1068-
if (destination.Length != publicKeySizeInBytes)
1069-
{
1070-
throw new ArgumentException(
1071-
SR.Format(SR.Argument_DestinationImprecise, publicKeySizeInBytes),
1072-
nameof(destination));
1073-
}
1074-
1202+
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.PublicKeySizeInBytes);
10751203
ThrowIfDisposed();
10761204

10771205
ExportMLDsaPublicKeyCore(destination);
@@ -1113,15 +1241,7 @@ public byte[] ExportMLDsaSecretKey()
11131241
/// </exception>
11141242
public void ExportMLDsaSecretKey(Span<byte> destination)
11151243
{
1116-
int secretKeySizeInBytes = Algorithm.SecretKeySizeInBytes;
1117-
1118-
if (destination.Length != secretKeySizeInBytes)
1119-
{
1120-
throw new ArgumentException(
1121-
SR.Format(SR.Argument_DestinationImprecise, secretKeySizeInBytes),
1122-
nameof(destination));
1123-
}
1124-
1244+
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.SecretKeySizeInBytes);
11251245
ThrowIfDisposed();
11261246

11271247
ExportMLDsaSecretKeyCore(destination);
@@ -1161,14 +1281,7 @@ public byte[] ExportMLDsaPrivateSeed()
11611281
/// </exception>
11621282
public void ExportMLDsaPrivateSeed(Span<byte> destination)
11631283
{
1164-
int privateSeedSizeInBytes = Algorithm.PrivateSeedSizeInBytes;
1165-
if (destination.Length != privateSeedSizeInBytes)
1166-
{
1167-
throw new ArgumentException(
1168-
SR.Format(SR.Argument_DestinationImprecise, privateSeedSizeInBytes),
1169-
nameof(destination));
1170-
}
1171-
1284+
Helpers.ThrowIfDestinationWrongLength(destination, Algorithm.PrivateSeedSizeInBytes);
11721285
ThrowIfDisposed();
11731286

11741287
ExportMLDsaPrivateSeedCore(destination);

src/libraries/Common/src/System/Security/Cryptography/MLDsaAlgorithm.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ public sealed class MLDsaAlgorithm : IEquatable<MLDsaAlgorithm>
5353
/// </value>
5454
public int SignatureSizeInBytes { get; }
5555

56+
/// <summary>
57+
/// Gets the size, in bytes, of the mu (&#x3BC;) value for the current ML-DSA algorithm.
58+
/// </summary>
59+
/// <value>
60+
/// The size, in bytes, of the mu (&#x3BC;) value for the current ML-DSA algorithm.
61+
/// </value>
62+
public int MuSizeInBytes => 64;
63+
5664
internal string Oid { get; }
5765
internal int LambdaCollisionStrength { get; }
5866

src/libraries/Common/src/System/Security/Cryptography/MLDsaCng.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public MLDsaCng(CngKey key)
4848
_key = duplicateKey;
4949
}
5050

51+
/// <inheritdoc />
52+
protected override void SignMuCore(ReadOnlySpan<byte> mu, Span<byte> destination) =>
53+
throw new PlatformNotSupportedException();
54+
55+
/// <inheritdoc />
56+
protected override bool VerifyMuCore(ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature) =>
57+
throw new PlatformNotSupportedException();
58+
5159
private static MLDsaAlgorithm AlgorithmFromHandleWithPlatformCheck(CngKey key, out CngKey duplicateKey)
5260
{
5361
if (!Helpers.IsOSPlatformWindows)

src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.NotSupported.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ protected override void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<by
3131
protected override bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature) =>
3232
throw new PlatformNotSupportedException();
3333

34+
protected override void SignMuCore(ReadOnlySpan<byte> mu, Span<byte> destination) =>
35+
throw new PlatformNotSupportedException();
36+
37+
protected override bool VerifyMuCore(ReadOnlySpan<byte> mu, ReadOnlySpan<byte> signature) =>
38+
throw new PlatformNotSupportedException();
39+
3440
protected override void ExportMLDsaPublicKeyCore(Span<byte> destination) =>
3541
throw new PlatformNotSupportedException();
3642

src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.Windows.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ protected override bool VerifyPreHashCore(
9191
return Interop.BCrypt.BCryptVerifySignaturePqcPreHash(_key, hash, hashAlgorithmIdentifier, context, signature);
9292
}
9393

94+
protected override void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination) =>
95+
throw new PlatformNotSupportedException();
96+
97+
protected override bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature) =>
98+
throw new PlatformNotSupportedException();
99+
94100
internal static partial MLDsaImplementation GenerateKeyImpl(MLDsaAlgorithm algorithm)
95101
{
96102
Debug.Assert(SupportsAny());

0 commit comments

Comments
 (0)