Skip to content

Commit

Permalink
Remove S.Sec.Crypto.Algorithms package dependency (#85701)
Browse files Browse the repository at this point in the history
* Remove S.Sec.Crypto.Algorithms package dependency

Contributes to #85641

System.Security.Cryptography.Algorithms/4.3.1 is being referenced in a
few .NET Framework builds. The reference to that package is undesirable
as it brings in the entire netstandard 1.x dependency graph.

The only type used from that package is IncrementalHash which isn't
available as a public API in .NET Framework before 4.7.1. Because of
that, polyfill the netfx code in with minor changes to the source to
make the analyzers happy.
  • Loading branch information
ViktorHofer authored May 3, 2023
1 parent bda61b9 commit c8f43d5
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 24 deletions.
1 change: 0 additions & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@
<SystemMemoryVersion>4.5.5</SystemMemoryVersion>
<SystemReflectionMetadataVersion>6.0.1</SystemReflectionMetadataVersion>
<SystemSecurityAccessControlVersion>6.0.0</SystemSecurityAccessControlVersion>
<SystemSecurityCryptographyAlgorithmsVersion>4.3.1</SystemSecurityCryptographyAlgorithmsVersion>
<SystemSecurityCryptographyCngVersion>5.0.0</SystemSecurityCryptographyCngVersion>
<SystemSecurityCryptographyOpenSslVersion>5.0.0</SystemSecurityCryptographyOpenSslVersion>
<SystemSecurityPrincipalWindowsVersion>5.0.0</SystemSecurityPrincipalWindowsVersion>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NETFRAMEWORK || NET471_OR_GREATER
#error "This implementation of IncrementalHash should only be used for .NET Framework where it is not available"
#endif

using System.Diagnostics;

namespace System.Security.Cryptography
{
/// <summary>
/// Provides support for computing a hash or HMAC value incrementally across several segments.
/// </summary>
internal sealed class IncrementalHash : IDisposable
{
private const int NTE_BAD_ALGID = unchecked((int)0x80090008);

private readonly HashAlgorithmName _algorithmName;
private HashAlgorithm _hash;
private bool _disposed;
private bool _resetPending;

private IncrementalHash(HashAlgorithmName name, HashAlgorithm hash)
{
Debug.Assert(!string.IsNullOrEmpty(name.Name));
Debug.Assert(hash != null);

_algorithmName = name;
_hash = hash;
}

/// <summary>
/// Get the name of the algorithm being performed.
/// </summary>
public HashAlgorithmName AlgorithmName
{
get { return _algorithmName; }
}

/// <summary>
/// Append the entire contents of <paramref name="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 <c>null</c>.</exception>
/// <exception cref="ObjectDisposedException">The 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>
/// Append <paramref name="count"/> bytes of <paramref name="data"/>, starting at <paramref name="offset"/>,
/// to the data already processed in the hash or HMAC.
/// </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 in the array to use as data.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="offset"/> is out of range. This parameter requires a non-negative number.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="count"/> is out of range. This parameter requires a non-negative number less than
/// the <see cref="Array.Length"/> value of <paramref name="data"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="count"/> is greater than
/// <paramref name="data"/>.<see cref="Array.Length"/> - <paramref name="offset"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">The 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), SR.ArgumentOutOfRange_NeedNonNegNum);
if (count < 0 || (count > data.Length))
throw new ArgumentOutOfRangeException(nameof(count));
if ((data.Length - count) < offset)
throw new ArgumentException(SR.Argument_InvalidOffLen);
if (_disposed)
throw new ObjectDisposedException(nameof(IncrementalHash));

Debug.Assert(_hash != null);

if (_resetPending)
{
_hash.Initialize();
_resetPending = false;
}

_hash.TransformBlock(data, offset, count, null, 0);
}

/// <summary>
/// Retrieve the hash or HMAC for the data accumulated from prior calls to
/// <see cref="AppendData(byte[])"/>, and return to the state the object
/// was in at construction.
/// </summary>
/// <returns>The computed hash or HMAC.</returns>
/// <exception cref="ObjectDisposedException">The object has already been disposed.</exception>
public byte[] GetHashAndReset()
{
if (_disposed)
throw new ObjectDisposedException(nameof(IncrementalHash));

Debug.Assert(_hash != null);

if (_resetPending)
{
// No point in setting _resetPending to false, we're about to set it to true.
_hash.Initialize();
}

_hash.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
byte[] hashValue = _hash.Hash;
_resetPending = true;

return hashValue;
}

/// <summary>
/// Release all resources used by the current instance of the
/// <see cref="IncrementalHash"/> class.
/// </summary>
public void Dispose()
{
_disposed = true;

if (_hash != null)
{
_hash.Dispose();
_hash = null;
}
}

/// <summary>
/// Create an <see cref="IncrementalHash"/> for the algorithm specified by <paramref name="hashAlgorithm"/>.
/// </summary>
/// <param name="hashAlgorithm">The name of the hash algorithm to perform.</param>
/// <returns>
/// An <see cref="IncrementalHash"/> instance ready to compute the hash algorithm specified
/// by <paramref name="hashAlgorithm"/>.
/// </returns>
/// <exception cref="ArgumentException">
/// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <c>null</c>, or
/// the empty string.
/// </exception>
/// <exception cref="CryptographicException"><paramref name="hashAlgorithm"/> is not a known hash algorithm.</exception>
public static IncrementalHash CreateHash(HashAlgorithmName hashAlgorithm)
{
if (string.IsNullOrEmpty(hashAlgorithm.Name))
throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));

return new IncrementalHash(hashAlgorithm, GetHashAlgorithm(hashAlgorithm));
}

/// <summary>
/// Create an <see cref="IncrementalHash"/> for the Hash-based Message Authentication Code (HMAC)
/// algorithm utilizing the hash algorithm specified by <paramref name="hashAlgorithm"/>, and a
/// key specified by <paramref name="key"/>.
/// </summary>
/// <param name="hashAlgorithm">The name of the hash algorithm to perform within the HMAC.</param>
/// <param name="key">
/// The secret key for the HMAC. The key can be any length, but a key longer than the output size
/// of the hash algorithm specified by <paramref name="hashAlgorithm"/> will be hashed (using the
/// algorithm specified by <paramref name="hashAlgorithm"/>) to derive a correctly-sized key. Therefore,
/// the recommended size of the secret key is the output size of the hash specified by
/// <paramref name="hashAlgorithm"/>.
/// </param>
/// <returns>
/// An <see cref="IncrementalHash"/> instance ready to compute the hash algorithm specified
/// by <paramref name="hashAlgorithm"/>.
/// </returns>
/// <exception cref="ArgumentException">
/// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <c>null</c>, or
/// the empty string.
/// </exception>
/// <exception cref="CryptographicException"><paramref name="hashAlgorithm"/> is not a known hash algorithm.</exception>
public static IncrementalHash CreateHMAC(HashAlgorithmName hashAlgorithm, byte[] key)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (string.IsNullOrEmpty(hashAlgorithm.Name))
throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));

return new IncrementalHash(hashAlgorithm, GetHMAC(hashAlgorithm, key));
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5351", Justification = "MD5 is used when the user asks for it.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 is used when the user asks for it.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5354", Justification = "SHA1 is used when the user asks for it.")]
private static HashAlgorithm GetHashAlgorithm(HashAlgorithmName hashAlgorithm)
{
if (hashAlgorithm == HashAlgorithmName.MD5)
return new MD5CryptoServiceProvider();
if (hashAlgorithm == HashAlgorithmName.SHA1)
return new SHA1CryptoServiceProvider();
if (hashAlgorithm == HashAlgorithmName.SHA256)
return new SHA256CryptoServiceProvider();
if (hashAlgorithm == HashAlgorithmName.SHA384)
return new SHA384CryptoServiceProvider();
if (hashAlgorithm == HashAlgorithmName.SHA512)
return new SHA512CryptoServiceProvider();

throw new CryptographicException(NTE_BAD_ALGID);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5351", Justification = "MD5 is used when the user asks for it.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 is used when the user asks for it.")]
private static HashAlgorithm GetHMAC(HashAlgorithmName hashAlgorithm, byte[] key)
{
if (hashAlgorithm == HashAlgorithmName.MD5)
return new HMACMD5(key);
if (hashAlgorithm == HashAlgorithmName.SHA1)
return new HMACSHA1(key);
if (hashAlgorithm == HashAlgorithmName.SHA256)
return new HMACSHA256(key);
if (hashAlgorithm == HashAlgorithmName.SHA384)
return new HMACSHA384(key);
if (hashAlgorithm == HashAlgorithmName.SHA512)
return new HMACSHA512(key);

throw new CryptographicException(NTE_BAD_ALGID);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
Link="Common\src\Extensions\NonCapturingTimer\NonCapturingTimer.cs" />
<Compile Include="$(CommonPath)System\ThrowHelper.cs"
Link="Common\System\ThrowHelper.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\IncrementalHash.netfx.cs"
Link="Common\System\Security\Cryptography\IncrementalHash.cs"
Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
</ItemGroup>

<ItemGroup>
Expand All @@ -24,8 +27,4 @@
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Primitives\src\Microsoft.Extensions.Primitives.csproj" />
</ItemGroup>

<!-- For IncrementalHash -->
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="$(SystemSecurityCryptographyAlgorithmsVersion)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Argument_InvalidOffLen" xml:space="preserve">
<value>Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.</value>
</data>
<data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
<value>Non-negative number required.</value>
</data>
<data name="Error_FileSystemWatcherRequiredWithoutPolling" xml:space="preserve">
<value>The fileSystemWatcher parameter must be non-null when pollForChanges is false.</value>
</data>
Expand All @@ -126,6 +132,9 @@
<data name="CannotModifyWhenFileWatcherInitialized" xml:space="preserve">
<value>Cannot modify {0} once file watcher has been initialized.</value>
</data>
<data name="Cryptography_HashAlgorithmNameNullOrEmpty" xml:space="preserve">
<value>The hash algorithm name cannot be null or empty.</value>
</data>
<data name="UnexpectedFileSystemInfo" xml:space="preserve">
<value>Unexpected type of FileSystemInfo</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ExternallyShipping>false</ExternallyShipping>
Expand All @@ -9,6 +10,7 @@
<EnableLibraryImportGenerator>true</EnableLibraryImportGenerator>
<DefineConstants Condition="'$(TargetOS)' == 'browser'">$(DefineConstants);TARGET_BROWSER</DefineConstants>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(CommonTestPath)System\Security\Cryptography\SignatureSupport.cs"
Link="CommonTest\System\Security\Cryptography\SignatureSupport.cs" />
Expand All @@ -24,6 +26,7 @@
Link="Common\Microsoft\Win32\SafeHandles\SafeLibraryHandle.cs" />
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs"
Link="Common\System\IO\TempFile.cs" />

<Compile Include="Metadata\BlobContentIdTests.cs" />
<Compile Include="Metadata\BlobTests.cs" />
<Compile Include="Metadata\BlobUtilitiesTests.cs" />
Expand Down Expand Up @@ -90,6 +93,7 @@
<Compile Include="Utilities\MemoryBlockTests.cs" />
<Compile Include="Utilities\OrderByTests.cs" />
</ItemGroup>

<ItemGroup>
<None Include="Resources\Namespace\NamespaceForwardedCS.cs" />
<None Include="Resources\Namespace\NamespaceTests.cs" />
Expand Down Expand Up @@ -135,16 +139,19 @@
<None Include="Resources\Misc\Signed.cs" />
<EmbeddedResource Include="Resources\Misc\Signed.exe" />
</ItemGroup>

<ItemGroup>
<!-- Some internal types are needed, so we reference the implementation assembly, rather than the reference assembly. -->
<ProjectReference Include="..\src\System.Reflection.Metadata.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>

<!-- For IncrementalHash -->
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="$(SystemSecurityCryptographyAlgorithmsVersion)" />
<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />
</ItemGroup>

<ItemGroup Condition="'$(TargetOS)' == 'browser'">
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,19 @@ public static byte[] CalculateRsaSignature(IEnumerable<Blob> content, byte[] pri

public static byte[] CalculateSha1(IEnumerable<Blob> content)
{
using (var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA1))
{
var stream = new MemoryStream();

foreach (var blob in content)
{
var segment = blob.GetBytes();
MemoryStream stream = new();

stream.Write(segment.Array, segment.Offset, segment.Count);
foreach (Blob blob in content)
{
var segment = blob.GetBytes();
stream.Write(segment.Array, segment.Offset, segment.Count);
}

hash.AppendData(segment.Array, segment.Offset, segment.Count);
}
stream.Position = 0;

return hash.GetHashAndReset();
using (SHA1 sha1 = SHA1.Create())
{
return sha1.ComputeHash(stream);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@
<data name="Argument_EncodeDestinationTooSmall" xml:space="preserve">
<value>The destination is too small to hold the encoded value.</value>
</data>
<data name="Argument_InvalidOffLen" xml:space="preserve">
<value>Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.</value>
</data>
<data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
<value>Non-negative number required.</value>
</data>
<data name="ContentWasDetached" xml:space="preserve">
<value>Content was not included in the message (detached message), provide a content to verify.</value>
</data>
Expand Down Expand Up @@ -156,6 +162,9 @@
<data name="CriticalHeadersMustBeArrayOfAtLeastOne" xml:space="preserve">
<value>Critical Headers must be a CBOR array of at least one element.</value>
</data>
<data name="Cryptography_HashAlgorithmNameNullOrEmpty" xml:space="preserve">
<value>The hash algorithm name cannot be null or empty.</value>
</data>
<data name="DecodeCoseSignatureMustBeArrayOfThree" xml:space="preserve">
<value>COSE Signature must be an array of three elements.</value>
</data>
Expand Down
Loading

0 comments on commit c8f43d5

Please sign in to comment.