Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,44 @@ internal static unsafe void BCryptSignHashPqcPure(
throw Interop.BCrypt.CreateCryptographicException(status);
}
}

internal static unsafe void BCryptSignHashPqcPreHash(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> hash,
string hashAlgorithmIdentifier,
ReadOnlySpan<byte> context,
Span<byte> destination)
{
NTSTATUS status;
int bytesWritten;

fixed (byte* pHash = &MemoryMarshal.GetReference(hash))
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
fixed (byte* pContext = &MemoryMarshal.GetReference(context))
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
{
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
paddingInfo.pbCtx = (IntPtr)pContext;
paddingInfo.cbCtx = context.Length;
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;

status = BCryptSignHash(
key,
&paddingInfo,
pHash,
hash.Length,
pDest,
destination.Length,
out bytesWritten,
BCryptSignVerifyFlags.BCRYPT_PAD_PQDSA);
}

Debug.Assert(bytesWritten == destination.Length);

if (status != BCrypt.NTSTATUS.STATUS_SUCCESS)
{
throw BCrypt.CreateCryptographicException(status);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,37 @@ internal static unsafe bool BCryptVerifySignaturePqcPure(

return status == NTSTATUS.STATUS_SUCCESS;
}

internal static unsafe bool BCryptVerifySignaturePqcPreHash(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> hash,
string hashAlgorithmIdentifier,
ReadOnlySpan<byte> context,
ReadOnlySpan<byte> signature)
{
NTSTATUS status;

fixed (byte* pHash = &MemoryMarshal.GetReference(hash))
fixed (byte* pSignature = &MemoryMarshal.GetReference(signature))
fixed (byte* pContext = &MemoryMarshal.GetReference(context))
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
{
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
paddingInfo.pbCtx = (IntPtr)pContext;
paddingInfo.cbCtx = context.Length;
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;

status = BCryptVerifySignature(
key,
&paddingInfo,
pHash,
hash.Length,
pSignature,
signature.Length,
BCryptSignVerifyFlags.BCRYPT_PAD_PQDSA);
}

return status == NTSTATUS.STATUS_SUCCESS;
}
}
}
109 changes: 105 additions & 4 deletions src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,29 @@ public void SignPreHash(ReadOnlySpan<byte> hash, Span<byte> destination, string
SR.Argument_SignatureContextTooLong255);
}

Helpers.ValidateHashLength(hash, hashAlgorithmOid);
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
hashAlgorithmOid,
out int hashLengthInBytes,
out bool insufficientCollisionResistance);

if (hashAlgorithmIdentifier is null)
{
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmOid));
}

if (insufficientCollisionResistance)
{
throw new CryptographicException(SR.Format(
SR.Cryptography_HashMLDsaAlgorithmMismatch,
Algorithm.Name,
hashAlgorithmIdentifier));
}

if (hashLengthInBytes != hash.Length)
{
throw new CryptographicException(SR.Cryptography_HashLengthMismatch);
}

ThrowIfDisposed();

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

Helpers.ValidateHashLength(hash, hashAlgorithmOid);
ThrowIfDisposed();
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
hashAlgorithmOid,
out int hashLengthInBytes,
out bool insufficientCollisionResistance);

if (signature.Length != Algorithm.SignatureSizeInBytes)
if (hashAlgorithmIdentifier is null || insufficientCollisionResistance ||
signature.Length != Algorithm.SignatureSizeInBytes)
{
return false;
}

if (hashLengthInBytes != hash.Length)
{
throw new CryptographicException(SR.Cryptography_HashLengthMismatch);
}

ThrowIfDisposed();

return VerifyPreHashCore(hash, context, hashAlgorithmOid, signature);
}

Expand Down Expand Up @@ -2114,7 +2146,76 @@ internal static void ThrowIfNotSupported()
}
}

// Returns a hash algorithm identifier for an OID.
// insufficientCollisionResistance is true if the hash algorithm is known, but does not meet the required
// collision resistance from FIPS 204.
private protected string? MapHashOidToAlgorithm(
string hashOid,
out int hashLengthInBytes,
out bool insufficientCollisionResistance)
{
int hashLambda;
string hashAlgorithmIdentifier;

switch (hashOid)
{
case Oids.Md5:
hashLengthInBytes = 128 / 8;
insufficientCollisionResistance = true;
return HashAlgorithmNames.MD5;
case Oids.Sha1:
hashLengthInBytes = 160 / 8;
insufficientCollisionResistance = true;
return HashAlgorithmNames.SHA1;
case Oids.Sha256:
hashLengthInBytes = 256 / 8;
hashLambda = 256 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHA256;
break;
case Oids.Sha3_256:
hashLengthInBytes = 256 / 8;
hashLambda = 256 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHA3_256;
break;
case Oids.Sha384:
hashLengthInBytes = 384 / 8;
hashLambda = 384 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHA384;
break;
case Oids.Sha3_384:
hashLengthInBytes = 384 / 8;
hashLambda = 384 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHA3_384;
break;
case Oids.Sha512:
hashLengthInBytes = 512 / 8;
hashLambda = 512 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHA512;
break;
case Oids.Sha3_512:
hashLengthInBytes = 512 / 8;
hashLambda = 512 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHA3_512;
break;
case Oids.Shake128: // SHAKE-128 with 256-bits of output
hashLengthInBytes = 256 / 8;
hashLambda = 256 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHAKE128;
break;
case Oids.Shake256: // SHAKE-256 with 512-bits of output
hashLengthInBytes = 512 / 8;
hashLambda = 512 / 2;
hashAlgorithmIdentifier = HashAlgorithmNames.SHAKE256;
break;
default:
hashLengthInBytes = 0;
insufficientCollisionResistance = false;
return null;
}

insufficientCollisionResistance = hashLambda < Algorithm.LambdaCollisionStrength;
return hashAlgorithmIdentifier;
}

private delegate TResult ExportPkcs8PrivateKeyFunc<TResult>(ReadOnlySpan<byte> pkcs8);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,21 @@ public sealed class MLDsaAlgorithm : IEquatable<MLDsaAlgorithm>
public int SignatureSizeInBytes { get; }

internal string Oid { get; }

/// <summary>
/// Initializes a new instance of the <see cref="MLDsaAlgorithm" /> structure with a custom name.
/// </summary>
/// <param name="name">
/// The name of the algorithm.
/// </param>
/// <param name="secretKeySizeInBytes">
/// The size of the secret key in bytes.
/// </param>
/// <param name="publicKeySizeInBytes">
/// The size of the public key in bytes.
/// </param>
/// <param name="signatureSizeInBytes">
/// The size of the signature in bytes.
/// </param>
/// <param name="oid">
/// The OID of the algorithm.
/// </param>
private MLDsaAlgorithm(string name, int secretKeySizeInBytes, int publicKeySizeInBytes, int signatureSizeInBytes, string oid)
internal int LambdaCollisionStrength { get; }

private MLDsaAlgorithm(
string name,
int secretKeySizeInBytes,
int publicKeySizeInBytes,
int signatureSizeInBytes,
int lambdaCollisionStrength,
string oid)
{
Name = name;
SecretKeySizeInBytes = secretKeySizeInBytes;
PublicKeySizeInBytes = publicKeySizeInBytes;
SignatureSizeInBytes = signatureSizeInBytes;
LambdaCollisionStrength = lambdaCollisionStrength;
Oid = oid;
}

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

/// <summary>
/// Gets an ML-DSA algorithm identifier for the ML-DSA-65 algorithm.
/// </summary>
/// <value>
/// An ML-DSA algorithm identifier for the ML-DSA-65 algorithm.
/// </value>
public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65", 4032, 1952, 3309, Oids.MLDsa65);
public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65", 4032, 1952, 3309, 192, Oids.MLDsa65);

/// <summary>
/// Gets an ML-DSA algorithm identifier for the ML-DSA-87 algorithm.
/// </summary>
/// <value>
/// An ML-DSA algorithm identifier for the ML-DSA-87 algorithm.
/// </value>
public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, Oids.MLDsa87);
public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, 256, Oids.MLDsa87);

internal static MLDsaAlgorithm? GetMLDsaAlgorithmFromOid(string? oid)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,74 @@ protected override unsafe bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlyS
}

/// <inheritdoc/>
protected override void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, Span<byte> destination) =>
throw new PlatformNotSupportedException();
protected override unsafe void SignPreHashCore(
ReadOnlySpan<byte> hash,
ReadOnlySpan<byte> context,
string hashAlgorithmOid,
Span<byte> destination)
{
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
hashAlgorithmOid,
out int hashLengthInBytes,
out bool insufficientCollisionResistance);

Debug.Assert(hashAlgorithmIdentifier is not null);
Debug.Assert(!insufficientCollisionResistance);
Debug.Assert(hashLengthInBytes == hash.Length);

using (SafeNCryptKeyHandle duplicatedHandle = _key.Handle)
{
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
fixed (void* pContext = context)
{
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
paddingInfo.pbCtx = (IntPtr)pContext;
paddingInfo.cbCtx = context.Length;
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;

duplicatedHandle.SignHash(
hash,
destination,
Interop.NCrypt.AsymmetricPaddingMode.NCRYPT_PAD_PQDSA_FLAG,
&paddingInfo);
}
}
}

/// <inheritdoc/>
protected override bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature) =>
throw new PlatformNotSupportedException();
protected override unsafe bool VerifyPreHashCore(
ReadOnlySpan<byte> hash,
ReadOnlySpan<byte> context,
string hashAlgorithmOid,
ReadOnlySpan<byte> signature)
{
string? hashAlgorithmIdentifier = MapHashOidToAlgorithm(
hashAlgorithmOid,
out int hashLengthInBytes,
out bool insufficientCollisionResistance);

Debug.Assert(hashAlgorithmIdentifier is not null);
Debug.Assert(!insufficientCollisionResistance);
Debug.Assert(hashLengthInBytes == hash.Length);

using (SafeNCryptKeyHandle duplicatedHandle = _key.Handle)
{
fixed (char* pHashAlgorithmIdentifier = hashAlgorithmIdentifier)
fixed (void* pContext = context)
{
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
paddingInfo.pbCtx = (IntPtr)pContext;
paddingInfo.cbCtx = context.Length;
paddingInfo.pszPreHashAlgId = (IntPtr)pHashAlgorithmIdentifier;

return duplicatedHandle.VerifyHash(
hash,
signature,
Interop.NCrypt.AsymmetricPaddingMode.NCRYPT_PAD_PQDSA_FLAG,
&paddingInfo);
}
}
}

[SupportedOSPlatform("windows")]
internal static MLDsaCng ImportPkcs8PrivateKey(byte[] source, out int bytesRead)
Expand Down
Loading
Loading