Skip to content

SHA3 #84132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 52 commits into from
Jun 1, 2023
Merged

SHA3 #84132

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ef69307
Initial SHA3 bringup on Linux
vcsjones Mar 19, 2023
64f1c2e
Fix failures on unsupported platforms
vcsjones Mar 20, 2023
d077e4e
Some HKDF tests
vcsjones Mar 20, 2023
deccd5a
Get HKDF working with tests
vcsjones Mar 20, 2023
2fa0c13
Add derived hash tests
vcsjones Mar 20, 2023
9319d66
Remove outdated comment
vcsjones Mar 20, 2023
9d1f651
Fix hash validation for PBKDF2
vcsjones Mar 20, 2023
0e79766
Wire in CNG algorithm support testing
vcsjones Mar 20, 2023
5741ea2
Fix querying invalid handles
vcsjones Mar 20, 2023
faa42cf
Wire SHA3 in to SP800108
vcsjones Mar 21, 2023
e8ff0b7
SP800108: Throw PNSE for SHA3 on .NET 8 when not supported
vcsjones Mar 21, 2023
7b90cca
Fix CNG identifiers
vcsjones Mar 23, 2023
9948ff6
Fix PlatformDetection
vcsjones Mar 23, 2023
8081638
Use correct version
vcsjones Mar 23, 2023
693e19b
Psuedo handles for Windows
vcsjones Mar 23, 2023
1f399e7
Fix algorithm identifiers throughout
vcsjones Mar 23, 2023
e80d2fd
Block sizes for SP800-108
vcsjones Mar 23, 2023
34e3077
Add one shots for SP800-108 in CNG
vcsjones Mar 23, 2023
54d16d4
Use known consts for identifiers
vcsjones Mar 23, 2023
8b8a193
SHA3_256 Platform guards
vcsjones Mar 29, 2023
63d383b
Fix SHA3-384 and 512 UOSP
vcsjones Mar 29, 2023
95af4f5
Fix missing SHA3 test coverage
vcsjones Mar 30, 2023
14bc9a0
Only ask CNG if supported for SHA3
vcsjones Mar 30, 2023
b1a27c4
Fix SHA3 availability detection for RSAOpenSsl
vcsjones Mar 30, 2023
bb13b32
Revert "Fix SHA3 availability detection for RSAOpenSsl"
vcsjones Mar 31, 2023
733813c
Prevent SHA3 usage on RSAOpenSsl for macOS
vcsjones Mar 31, 2023
3d608eb
Fixup CAPI and add some tests for signatures
vcsjones Mar 31, 2023
f459f63
Fix verify for CryptoServiceProvider, too
vcsjones Apr 1, 2023
4ae22be
Make PSS as forgiving as PKCS1
vcsjones Apr 1, 2023
c178ca0
Add HMAC and hash tests for ECDH with SHA-3
vcsjones Apr 1, 2023
52f5434
Merge remote-tracking branch 'ms/main' into sha3
vcsjones Apr 13, 2023
81071fd
Code review feedback
vcsjones Apr 13, 2023
630b72d
More code review feedback
vcsjones Apr 13, 2023
a8d26e1
Merge remote-tracking branch 'ms/main' into sha3
vcsjones Apr 25, 2023
9fc9541
Merge remote-tracking branch 'ms/main' into sha3
vcsjones May 3, 2023
86d7ec6
Merge remote-tracking branch 'ms/main' into sha3
vcsjones May 9, 2023
7dfcd6f
Remove UOSP attribute from SHA3
vcsjones May 9, 2023
d7d9abe
Remove unused using for HMAC
vcsjones May 9, 2023
f904880
Merge remote-tracking branch 'ms/main' into sha3
vcsjones May 17, 2023
43385c2
Code review feedback on HKDF tests
vcsjones May 17, 2023
08af75f
Additional code review feedback
vcsjones May 17, 2023
8abcc7a
Add tests for IsSupported on algorithm implementations
vcsjones May 17, 2023
dc8ae5f
Simplify IsBCryptAlgorithmSupported
vcsjones May 17, 2023
b3efe82
Implement and test SHA3 with SignedCms
vcsjones May 17, 2023
5305f10
Add multi-'block' tests for SHA3
vcsjones May 17, 2023
a639910
Use a separate bool to track cached EVP handles
vcsjones May 17, 2023
af7a6b1
Add a SHA3 certificate test
vcsjones May 18, 2023
95a7ced
Change IsSupported to have backing fields to help enlighten JIT about…
vcsjones May 19, 2023
81049b2
Change HashOneShotHelpers to use a switch
vcsjones May 19, 2023
64329a9
Add trailing commas
vcsjones May 19, 2023
c55fea4
Fix CNG test failures
vcsjones May 19, 2023
c735e7a
Fix hash algorithm support detection on Android
vcsjones May 21, 2023
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 @@ -47,12 +47,33 @@ internal static IntPtr EvpSha512() =>

internal static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch
{
nameof(HashAlgorithmName.SHA1) => EvpSha1(),
nameof(HashAlgorithmName.SHA256) => EvpSha256(),
nameof(HashAlgorithmName.SHA384) => EvpSha384(),
nameof(HashAlgorithmName.SHA512) => EvpSha512(),
nameof(HashAlgorithmName.MD5) => EvpMd5(),
HashAlgorithmNames.SHA1 => EvpSha1(),
HashAlgorithmNames.SHA256 => EvpSha256(),
HashAlgorithmNames.SHA384 => EvpSha384(),
HashAlgorithmNames.SHA512 => EvpSha512(),
HashAlgorithmNames.MD5 => EvpMd5(),
HashAlgorithmNames.SHA3_256 or HashAlgorithmNames.SHA3_384 or HashAlgorithmNames.SHA3_512 =>
throw new PlatformNotSupportedException(),
_ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId))
};

internal static bool HashAlgorithmSupported(string hashAlgorithmId)
{
switch (hashAlgorithmId)
{
case HashAlgorithmNames.SHA1:
case HashAlgorithmNames.SHA256:
case HashAlgorithmNames.SHA384:
case HashAlgorithmNames.SHA512:
case HashAlgorithmNames.MD5:
return true;
case HashAlgorithmNames.SHA3_256:
case HashAlgorithmNames.SHA3_384:
case HashAlgorithmNames.SHA3_512:
return false;
default:
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,128 @@ internal static partial class Crypto
private static volatile IntPtr s_evpSha256;
private static volatile IntPtr s_evpSha384;
private static volatile IntPtr s_evpSha512;
private static volatile IntPtr s_evpSha3_256;
private static volatile IntPtr s_evpSha3_384;
private static volatile IntPtr s_evpSha3_512;
private static volatile bool s_evpSha3_256Cached;
private static volatile bool s_evpSha3_384Cached;
private static volatile bool s_evpSha3_512Cached;

[LibraryImport(Libraries.CryptoNative)]
private static partial IntPtr CryptoNative_EvpMd5();

internal static IntPtr EvpMd5() =>
private static IntPtr EvpMd5() =>
s_evpMd5 != IntPtr.Zero ? s_evpMd5 : (s_evpMd5 = CryptoNative_EvpMd5());

[LibraryImport(Libraries.CryptoNative)]
internal static partial IntPtr CryptoNative_EvpSha1();
private static partial IntPtr CryptoNative_EvpSha1();

internal static IntPtr EvpSha1() =>
private static IntPtr EvpSha1() =>
s_evpSha1 != IntPtr.Zero ? s_evpSha1 : (s_evpSha1 = CryptoNative_EvpSha1());

[LibraryImport(Libraries.CryptoNative)]
internal static partial IntPtr CryptoNative_EvpSha256();
private static partial IntPtr CryptoNative_EvpSha256();

internal static IntPtr EvpSha256() =>
private static IntPtr EvpSha256() =>
s_evpSha256 != IntPtr.Zero ? s_evpSha256 : (s_evpSha256 = CryptoNative_EvpSha256());

[LibraryImport(Libraries.CryptoNative)]
internal static partial IntPtr CryptoNative_EvpSha384();
private static partial IntPtr CryptoNative_EvpSha384();

internal static IntPtr EvpSha384() =>
private static IntPtr EvpSha384() =>
s_evpSha384 != IntPtr.Zero ? s_evpSha384 : (s_evpSha384 = CryptoNative_EvpSha384());

[LibraryImport(Libraries.CryptoNative)]
internal static partial IntPtr CryptoNative_EvpSha512();
private static partial IntPtr CryptoNative_EvpSha512();

internal static IntPtr EvpSha512() =>
private static IntPtr EvpSha512() =>
s_evpSha512 != IntPtr.Zero ? s_evpSha512 : (s_evpSha512 = CryptoNative_EvpSha512());

internal static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch
[LibraryImport(Libraries.CryptoNative)]
private static partial IntPtr CryptoNative_EvpSha3_256();

private static IntPtr EvpSha3_256()
{
if (!s_evpSha3_256Cached)
{
s_evpSha3_256 = CryptoNative_EvpSha3_256();
s_evpSha3_256Cached = true;
}

return s_evpSha3_256;
}

[LibraryImport(Libraries.CryptoNative)]
private static partial IntPtr CryptoNative_EvpSha3_384();

private static IntPtr EvpSha3_384()
{
if (!s_evpSha3_384Cached)
{
s_evpSha3_384 = CryptoNative_EvpSha3_384();
s_evpSha3_384Cached = true;
}

return s_evpSha3_384;
}

[LibraryImport(Libraries.CryptoNative)]
private static partial IntPtr CryptoNative_EvpSha3_512();

private static IntPtr EvpSha3_512()
{
if (!s_evpSha3_512Cached)
{
s_evpSha3_512 = CryptoNative_EvpSha3_512();
s_evpSha3_512Cached = true;
}

return s_evpSha3_512;
}


internal static IntPtr HashAlgorithmToEvp(string hashAlgorithmId)
{
switch (hashAlgorithmId)
{
case HashAlgorithmNames.SHA1: return EvpSha1();
case HashAlgorithmNames.SHA256: return EvpSha256();
case HashAlgorithmNames.SHA384: return EvpSha384();
case HashAlgorithmNames.SHA512: return EvpSha512();
case HashAlgorithmNames.SHA3_256:
IntPtr sha3_256 = EvpSha3_256();
return sha3_256 != 0 ? sha3_256 : throw new PlatformNotSupportedException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do any of the myriad PNSEs in this change need a custom string, or will it always be pretty obvious that the thing not supported is a SHA-3?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most straight forward uses of SHA3 like SHA3_256 are going to check is supported in Create or their constructor, etc. Many other places try to guard against unsupported SHA3 (like RSA.SignData) guard as well.

We can change it to have a custom string if you prefer, but the call site that throws should make it pretty obvious. The throw here is unlikely to even get hit since we do more up-front checking, but, I can imagine there might be some path to this that doesn't have a precondition check, so an assert felt wrong.

case HashAlgorithmNames.SHA3_384:
IntPtr sha3_384 = EvpSha3_384();
return sha3_384 != 0 ? sha3_384 : throw new PlatformNotSupportedException();
case HashAlgorithmNames.SHA3_512:
IntPtr sha3_512 = EvpSha3_512();
return sha3_512 != 0 ? sha3_512 : throw new PlatformNotSupportedException();
case nameof(HashAlgorithmName.MD5): return EvpMd5();
default:
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId));
};
}

internal static bool HashAlgorithmSupported(string hashAlgorithmId)
{
nameof(HashAlgorithmName.SHA1) => EvpSha1(),
nameof(HashAlgorithmName.SHA256) => EvpSha256(),
nameof(HashAlgorithmName.SHA384) => EvpSha384(),
nameof(HashAlgorithmName.SHA512) => EvpSha512(),
nameof(HashAlgorithmName.MD5) => EvpMd5(),
_ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId))
};
switch (hashAlgorithmId)
{
case HashAlgorithmNames.SHA1:
case HashAlgorithmNames.SHA256:
case HashAlgorithmNames.SHA384:
case HashAlgorithmNames.SHA512:
case HashAlgorithmNames.MD5:
return true;
case HashAlgorithmNames.SHA3_256:
return EvpSha3_256() != 0;
case HashAlgorithmNames.SHA3_384:
return EvpSha3_384() != 0;
case HashAlgorithmNames.SHA3_512:
return EvpSha3_512() != 0;
default:
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal static partial class BCrypt
internal static class BCryptAlgorithmCache
{
private static readonly ConcurrentDictionary<(string HashAlgorithmId, BCryptOpenAlgorithmProviderFlags Flags), (SafeBCryptAlgorithmHandle Handle, int HashSizeInBytes)> s_handles = new();
private static readonly ConcurrentDictionary<(string HashAlgorithmId, BCryptOpenAlgorithmProviderFlags Flags), bool> s_supported = new();

/// <summary>
/// Returns a SafeBCryptAlgorithmHandle of the desired algorithm and flags. This is a shared handle so do not dispose it!
Expand Down Expand Up @@ -43,6 +44,42 @@ public static unsafe SafeBCryptAlgorithmHandle GetCachedBCryptAlgorithmHandle(st
}
}
}

public static unsafe bool IsBCryptAlgorithmSupported(string hashAlgorithmId, BCryptOpenAlgorithmProviderFlags flags)
{
var key = (hashAlgorithmId, flags);

if (s_supported.TryGetValue(key, out bool supported))
{
return supported;
}

NTSTATUS status = BCryptOpenAlgorithmProvider(
out SafeBCryptAlgorithmHandle handle,
key.hashAlgorithmId,
null,
key.flags);

bool isSupported = status == NTSTATUS.STATUS_SUCCESS;

if (s_supported.TryAdd(key, isSupported) && isSupported)
{
// It's a valid algorithm. Let's prime the handle cache while we are here. Presumably it's
// going to get used if we're asking if it's supported.
int hashSize = BCryptGetDWordProperty(handle, BCryptPropertyStrings.BCRYPT_HASH_LENGTH);
Debug.Assert(hashSize > 0);

if (s_handles.TryAdd(key, (handle, hashSize)))
{
// If we added the handle to the cache, don't dispose of it and return our answer.
return isSupported;
}
}

// Either the algorithm isn't supported or we don't need it for priming the cache, so Dispose.
handle.Dispose();
return isSupported;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public enum BCryptAlgPseudoHandle : uint
BCRYPT_HMAC_SHA384_ALG_HANDLE = 0x000000c1,
BCRYPT_HMAC_SHA512_ALG_HANDLE = 0x000000d1,
BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
BCRYPT_SHA3_256_ALG_HANDLE = 0x000003B1,
BCRYPT_SHA3_384_ALG_HANDLE = 0x000003C1,
BCRYPT_SHA3_512_ALG_HANDLE = 0x000003D1,
BCRYPT_HMAC_SHA3_256_ALG_HANDLE = 0x000003E1,
BCRYPT_HMAC_SHA3_384_ALG_HANDLE = 0x000003F1,
BCRYPT_HMAC_SHA3_512_ALG_HANDLE = 0x00000401,
}

internal static bool PseudoHandlesSupported { get; } =
Expand Down
Loading