Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 505c2bb

Browse files
author
Atsushi Kanamori
committed
Implement full CNG hash support for RSACng.SignData
1 parent f6e99e3 commit 505c2bb

24 files changed

+620
-243
lines changed
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Internal.Cryptography
99
//
1010
// This abstract class represents a reusable hash object and can wrap a CNG or WinRT hash object.
1111
//
12-
internal abstract class HashProvider
12+
internal abstract class HashProvider : IDisposable
1313
{
1414
// Adds new data to be hashed. This can be called repeatedly in order to hash data from incontiguous sources.
1515
public void AppendHashData(byte[] data, int offset, int count)
@@ -41,6 +41,13 @@ public void AppendHashData(byte[] data, int offset, int count)
4141
// Returns the length of the byte array returned by FinalizeHashAndReset.
4242
public abstract int HashSizeInBytes { get; }
4343

44+
// Releases any native resources and keys used by the HashProvider.
45+
public void Dispose()
46+
{
47+
Dispose(true);
48+
GC.SuppressFinalize(this);
49+
}
50+
4451
// Releases any native resources and keys used by the HashProvider.
4552
public abstract void Dispose(bool disposing);
4653
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Security.Cryptography;
7+
8+
using Microsoft.Win32.SafeHandles;
9+
using NTSTATUS = Interop.BCrypt.NTSTATUS;
10+
using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags;
11+
using BCryptCreateHashFlags = Interop.BCrypt.BCryptCreateHashFlags;
12+
13+
namespace Internal.Cryptography
14+
{
15+
//
16+
// Provides hash services via the native provider (CNG).
17+
//
18+
internal sealed class HashProviderCng : HashProvider
19+
{
20+
//
21+
// - "hashAlgId" must be a name recognized by BCryptOpenAlgorithmProvider(). Examples: MD5, SHA1, SHA256.
22+
//
23+
// - "key" activates MAC hashing if present. If null, this HashProvider performs a regular old hash.
24+
//
25+
public HashProviderCng(string hashAlgId, byte[] key)
26+
{
27+
BCryptOpenAlgorithmProviderFlags dwFlags = BCryptOpenAlgorithmProviderFlags.None;
28+
if (key != null)
29+
{
30+
_key = key.CloneByteArray();
31+
dwFlags |= BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG;
32+
}
33+
34+
_hAlgorithm = Interop.BCrypt.BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(hashAlgId, dwFlags);
35+
36+
SafeBCryptHashHandle hHash = null;
37+
NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out hHash, IntPtr.Zero, 0, key, key == null ? 0 : key.Length, BCryptCreateHashFlags.BCRYPT_HASH_REUSABLE_FLAG);
38+
if (ntStatus == NTSTATUS.STATUS_INVALID_PARAMETER)
39+
{
40+
// If we got here, we're running on a downlevel OS (pre-Win8) that doesn't support reusable CNG hash objects. Fall back to creating a
41+
// new HASH object each time.
42+
ResetHashObject();
43+
}
44+
else if (ntStatus != NTSTATUS.STATUS_SUCCESS)
45+
{
46+
throw Interop.BCrypt.CreateCryptographicException(ntStatus);
47+
}
48+
else
49+
{
50+
_hHash = hHash;
51+
_reusable = true;
52+
}
53+
54+
unsafe
55+
{
56+
int cbSizeOfHashSize;
57+
int hashSize;
58+
ntStatus = Interop.BCrypt.BCryptGetProperty(hHash, Interop.BCrypt.BCryptPropertyStrings.BCRYPT_HASH_LENGTH, &hashSize, sizeof(int), out cbSizeOfHashSize, 0);
59+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
60+
throw Interop.BCrypt.CreateCryptographicException(ntStatus);
61+
_hashSize = hashSize;
62+
}
63+
return;
64+
}
65+
66+
public sealed override void AppendHashDataCore(byte[] data, int offset, int count)
67+
{
68+
unsafe
69+
{
70+
fixed (byte* pRgb = data)
71+
{
72+
NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hHash, pRgb + offset, count, 0);
73+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
74+
throw Interop.BCrypt.CreateCryptographicException(ntStatus);
75+
}
76+
}
77+
}
78+
79+
public sealed override byte[] FinalizeHashAndReset()
80+
{
81+
byte[] hash = new byte[_hashSize];
82+
NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hHash, hash, hash.Length, 0);
83+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
84+
throw Interop.BCrypt.CreateCryptographicException(ntStatus);
85+
86+
ResetHashObject();
87+
return hash;
88+
}
89+
90+
public sealed override void Dispose(bool disposing)
91+
{
92+
if (disposing)
93+
{
94+
DestroyHash();
95+
if (_key != null)
96+
{
97+
byte[] key = _key;
98+
_key = null;
99+
Array.Clear(key, 0, key.Length);
100+
}
101+
}
102+
}
103+
104+
public sealed override int HashSizeInBytes
105+
{
106+
get
107+
{
108+
return _hashSize;
109+
}
110+
}
111+
112+
private void ResetHashObject()
113+
{
114+
if (_reusable)
115+
return;
116+
DestroyHash();
117+
118+
SafeBCryptHashHandle hHash;
119+
NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out hHash, IntPtr.Zero, 0, _key, _key == null ? 0 : _key.Length, BCryptCreateHashFlags.None);
120+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
121+
throw Interop.BCrypt.CreateCryptographicException(ntStatus);
122+
123+
_hHash = hHash;
124+
}
125+
126+
private void DestroyHash()
127+
{
128+
SafeBCryptHashHandle hHash = _hHash;
129+
_hHash = null;
130+
if (hHash != null)
131+
{
132+
hHash.Dispose();
133+
}
134+
135+
// Not disposing of _hAlgorithm as we got this from a cache. So it's not ours to Dispose().
136+
}
137+
138+
private readonly SafeBCryptAlgorithmHandle _hAlgorithm;
139+
private SafeBCryptHashHandle _hHash;
140+
private byte[] _key;
141+
private readonly bool _reusable;
142+
143+
private readonly int _hashSize;
144+
}
145+
}
146+
147+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Collections.Generic;
7+
using System.Security.Cryptography;
8+
9+
using Microsoft.Win32.SafeHandles;
10+
11+
internal partial class Interop
12+
{
13+
internal partial class BCrypt
14+
{
15+
internal static class BCryptAlgorithmCache
16+
{
17+
/// <summary>
18+
/// Return a SafeBCryptAlgorithmHandle of the desired algorithm and flags. This is a shared handle so do not dispose it!
19+
/// </summary>
20+
public static SafeBCryptAlgorithmHandle GetCachedBCryptAlgorithmHandle(string hashAlgorithmId, BCryptOpenAlgorithmProviderFlags flags)
21+
{
22+
// There aren't that many hash algorithms around so rather than use a LowLevelDictionary and guard it with a lock,
23+
// we'll use a simple list. To avoid locking, we'll recreate the entire list each time an entry is added and replace it atomically.
24+
//
25+
// This does mean that on occasion, racing threads may create two handles of the same type, but this is ok.
26+
27+
// Latch the _cache value into a local so we aren't disrupted by concurrent changes to it.
28+
Entry[] cache = _cache;
29+
foreach (Entry entry in cache)
30+
{
31+
if (entry.HashAlgorithmId == hashAlgorithmId && entry.Flags == flags)
32+
return entry.Handle;
33+
}
34+
35+
SafeBCryptAlgorithmHandle safeBCryptAlgorithmHandle;
36+
NTSTATUS ntStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(out safeBCryptAlgorithmHandle, hashAlgorithmId, null, flags);
37+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
38+
throw Interop.BCrypt.CreateCryptographicException(ntStatus);
39+
40+
Entry[] newCache = new Entry[cache.Length + 1];
41+
Entry newEntry = new Entry(hashAlgorithmId, flags, safeBCryptAlgorithmHandle);
42+
Array.Copy(cache, newCache, cache.Length);
43+
newCache[newCache.Length - 1] = newEntry;
44+
45+
// Atomically overwrite the cache with our new cache. It's possible some other thread raced to add a new entry with us - if so, one of the new entries
46+
// will be lost and the next guy that requests it will have to allocate it again. That's considered acceptable collateral damage.
47+
_cache = newCache;
48+
return newEntry.Handle;
49+
}
50+
51+
private static volatile Entry[] _cache = Array.Empty<Entry>();
52+
53+
private struct Entry
54+
{
55+
public Entry(string hashAlgorithmId, BCryptOpenAlgorithmProviderFlags flags, SafeBCryptAlgorithmHandle handle)
56+
: this()
57+
{
58+
HashAlgorithmId = hashAlgorithmId;
59+
Flags = flags;
60+
Handle = handle;
61+
}
62+
63+
public string HashAlgorithmId { get; private set; }
64+
public BCryptOpenAlgorithmProviderFlags Flags { get; private set; }
65+
public SafeBCryptAlgorithmHandle Handle { get; private set; }
66+
}
67+
}
68+
}
69+
}

src/Common/src/Interop/Windows/BCrypt/Cng.cs

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -77,58 +77,6 @@ public static SafeAlgorithmHandle BCryptOpenAlgorithmProvider(String pszAlgId, S
7777
return hAlgorithm;
7878
}
7979

80-
public static SafeHashHandle BCryptCreateHash(this SafeAlgorithmHandle hAlgorithm, byte[] pbSecret, int dwFlags)
81-
{
82-
SafeHashHandle hHash = null;
83-
NTSTATUS ntStatus = Interop.BCryptCreateHash(hAlgorithm, out hHash, IntPtr.Zero, 0, pbSecret, pbSecret == null ? 0 : pbSecret.Length, dwFlags);
84-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
85-
throw CreateCryptographicException(ntStatus);
86-
return hHash;
87-
}
88-
89-
public static SafeHashHandle BCryptTryCreateReusableHash(this SafeAlgorithmHandle hAlgorithm, byte[] pbSecret)
90-
{
91-
const int BCRYPT_HASH_REUSABLE_FLAG = 0x00000020;
92-
93-
SafeHashHandle hHash = null;
94-
NTSTATUS ntStatus = Interop.BCryptCreateHash(hAlgorithm, out hHash, IntPtr.Zero, 0, pbSecret, pbSecret == null ? 0 : pbSecret.Length, BCRYPT_HASH_REUSABLE_FLAG);
95-
if (ntStatus == NTSTATUS.STATUS_INVALID_PARAMETER)
96-
return null; // Pre-Win8 OS's do not support BCRYPT_HASH_REUSABLE_FLAG.
97-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
98-
throw CreateCryptographicException(ntStatus);
99-
return hHash;
100-
}
101-
102-
public static unsafe void BCryptHashData(this SafeHashHandle hHash, byte* pbInput, int cbInput)
103-
{
104-
NTSTATUS ntStatus = Interop.BCryptHashData(hHash, pbInput, cbInput, 0);
105-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
106-
throw CreateCryptographicException(ntStatus);
107-
return;
108-
}
109-
110-
public static byte[] BCryptFinishHash(this SafeHashHandle hHash, int cbHashSize)
111-
{
112-
byte[] hash = new byte[cbHashSize];
113-
NTSTATUS ntStatus = Interop.BCryptFinishHash(hHash, hash, cbHashSize, 0);
114-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
115-
throw CreateCryptographicException(ntStatus);
116-
return hash;
117-
}
118-
119-
public static int GetHashSizeInBytes(this SafeHashHandle hHash)
120-
{
121-
unsafe
122-
{
123-
int cbSizeOfHashSize;
124-
int hashSize;
125-
NTSTATUS ntStatus = Interop.BCryptGetProperty(hHash, BCryptGetPropertyStrings.BCRYPT_HASH_LENGTH, (byte*)&hashSize, 4, out cbSizeOfHashSize, 0);
126-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
127-
throw CreateCryptographicException(ntStatus);
128-
return hashSize;
129-
}
130-
}
131-
13280
public static SafeKeyHandle BCryptImportKey(this SafeAlgorithmHandle hAlg, byte[] key)
13381
{
13482
unsafe
@@ -274,21 +222,8 @@ private static class Interop
274222
[DllImport(CngDll, CharSet = CharSet.Unicode)]
275223
public static extern NTSTATUS BCryptOpenAlgorithmProvider(out SafeAlgorithmHandle phAlgorithm, String pszAlgId, String pszImplementation, int dwFlags);
276224

277-
[DllImport(CngDll, CharSet = CharSet.Unicode)]
278-
public static extern NTSTATUS BCryptCreateHash(SafeAlgorithmHandle hAlgorithm, out SafeHashHandle phHash, IntPtr pbHashObject, int cbHashObject, [In, Out] byte[] pbSecret, int cbSecret, int dwFlags);
279-
280-
[DllImport(CngDll, CharSet = CharSet.Unicode)]
281-
public static extern unsafe NTSTATUS BCryptHashData(SafeHashHandle hHash, byte* pbInput, int cbInput, int dwFlags);
282-
283-
[DllImport(CngDll, CharSet = CharSet.Unicode)]
284-
public static extern NTSTATUS BCryptFinishHash(SafeHashHandle hHash, [Out] byte[] pbOutput, int cbOutput, int dwFlags);
285-
286-
[DllImport(CngDll, CharSet = CharSet.Unicode)]
287-
public static extern unsafe NTSTATUS BCryptGetProperty(SafeBCryptHandle hObject, String pszProperty, byte* pbOutput, int cbOutput, out int pcbResult, int dwFlags);
288-
289225
[DllImport(CngDll, CharSet = CharSet.Unicode)]
290226
public static extern unsafe NTSTATUS BCryptSetProperty(SafeAlgorithmHandle hObject, String pszProperty, String pbInput, int cbInput, int dwFlags);
291-
292227
[DllImport(CngDll, CharSet = CharSet.Unicode)]
293228
public static extern NTSTATUS BCryptImportKey(SafeAlgorithmHandle hAlgorithm, IntPtr hImportKey, String pszBlobType, out SafeKeyHandle hKey, IntPtr pbKeyObject, int cbKeyObject, byte[] pbInput, int cbInput, int dwFlags);
294229

@@ -365,6 +300,5 @@ protected sealed override bool ReleaseHandle()
365300
[DllImport(Cng.CngDll)]
366301
private static extern uint BCryptDestroyKey(IntPtr hKey);
367302
}
368-
369303
}
370304

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
7+
8+
internal partial class Interop
9+
{
10+
internal partial class BCrypt
11+
{
12+
[DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
13+
internal static extern NTSTATUS BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, int dwFlags);
14+
}
15+
}
16+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
7+
8+
using Microsoft.Win32.SafeHandles;
9+
10+
internal partial class Interop
11+
{
12+
internal partial class BCrypt
13+
{
14+
[DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
15+
internal static extern NTSTATUS BCryptCreateHash(SafeBCryptAlgorithmHandle hAlgorithm, out SafeBCryptHashHandle phHash, IntPtr pbHashObject, int cbHashObject, [In, Out] byte[] pbSecret, int cbSecret, BCryptCreateHashFlags dwFlags);
16+
17+
[Flags]
18+
internal enum BCryptCreateHashFlags : int
19+
{
20+
None = 0x00000000,
21+
BCRYPT_HASH_REUSABLE_FLAG = 0x00000020,
22+
}
23+
}
24+
}
25+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
7+
8+
internal partial class Interop
9+
{
10+
internal partial class BCrypt
11+
{
12+
[DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
13+
internal static extern NTSTATUS BCryptDestroyHash(IntPtr hHash);
14+
}
15+
}
16+

0 commit comments

Comments
 (0)