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
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class BrowserCrypto
{
[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_Sign")]
internal static unsafe partial int Sign(
SimpleDigest hashAlgorithm,
byte* key_buffer,
int key_len,
byte* input_buffer,
int input_len,
byte* output_buffer,
int output_len);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ internal enum SimpleDigest
Sha512,
};

[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSimpleDigestHash")]
internal static partial int CanUseSimpleDigestHash();
[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl")]
internal static partial int CanUseSubtleCryptoImpl();

[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_SimpleDigestHash")]
internal static unsafe partial int SimpleDigestHash(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,8 @@
Link="Common\System\Sha1ForNonSecretPurposes.cs" />
<Compile Include="$(CommonPath)Interop\Browser\System.Security.Cryptography.Native.Browser\Interop.SimpleDigestHash.cs"
Link="Common\Interop\Browser\System.Security.Cryptography.Native.Browser\Interop.SimpleDigestHash.cs" />
<Compile Include="$(CommonPath)Interop\Browser\System.Security.Cryptography.Native.Browser\Interop.Sign.cs"
Link="Common\Interop\Browser\System.Security.Cryptography.Native.Browser\Interop.Sign.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesImplementation.NotSupported.cs" />
Expand All @@ -559,6 +561,7 @@
<Compile Include="System\Security\Cryptography\ECDsa.Create.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\HashProviderDispenser.Browser.cs" />
<Compile Include="System\Security\Cryptography\HMACHashProvider.Browser.Managed.cs" />
<Compile Include="System\Security\Cryptography\HMACHashProvider.Browser.Native.cs" />
<Compile Include="System\Security\Cryptography\LiteHash.Browser.cs" />
<Compile Include="System\Security\Cryptography\OidLookup.NoFallback.cs" />
<Compile Include="System\Security\Cryptography\OpenSsl.NotSupported.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Diagnostics;
using System.Security.Cryptography;

using SimpleDigest = Interop.BrowserCrypto.SimpleDigest;

namespace System.Security.Cryptography
{
internal sealed class HMACNativeHashProvider : HashProvider
{
private readonly int _hashSizeInBytes;
private readonly SimpleDigest _hashAlgorithm;
private readonly byte[] _key;
private MemoryStream? _buffer;

public HMACNativeHashProvider(string hashAlgorithmId, ReadOnlySpan<byte> key)
{
Debug.Assert(HashProviderDispenser.CanUseSubtleCryptoImpl);

(_hashAlgorithm, _hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId);
_key = key.ToArray();
}

public override void AppendHashData(ReadOnlySpan<byte> data)
{
_buffer ??= new MemoryStream(1000);
_buffer.Write(data);
Copy link
Member

Choose a reason for hiding this comment

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

Is this buffering all of the data that's used to compute the hash?

Copy link
Member

@vcsjones vcsjones Jun 14, 2022

Choose a reason for hiding this comment

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

Yes. There is no other way with SubtleCrypto, currently. The browser-provided implementations are one-shots. You need to give it all of the data up-front. See w3c/webcrypto#73

Copy link
Member

Choose a reason for hiding this comment

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

This seems like it could be a real pit of failure, especially if we may sometimes use SubtleCrypto and sometimes use the managed implementation based on configuration.

Copy link
Member Author

Choose a reason for hiding this comment

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

a real pit of failure

A performance pit of feature because of memory usage? Or something else?

and sometimes use the managed implementation based on configuration.

From my understanding, it is based on the browser's capabilities and even the settings/headers of the page it is hosted in. See https://developer.chrome.com/blog/enabling-shared-array-buffer/ for when we can use SharedArrayBuffer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, note that this is how the SHA algos are implemented as well in #65966.

Copy link
Member

Choose a reason for hiding this comment

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

A performance pit of feature because of memory usage? Or something else?

Memory, and then any knock-on effects from that, e.g. exceptions that result from the memory pressure (do the wasm environments enforce any kind of limits on working set size)? I'm just imagining someone downloading a gig of data to hash, expecting it to just stream through, but instead having the whole thing be buffered.

Copy link
Member

Choose a reason for hiding this comment

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

do the wasm environments enforce any kind of limits on working set size)

WASM is 32-bit, currently, so you have 4 GB of address space to work with, at most. In practice, v8 (Chromium)'s limit is more like 2 GB: https://v8.dev/blog/4gb-wasm-memory. The post is a few years old but I am not aware of that needle moving since then.

Copy link
Member

Choose a reason for hiding this comment

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

Reality is even more complicated WebAssembly/design#1397 and because of that it can definitely make sense to do the buffering on the js side rather than in managed but I expect most of the uses on this would look more like https://en.wikipedia.org/wiki/JSON_Web_Token than a large binary

}

public override int FinalizeHashAndReset(Span<byte> destination)
{
int written = GetCurrentHash(destination);
_buffer = null;

return written;
}

public override int GetCurrentHash(Span<byte> destination)
{
Debug.Assert(destination.Length >= _hashSizeInBytes);

byte[] srcArray = Array.Empty<byte>();
int srcLength = 0;
if (_buffer != null)
{
srcArray = _buffer.GetBuffer();
srcLength = (int)_buffer.Length;
}

unsafe
{
fixed (byte* key = _key)
fixed (byte* src = srcArray)
fixed (byte* dest = destination)
{
int res = Interop.BrowserCrypto.Sign(_hashAlgorithm, key, _key.Length, src, srcLength, dest, destination.Length);
Debug.Assert(res != 0);
}
}

return _hashSizeInBytes;
}

public static unsafe int MacDataOneShot(string hashAlgorithmId, ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> destination)
{
(SimpleDigest hashName, int hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId);
Debug.Assert(destination.Length >= hashSizeInBytes);

fixed (byte* k = key)
fixed (byte* src = data)
fixed (byte* dest = destination)
{
int res = Interop.BrowserCrypto.Sign(hashName, k, key.Length, src, data.Length, dest, destination.Length);
Debug.Assert(res != 0);
}

return hashSizeInBytes;
}

public override int HashSizeInBytes => _hashSizeInBytes;

public override void Dispose(bool disposing)
{
if (disposing)
{
CryptographicOperations.ZeroMemory(_key);
}
}

public override void Reset()
{
_buffer = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Internal.Cryptography;

namespace System.Security.Cryptography
{
internal static partial class HashProviderDispenser
{
internal static readonly bool CanUseSubtleCryptoImpl = Interop.BrowserCrypto.CanUseSimpleDigestHash() == 1;
internal static readonly bool CanUseSubtleCryptoImpl = Interop.BrowserCrypto.CanUseSubtleCryptoImpl() == 1;

public static HashProvider CreateHashProvider(string hashAlgorithmId)
{
Expand All @@ -32,9 +30,16 @@ public static unsafe int MacData(
ReadOnlySpan<byte> source,
Span<byte> destination)
{
HashProvider provider = CreateMacProvider(hashAlgorithmId, key);
provider.AppendHashData(source);
return provider.FinalizeHashAndReset(destination);
if (CanUseSubtleCryptoImpl)
{
return HMACNativeHashProvider.MacDataOneShot(hashAlgorithmId, key, source, destination);
}
else
{
using HashProvider provider = CreateMacProvider(hashAlgorithmId, key);
provider.AppendHashData(source);
return provider.FinalizeHashAndReset(destination);
}
}

public static int HashData(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination)
Expand All @@ -60,7 +65,9 @@ public static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, Read
case HashAlgorithmNames.SHA256:
case HashAlgorithmNames.SHA384:
case HashAlgorithmNames.SHA512:
return new HMACManagedHashProvider(hashAlgorithmId, key);
return CanUseSubtleCryptoImpl
? new HMACNativeHashProvider(hashAlgorithmId, key)
: new HMACManagedHashProvider(hashAlgorithmId, key);
}
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

using SimpleDigest = Interop.BrowserCrypto.SimpleDigest;

namespace Internal.Cryptography
namespace System.Security.Cryptography
{
internal sealed class SHANativeHashProvider : HashProvider
{
Expand Down Expand Up @@ -87,7 +87,7 @@ public override void Reset()
_buffer = null;
}

private static (SimpleDigest, int) HashAlgorithmToPal(string hashAlgorithmId)
internal static (SimpleDigest HashName, int HashSizeInBytes) HashAlgorithmToPal(string hashAlgorithmId)
{
return hashAlgorithmId switch
{
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/System.Security.Cryptography/tests/HmacMD5Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ public void HMacMD5_ThrowsArgumentNullForNullConstructorKey()
AssertExtensions.Throws<ArgumentNullException>("key", () => new HMACMD5(null));
}

[Fact]
public void HMacMD5_EmptyKey()
{
VerifyRepeating(
input: "Crypto is fun!",
1,
hexKey: "",
output: "7554A8C4641CBA36BE2AC20CACEA1136");
}

[Fact]
public void HmacMD5_Stream_MultipleOf4096()
{
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/System.Security.Cryptography/tests/HmacSha1Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ public void HmacSha1_ThrowsArgumentNullForNullConstructorKey()
AssertExtensions.Throws<ArgumentNullException>("key", () => new HMACSHA1(null));
}

[Fact]
public void HmacSha1_EmptyKey()
{
VerifyRepeating(
input: "Crypto is fun!",
1,
hexKey: "",
output: "C979AD8DE8CC546CF82D948226FDD8024599F6CE");
}

[Fact]
public void HmacSha1_Rfc2202_1()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ public void HmacSha256_ThrowsArgumentNullForNullConstructorKey()
AssertExtensions.Throws<ArgumentNullException>("key", () => new HMACSHA256(null));
}

[Fact]
public void HmacSha256_EmptyKey()
{
VerifyRepeating(
input: "Crypto is fun!",
1,
hexKey: "",
output: "DE26DD5A23A91021F61EACF8A8DD324AB5637977486A10D701C4DFA4AE33CB4F");
}

[Fact]
public void HmacSha256_Stream_MultipleOf4096()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ public void HmacSha384_ThrowsArgumentNullForNullConstructorKey()
AssertExtensions.Throws<ArgumentNullException>("key", () => new HMACSHA384(null));
}

[Fact]
public void HmacSha384_EmptyKey()
{
VerifyRepeating(
input: "Crypto is fun!",
1,
hexKey: "",
output: "CFEB81812C8DB4EDB385FCC7CB81E4D715685741AAB1E470FB0B395A414F89867E510E4A2BA2F1F11D7005849FA0DF11");
}

[Fact]
public void HmacSha384_Stream_MultipleOf4096()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ public void HmacSha512_ThrowsArgumentNullForNullConstructorKey()
AssertExtensions.Throws<ArgumentNullException>("key", () => new HMACSHA512(null));
}

[Fact]
public void HmacSha512_EmptyKey()
{
VerifyRepeating(
input: "Crypto is fun!",
1,
hexKey: "",
output: "0C75CCE182743282AAB081BA12AA6C9DEA44852E567063B4EEBD7B33F940B6C8BC16958F9A23401E6FAA00483962A2A8FC7DE9D8B7A14EDD55B49419A211BC37");
}

[Fact]
public void HmacSha512_Stream_MultipleOf4096()
{
Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ const linked_functions = [
"mono_wasm_get_icudt_name",

// pal_crypto_webworker.c
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_can_use_simple_digest_hash",
"dotnet_browser_sign",
];

// -- this javascript file is evaluated by emcc during compilation! --
Expand Down
23 changes: 22 additions & 1 deletion src/mono/wasm/runtime/crypto-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ let mono_wasm_crypto: {
worker: Worker
} | null = null;

export function dotnet_browser_can_use_simple_digest_hash(): number {
export function dotnet_browser_can_use_subtle_crypto_impl(): number {
return mono_wasm_crypto === null ? 0 : 1;
}

Expand All @@ -33,6 +33,27 @@ export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: num
return 1;
}

export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, key_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number {
mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");

const msg = {
func: "sign",
type: hashAlgorithm,
key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)),
data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len))
};

const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg));
const signResult = JSON.parse(response);
if (signResult.length > output_len) {
console.info("dotnet_browser_sign: about to throw!");
throw "SIGN HASH: Sign length exceeds output length: " + signResult.length + " > " + output_len;
}

Module.HEAPU8.set(signResult, output_buffer);
return 1;
}

export function init_crypto(): void {
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined"
&& typeof SharedArrayBuffer !== "undefined"
Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/runtime/es6/dotnet.es6.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ const linked_functions = [
"mono_wasm_get_icudt_name",

// pal_crypto_webworker.c
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_can_use_simple_digest_hash",
"dotnet_browser_sign",
];

// -- this javascript file is evaluated by emcc during compilation! --
Expand Down
9 changes: 7 additions & 2 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ import { fetch_like, readAsync_like } from "./polyfills";
import { EmscriptenModule } from "./types/emscripten";
import { mono_run_main, mono_run_main_and_exit } from "./run";
import { diagnostics } from "./diagnostics";
import { dotnet_browser_can_use_simple_digest_hash, dotnet_browser_simple_digest_hash } from "./crypto-worker";
import {
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
dotnet_browser_sign
} from "./crypto-worker";

const MONO = {
// current "public" MONO API
Expand Down Expand Up @@ -370,8 +374,9 @@ export const __linker_exports: any = {
mono_wasm_get_icudt_name,

// pal_crypto_webworker.c
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
dotnet_browser_can_use_simple_digest_hash,
dotnet_browser_sign
};

const INTERNAL: any = {
Expand Down
Loading