-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Avoid finalizable internal state for non-FIPS scenarios #67998
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Diagnostics; | ||
using System.Runtime.Versioning; | ||
using System.Security.Cryptography; | ||
|
||
namespace Roslyn.Utilities; | ||
|
||
/// <summary>Provides support for computing a hash value incrementally across several segments.</summary> | ||
internal sealed class RoslynIncrementalHash : IDisposable | ||
{ | ||
private const int NTE_BAD_ALGID = -2146893816; | ||
|
||
private readonly HashAlgorithmName _algorithmName; | ||
private HashAlgorithm _hash; | ||
private bool _disposed; | ||
private bool _resetPending; | ||
|
||
/// <summary>Gets the name of the algorithm being performed.</summary> | ||
/// <value>The name of the algorithm being performed.</value> | ||
public HashAlgorithmName AlgorithmName => _algorithmName; | ||
|
||
private RoslynIncrementalHash(HashAlgorithmName name, HashAlgorithm hash) | ||
{ | ||
_algorithmName = name; | ||
_hash = hash; | ||
} | ||
|
||
/// <summary>Appends the specified data to the data already processed in the hash or HMAC.</summary> | ||
/// <param name="data">The data to process.</param> | ||
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ObjectDisposedException">The <see cref="RoslynIncrementalHash"/> object has already been disposed.</exception> | ||
public void AppendData(byte[] data) | ||
{ | ||
if (data == null) | ||
{ | ||
throw new ArgumentNullException(nameof(data)); | ||
} | ||
|
||
AppendData(data, 0, data.Length); | ||
} | ||
|
||
/// <summary>Appends the specified number of bytes from the specified data, starting at the specified offset, to the | ||
/// data already processed in the hash.</summary> | ||
/// <param name="data">The data to process.</param> | ||
/// <param name="offset">The offset into the byte array from which to begin using data.</param> | ||
/// <param name="count">The number of bytes to use from <paramref name="data"/>.</param> | ||
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> or <paramref name="offset"/> is negative. | ||
/// -or- | ||
/// <paramref name="count"/> is larger than the length of <paramref name="data"/>.</exception> | ||
/// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is larger than the data length.</exception> | ||
/// <exception cref="ObjectDisposedException">The <see cref="RoslynIncrementalHash"/> object has already been disposed.</exception> | ||
public void AppendData(byte[] data, int offset, int count) | ||
{ | ||
if (data == null) | ||
{ | ||
throw new ArgumentNullException(nameof(data)); | ||
} | ||
|
||
if (offset < 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(offset), "ArgumentOutOfRange_NeedNonNegNum"); | ||
} | ||
|
||
if (count < 0 || count > data.Length) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(count)); | ||
} | ||
|
||
if (data.Length - count < offset) | ||
{ | ||
throw new ArgumentException("Argument_InvalidOffLen"); | ||
} | ||
|
||
if (_disposed) | ||
{ | ||
throw new ObjectDisposedException(typeof(RoslynIncrementalHash).Name); | ||
} | ||
|
||
if (_resetPending) | ||
{ | ||
_hash.Initialize(); | ||
_resetPending = false; | ||
} | ||
|
||
_hash.TransformBlock(data, offset, count, null, 0); | ||
} | ||
|
||
/// <summary>Retrieves the hash for the data accumulated from prior calls to the <see cref="AppendData(byte[])"/> | ||
/// method, and resets the object to its initial state.</summary> | ||
/// <returns>The computed hash or HMAC.</returns> | ||
/// <exception cref="ObjectDisposedException">The <see cref="RoslynIncrementalHash"/> object has already been disposed.</exception> | ||
public byte[] GetHashAndReset() | ||
{ | ||
if (_disposed) | ||
{ | ||
throw new ObjectDisposedException(typeof(RoslynIncrementalHash).Name); | ||
} | ||
|
||
if (_resetPending) | ||
{ | ||
_hash.Initialize(); | ||
} | ||
|
||
_hash.TransformFinalBlock(Array.Empty<byte>(), 0, 0); | ||
byte[] hash = _hash.Hash!; | ||
_resetPending = true; | ||
return hash; | ||
} | ||
|
||
/// <summary>Releases the resources used by the current instance of the <see cref="RoslynIncrementalHash"/> class.</summary> | ||
public void Dispose() | ||
{ | ||
_disposed = true; | ||
if (_hash != null) | ||
{ | ||
_hash.Dispose(); | ||
_hash = null!; | ||
} | ||
} | ||
|
||
/// <summary>Creates a <see cref="RoslynIncrementalHash"/> for the specified algorithm.</summary> | ||
/// <param name="hashAlgorithm">The name of the hash algorithm to perform.</param> | ||
/// <returns>An <see cref="RoslynIncrementalHash"/> instance ready to compute the hash algorithm specified by <paramref name="hashAlgorithm"/>.</returns> | ||
/// <exception cref="ArgumentException"><paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <see langword="null"/> or an empty string.</exception> | ||
/// <exception cref="CryptographicException"><paramref name="hashAlgorithm" /> is not a known hash algorithm.</exception> | ||
public static RoslynIncrementalHash CreateHash(HashAlgorithmName hashAlgorithm) | ||
{ | ||
if (string.IsNullOrEmpty(hashAlgorithm.Name)) | ||
{ | ||
throw new ArgumentException("Cryptography_HashAlgorithmNameNullOrEmpty", nameof(hashAlgorithm)); | ||
} | ||
return new RoslynIncrementalHash(hashAlgorithm, GetHashAlgorithm(hashAlgorithm)); | ||
} | ||
|
||
private static HashAlgorithm GetHashAlgorithm(HashAlgorithmName hashAlgorithm) | ||
{ | ||
if (hashAlgorithm == HashAlgorithmName.MD5) | ||
{ | ||
return MD5.Create(); | ||
} | ||
|
||
if (hashAlgorithm == HashAlgorithmName.SHA1) | ||
{ | ||
return SHA1.Create(); | ||
} | ||
|
||
if (hashAlgorithm == HashAlgorithmName.SHA256) | ||
{ | ||
return SHA256.Create(); | ||
} | ||
|
||
if (hashAlgorithm == HashAlgorithmName.SHA384) | ||
{ | ||
return SHA384.Create(); | ||
} | ||
|
||
if (hashAlgorithm == HashAlgorithmName.SHA512) | ||
{ | ||
return SHA512.Create(); | ||
} | ||
Comment on lines
+142
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 The change relative to the reference implementation is here. .NET Framework creates instances of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💭 In theory, we could also address this by modifying the way we create our pool of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and it's hte provider that was causing the problem before? |
||
|
||
throw new CryptographicException(NTE_BAD_ALGID); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yikes. feels like this should be better on Core as well, by using available hash functions that don't require tehse intermediary allocations.