Skip to content

Commit aa122ad

Browse files
authored
Support HashML-DSA on Windows
1 parent d342697 commit aa122ad

File tree

15 files changed

+378
-125
lines changed

15 files changed

+378
-125
lines changed

src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,44 @@ internal static unsafe void BCryptSignHashPqcPure(
111111
throw Interop.BCrypt.CreateCryptographicException(status);
112112
}
113113
}
114+
115+
internal static unsafe void BCryptSignHashPqcPreHash(
116+
SafeBCryptKeyHandle key,
117+
ReadOnlySpan<byte> hash,
118+
string hashAlgorithmIdentifier,
119+
ReadOnlySpan<byte> context,
120+
Span<byte> destination)
121+
{
122+
NTSTATUS status;
123+
int bytesWritten;
124+
125+
fixed (byte* pHash = &MemoryMarshal.GetReference(hash))
126+
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
127+
fixed (byte* pContext = &MemoryMarshal.GetReference(context))
128+
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
129+
{
130+
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
131+
paddingInfo.pbCtx = (IntPtr)pContext;
132+
paddingInfo.cbCtx = context.Length;
133+
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;
134+
135+
status = BCryptSignHash(
136+
key,
137+
&paddingInfo,
138+
pHash,
139+
hash.Length,
140+
pDest,
141+
destination.Length,
142+
out bytesWritten,
143+
BCryptSignVerifyFlags.BCRYPT_PAD_PQDSA);
144+
}
145+
146+
Debug.Assert(bytesWritten == destination.Length);
147+
148+
if (status != BCrypt.NTSTATUS.STATUS_SUCCESS)
149+
{
150+
throw BCrypt.CreateCryptographicException(status);
151+
}
152+
}
114153
}
115154
}

src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,37 @@ internal static unsafe bool BCryptVerifySignaturePqcPure(
115115

116116
return status == NTSTATUS.STATUS_SUCCESS;
117117
}
118+
119+
internal static unsafe bool BCryptVerifySignaturePqcPreHash(
120+
SafeBCryptKeyHandle key,
121+
ReadOnlySpan<byte> hash,
122+
string hashAlgorithmIdentifier,
123+
ReadOnlySpan<byte> context,
124+
ReadOnlySpan<byte> signature)
125+
{
126+
NTSTATUS status;
127+
128+
fixed (byte* pHash = &MemoryMarshal.GetReference(hash))
129+
fixed (byte* pSignature = &MemoryMarshal.GetReference(signature))
130+
fixed (byte* pContext = &MemoryMarshal.GetReference(context))
131+
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
132+
{
133+
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
134+
paddingInfo.pbCtx = (IntPtr)pContext;
135+
paddingInfo.cbCtx = context.Length;
136+
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;
137+
138+
status = BCryptVerifySignature(
139+
key,
140+
&paddingInfo,
141+
pHash,
142+
hash.Length,
143+
pSignature,
144+
signature.Length,
145+
BCryptSignVerifyFlags.BCRYPT_PAD_PQDSA);
146+
}
147+
148+
return status == NTSTATUS.STATUS_SUCCESS;
149+
}
118150
}
119151
}

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

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,29 @@ public void SignPreHash(ReadOnlySpan<byte> hash, Span<byte> destination, string
325325
SR.Argument_SignatureContextTooLong255);
326326
}
327327

328-
Helpers.ValidateHashLength(hash, hashAlgorithmOid);
328+
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
329+
hashAlgorithmOid,
330+
out int hashLengthInBytes,
331+
out bool insufficientCollisionResistance);
332+
333+
if (hashAlgorithmIdentifier is null)
334+
{
335+
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmOid));
336+
}
337+
338+
if (insufficientCollisionResistance)
339+
{
340+
throw new CryptographicException(SR.Format(
341+
SR.Cryptography_HashMLDsaAlgorithmMismatch,
342+
Algorithm.Name,
343+
hashAlgorithmIdentifier));
344+
}
345+
346+
if (hashLengthInBytes != hash.Length)
347+
{
348+
throw new CryptographicException(SR.Cryptography_HashLengthMismatch);
349+
}
350+
329351
ThrowIfDisposed();
330352

331353
SignPreHashCore(hash, context, hashAlgorithmOid, destination);
@@ -413,14 +435,24 @@ public bool VerifyPreHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature,
413435
SR.Argument_SignatureContextTooLong255);
414436
}
415437

416-
Helpers.ValidateHashLength(hash, hashAlgorithmOid);
417-
ThrowIfDisposed();
438+
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
439+
hashAlgorithmOid,
440+
out int hashLengthInBytes,
441+
out bool insufficientCollisionResistance);
418442

419-
if (signature.Length != Algorithm.SignatureSizeInBytes)
443+
if (hashAlgorithmIdentifier is null || insufficientCollisionResistance ||
444+
signature.Length != Algorithm.SignatureSizeInBytes)
420445
{
421446
return false;
422447
}
423448

449+
if (hashLengthInBytes != hash.Length)
450+
{
451+
throw new CryptographicException(SR.Cryptography_HashLengthMismatch);
452+
}
453+
454+
ThrowIfDisposed();
455+
424456
return VerifyPreHashCore(hash, context, hashAlgorithmOid, signature);
425457
}
426458

@@ -2114,7 +2146,76 @@ internal static void ThrowIfNotSupported()
21142146
}
21152147
}
21162148

2149+
// Returns a hash algorithm identifier for an OID.
2150+
// insufficientCollisionResistance is true if the hash algorithm is known, but does not meet the required
2151+
// collision resistance from FIPS 204.
2152+
private protected string? MapHashOidToAlgorithm(
2153+
string hashOid,
2154+
out int hashLengthInBytes,
2155+
out bool insufficientCollisionResistance)
2156+
{
2157+
int hashLambda;
2158+
string hashAlgorithmIdentifier;
2159+
2160+
switch (hashOid)
2161+
{
2162+
case Oids.Md5:
2163+
hashLengthInBytes = 128 / 8;
2164+
insufficientCollisionResistance = true;
2165+
return HashAlgorithmNames.MD5;
2166+
case Oids.Sha1:
2167+
hashLengthInBytes = 160 / 8;
2168+
insufficientCollisionResistance = true;
2169+
return HashAlgorithmNames.SHA1;
2170+
case Oids.Sha256:
2171+
hashLengthInBytes = 256 / 8;
2172+
hashLambda = 256 / 2;
2173+
hashAlgorithmIdentifier = HashAlgorithmNames.SHA256;
2174+
break;
2175+
case Oids.Sha3_256:
2176+
hashLengthInBytes = 256 / 8;
2177+
hashLambda = 256 / 2;
2178+
hashAlgorithmIdentifier = HashAlgorithmNames.SHA3_256;
2179+
break;
2180+
case Oids.Sha384:
2181+
hashLengthInBytes = 384 / 8;
2182+
hashLambda = 384 / 2;
2183+
hashAlgorithmIdentifier = HashAlgorithmNames.SHA384;
2184+
break;
2185+
case Oids.Sha3_384:
2186+
hashLengthInBytes = 384 / 8;
2187+
hashLambda = 384 / 2;
2188+
hashAlgorithmIdentifier = HashAlgorithmNames.SHA3_384;
2189+
break;
2190+
case Oids.Sha512:
2191+
hashLengthInBytes = 512 / 8;
2192+
hashLambda = 512 / 2;
2193+
hashAlgorithmIdentifier = HashAlgorithmNames.SHA512;
2194+
break;
2195+
case Oids.Sha3_512:
2196+
hashLengthInBytes = 512 / 8;
2197+
hashLambda = 512 / 2;
2198+
hashAlgorithmIdentifier = HashAlgorithmNames.SHA3_512;
2199+
break;
2200+
case Oids.Shake128: // SHAKE-128 with 256-bits of output
2201+
hashLengthInBytes = 256 / 8;
2202+
hashLambda = 256 / 2;
2203+
hashAlgorithmIdentifier = HashAlgorithmNames.SHAKE128;
2204+
break;
2205+
case Oids.Shake256: // SHAKE-256 with 512-bits of output
2206+
hashLengthInBytes = 512 / 8;
2207+
hashLambda = 512 / 2;
2208+
hashAlgorithmIdentifier = HashAlgorithmNames.SHAKE256;
2209+
break;
2210+
default:
2211+
hashLengthInBytes = 0;
2212+
insufficientCollisionResistance = false;
2213+
return null;
2214+
}
21172215

2216+
insufficientCollisionResistance = hashLambda < Algorithm.LambdaCollisionStrength;
2217+
return hashAlgorithmIdentifier;
2218+
}
21182219

21192220
private delegate TResult ExportPkcs8PrivateKeyFunc<TResult>(ReadOnlySpan<byte> pkcs8);
21202221
}

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

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,21 @@ public sealed class MLDsaAlgorithm : IEquatable<MLDsaAlgorithm>
5454
public int SignatureSizeInBytes { get; }
5555

5656
internal string Oid { get; }
57-
58-
/// <summary>
59-
/// Initializes a new instance of the <see cref="MLDsaAlgorithm" /> structure with a custom name.
60-
/// </summary>
61-
/// <param name="name">
62-
/// The name of the algorithm.
63-
/// </param>
64-
/// <param name="secretKeySizeInBytes">
65-
/// The size of the secret key in bytes.
66-
/// </param>
67-
/// <param name="publicKeySizeInBytes">
68-
/// The size of the public key in bytes.
69-
/// </param>
70-
/// <param name="signatureSizeInBytes">
71-
/// The size of the signature in bytes.
72-
/// </param>
73-
/// <param name="oid">
74-
/// The OID of the algorithm.
75-
/// </param>
76-
private MLDsaAlgorithm(string name, int secretKeySizeInBytes, int publicKeySizeInBytes, int signatureSizeInBytes, string oid)
57+
internal int LambdaCollisionStrength { get; }
58+
59+
private MLDsaAlgorithm(
60+
string name,
61+
int secretKeySizeInBytes,
62+
int publicKeySizeInBytes,
63+
int signatureSizeInBytes,
64+
int lambdaCollisionStrength,
65+
string oid)
7766
{
7867
Name = name;
7968
SecretKeySizeInBytes = secretKeySizeInBytes;
8069
PublicKeySizeInBytes = publicKeySizeInBytes;
8170
SignatureSizeInBytes = signatureSizeInBytes;
71+
LambdaCollisionStrength = lambdaCollisionStrength;
8272
Oid = oid;
8373
}
8474

@@ -91,23 +81,23 @@ private MLDsaAlgorithm(string name, int secretKeySizeInBytes, int publicKeySizeI
9181
/// <value>
9282
/// An ML-DSA algorithm identifier for the ML-DSA-44 algorithm.
9383
/// </value>
94-
public static MLDsaAlgorithm MLDsa44 { get; } = new MLDsaAlgorithm("ML-DSA-44", 2560, 1312, 2420, Oids.MLDsa44);
84+
public static MLDsaAlgorithm MLDsa44 { get; } = new MLDsaAlgorithm("ML-DSA-44", 2560, 1312, 2420, 128, Oids.MLDsa44);
9585

9686
/// <summary>
9787
/// Gets an ML-DSA algorithm identifier for the ML-DSA-65 algorithm.
9888
/// </summary>
9989
/// <value>
10090
/// An ML-DSA algorithm identifier for the ML-DSA-65 algorithm.
10191
/// </value>
102-
public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65", 4032, 1952, 3309, Oids.MLDsa65);
92+
public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65", 4032, 1952, 3309, 192, Oids.MLDsa65);
10393

10494
/// <summary>
10595
/// Gets an ML-DSA algorithm identifier for the ML-DSA-87 algorithm.
10696
/// </summary>
10797
/// <value>
10898
/// An ML-DSA algorithm identifier for the ML-DSA-87 algorithm.
10999
/// </value>
110-
public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, Oids.MLDsa87);
100+
public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, 256, Oids.MLDsa87);
111101

112102
internal static MLDsaAlgorithm? GetMLDsaAlgorithmFromOid(string? oid)
113103
{

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

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,74 @@ protected override unsafe bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlyS
267267
}
268268

269269
/// <inheritdoc/>
270-
protected override void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, Span<byte> destination) =>
271-
throw new PlatformNotSupportedException();
270+
protected override unsafe void SignPreHashCore(
271+
ReadOnlySpan<byte> hash,
272+
ReadOnlySpan<byte> context,
273+
string hashAlgorithmOid,
274+
Span<byte> destination)
275+
{
276+
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
277+
hashAlgorithmOid,
278+
out int hashLengthInBytes,
279+
out bool insufficientCollisionResistance);
280+
281+
Debug.Assert(hashAlgorithmIdentifier is not null);
282+
Debug.Assert(!insufficientCollisionResistance);
283+
Debug.Assert(hashLengthInBytes == hash.Length);
284+
285+
using (SafeNCryptKeyHandle duplicatedHandle = _key.Handle)
286+
{
287+
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
288+
fixed (void* pContext = context)
289+
{
290+
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
291+
paddingInfo.pbCtx = (IntPtr)pContext;
292+
paddingInfo.cbCtx = context.Length;
293+
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;
294+
295+
duplicatedHandle.SignHash(
296+
hash,
297+
destination,
298+
Interop.NCrypt.AsymmetricPaddingMode.NCRYPT_PAD_PQDSA_FLAG,
299+
&paddingInfo);
300+
}
301+
}
302+
}
272303

273304
/// <inheritdoc/>
274-
protected override bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature) =>
275-
throw new PlatformNotSupportedException();
305+
protected override unsafe bool VerifyPreHashCore(
306+
ReadOnlySpan<byte> hash,
307+
ReadOnlySpan<byte> context,
308+
string hashAlgorithmOid,
309+
ReadOnlySpan<byte> signature)
310+
{
311+
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
312+
hashAlgorithmOid,
313+
out int hashLengthInBytes,
314+
out bool insufficientCollisionResistance);
315+
316+
Debug.Assert(hashAlgorithmIdentifier is not null);
317+
Debug.Assert(!insufficientCollisionResistance);
318+
Debug.Assert(hashLengthInBytes == hash.Length);
319+
320+
using (SafeNCryptKeyHandle duplicatedHandle = _key.Handle)
321+
{
322+
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
323+
fixed (void* pContext = context)
324+
{
325+
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
326+
paddingInfo.pbCtx = (IntPtr)pContext;
327+
paddingInfo.cbCtx = context.Length;
328+
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;
329+
330+
return duplicatedHandle.VerifyHash(
331+
hash,
332+
signature,
333+
Interop.NCrypt.AsymmetricPaddingMode.NCRYPT_PAD_PQDSA_FLAG,
334+
&paddingInfo);
335+
}
336+
}
337+
}
276338

277339
[SupportedOSPlatform("windows")]
278340
internal static MLDsaCng ImportPkcs8PrivateKey(byte[] source, out int bytesRead)

0 commit comments

Comments
 (0)