Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEric Erhardt <eric.erhardt@microsoft.com>2022-07-06 03:46:09 +0300
committerGitHub <noreply@github.com>2022-07-06 03:46:09 +0300
commit4222e699371ed72ac1fe702e5cfb44a01f3847d8 (patch)
treef3a14cf07b502644b8559606a246f2b8018d2fb1 /src
parent0e5fcea02506b4f4526aa5e3aaec13d81a181b2d (diff)
Use crypto.subtle for AES on Browser WASM (#71501)
* Use crypto.subtle for AES on Browser WASM Implement the browser "native" portion for AES on Browser WASM. There are two issues to solve .NET's Aes API on crypto.subtle: 1. The .NET API supports streaming while crypto.subtle only supports "one shot" APIs. 2. The .NET API supports multiple padding modes while crypto.subtle only supports PKCS7. To solve these issues, we use the following approach: 1. We only invoke crypto.subtle with complete AES "blocks" of data. This allows us to make assumptions about the padding behavior. 2. To implement streaming, remember the last block of the previous cipher text to use as the IV for the next stream of data. 3. When encrypting, since we have a complete block of data and crypto.subtle uses PKCS7 padding, strip off the last block of cipher text which will always be a full block of padding. 4. When decrypting do the inverse of encrypting - append an encrypted block of padding to the cipher text so crypto.subtle will return the full message as plain text. Other changes: - Make a few refactoring / simplifications where necessary. - SubtleCrypto doesn't support 192 bit AES keys, so no longer support AES-192 on Browser. Contributes to #40074 * Use an empty array to create encrypted padding block.
Diffstat (limited to 'src')
-rw-r--r--src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.Sign.cs22
-rw-r--r--src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs (renamed from src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs)26
-rw-r--r--src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs3
-rw-r--r--src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs6
-rw-r--r--src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj10
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs3
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs22
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs10
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs12
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs169
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs2
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs10
-rw-r--r--src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs2
-rw-r--r--src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs15
-rw-r--r--src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js1
-rw-r--r--src/mono/wasm/runtime/crypto-worker.ts29
-rw-r--r--src/mono/wasm/runtime/es6/dotnet.es6.lib.js1
-rw-r--r--src/mono/wasm/runtime/exports.ts4
-rw-r--r--src/mono/wasm/runtime/workers/dotnet-crypto-worker.js78
-rw-r--r--src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c25
-rw-r--r--src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h11
21 files changed, 406 insertions, 55 deletions
diff --git a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.Sign.cs b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.Sign.cs
deleted file mode 100644
index 2100b3f0530..00000000000
--- a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.Sign.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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);
- }
-}
diff --git a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs
index d664276a278..f0330945eda 100644
--- a/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs
+++ b/src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs
@@ -18,8 +18,10 @@ internal static partial class Interop
Sha512,
};
+ internal static readonly bool CanUseSubtleCrypto = CanUseSubtleCryptoImpl() == 1;
+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl")]
- internal static partial int CanUseSubtleCryptoImpl();
+ private static partial int CanUseSubtleCryptoImpl();
[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_SimpleDigestHash")]
internal static unsafe partial int SimpleDigestHash(
@@ -28,5 +30,27 @@ internal static partial class Interop
int input_len,
byte* output_buffer,
int output_len);
+
+ [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);
+
+ [LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_EncryptDecrypt")]
+ internal static unsafe partial int EncryptDecrypt(
+ int encrypting,
+ byte* key_buffer,
+ int key_len,
+ byte* iv_buffer,
+ int iv_len,
+ byte* input_buffer,
+ int input_len,
+ byte* output_buffer,
+ int output_len);
}
}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs
index 27ee7aec3c8..8bf77c7e73b 100644
--- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs
+++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs
@@ -34,6 +34,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
}
[Fact]
+ [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")]
public static void RandomKeyRoundtrip_192()
{
using (Aes aes = AesFactory.Create())
@@ -485,6 +486,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
}
[Fact]
+ [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")]
public static void VerifyKnownTransform_CBC192_NoPadding()
{
TestAesTransformDirectKey(
@@ -525,6 +527,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
}
[Fact]
+ [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")]
public static void VerifyKnownTransform_CBC192_NoPadding_2()
{
TestAesTransformDirectKey(
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs
index f2a63a2cbb2..dc8940d6b38 100644
--- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs
+++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs
@@ -55,7 +55,10 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
Assert.Equal(128, keySizeLimits.MinSize);
Assert.Equal(256, keySizeLimits.MaxSize);
- Assert.Equal(64, keySizeLimits.SkipSize);
+
+ // Browser's SubtleCrypto doesn't support AES-192
+ int expectedKeySkipSize = PlatformDetection.IsBrowser ? 128 : 64;
+ Assert.Equal(expectedKeySkipSize, keySizeLimits.SkipSize);
}
}
@@ -214,6 +217,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests
}
[Fact]
+ [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")]
public static void VerifyKeyGeneration_192()
{
using (Aes aes = AesFactory.Create())
diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
index 7821a8e03a5..cd48ee4e222 100644
--- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
+++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
@@ -541,14 +541,13 @@
Link="Common\Interop\Browser\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.cs"
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="$(CommonPath)Interop\Browser\System.Security.Cryptography.Native.Browser\Interop.SubtleCrypto.cs"
+ Link="Common\Interop\Browser\System.Security.Cryptography.Native.Browser\Interop.SubtleCrypto.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesImplementation.Browser.cs" />
<Compile Include="System\Security\Cryptography\AesManagedTransform.Browser.cs" />
+ <Compile Include="System\Security\Cryptography\AesSubtleCryptoTransform.Browser.cs" />
<Compile Include="System\Security\Cryptography\AsnFormatter.Managed.cs" />
<Compile Include="System\Security\Cryptography\CapiHelper.Browser.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.NotSupported.cs" />
@@ -583,6 +582,9 @@
<Compile Include="System\Security\Cryptography\X509Certificates\StorePal.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X509Pal.NotSupported.cs" />
</ItemGroup>
+ <ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'Browser'">
+ <Compile Include="System\Security\Cryptography\AesImplementation.NonBrowser.cs" />
+ </ItemGroup>
<ItemGroup Condition="'$(NeedOpenSslInitializer)' == 'true'">
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
Link="Common\Interop\Unix\Interop.Libraries.cs" />
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs
index 57658ac9ad6..b28378d4362 100644
--- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs
@@ -11,7 +11,7 @@ namespace System.Security.Cryptography
protected Aes()
{
LegalBlockSizesValue = s_legalBlockSizes.CloneKeySizesArray();
- LegalKeySizesValue = s_legalKeySizes.CloneKeySizesArray();
+ LegalKeySizesValue = AesImplementation.s_legalKeySizes.CloneKeySizesArray();
BlockSizeValue = 128;
FeedbackSizeValue = 8;
@@ -31,6 +31,5 @@ namespace System.Security.Cryptography
}
private static readonly KeySizes[] s_legalBlockSizes = { new KeySizes(128, 128, 0) };
- private static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 64) };
}
}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs
index 5e3f9c4eb0e..3523b919f71 100644
--- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs
@@ -2,12 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
-using Internal.Cryptography;
namespace System.Security.Cryptography
{
internal sealed partial class AesImplementation
{
+ internal const int BlockSizeBytes = 16; // 128 bits
+
+ // SubtleCrypto doesn't support AES-192. http://crbug.com/533699
+ internal static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 128) };
+
private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
@@ -19,11 +23,17 @@ namespace System.Security.Cryptography
bool encrypting)
{
ValidateCipherMode(cipherMode);
+ if (iv is null)
+ throw new CryptographicException(SR.Cryptography_MissingIV);
- Debug.Assert(blockSize == AesManagedTransform.BlockSizeBytes);
+ Debug.Assert(blockSize == BlockSizeBytes);
Debug.Assert(paddingSize == blockSize);
- return UniversalCryptoTransform.Create(paddingMode, new AesManagedTransform(key, iv, encrypting), encrypting);
+ BasicSymmetricCipher cipher = Interop.BrowserCrypto.CanUseSubtleCrypto ?
+ new AesSubtleCryptoTransform(key, iv, encrypting) :
+ new AesManagedTransform(key, iv, encrypting);
+
+ return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
}
private static ILiteSymmetricCipher CreateLiteCipher(
@@ -37,10 +47,12 @@ namespace System.Security.Cryptography
{
ValidateCipherMode(cipherMode);
- Debug.Assert(blockSize == AesManagedTransform.BlockSizeBytes);
+ Debug.Assert(blockSize == BlockSizeBytes);
Debug.Assert(paddingSize == blockSize);
- return new AesManagedTransform(key, iv, encrypting);
+ return Interop.BrowserCrypto.CanUseSubtleCrypto ?
+ new AesSubtleCryptoTransform(key, iv, encrypting) :
+ new AesManagedTransform(key, iv, encrypting);
}
private static void ValidateCipherMode(CipherMode cipherMode)
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs
new file mode 100644
index 00000000000..78b74ac82ab
--- /dev/null
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Security.Cryptography
+{
+ internal sealed partial class AesImplementation
+ {
+ internal static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 64) };
+ }
+}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs
index 6354214dffb..0ae66acc18e 100644
--- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs
@@ -9,7 +9,7 @@ namespace System.Security.Cryptography
{
internal sealed class AesManagedTransform : BasicSymmetricCipher, ILiteSymmetricCipher
{
- public const int BlockSizeBytes = 16; // 128 bits
+ private const int BlockSizeBytes = AesImplementation.BlockSizeBytes;
private const int BlockSizeInts = BlockSizeBytes / 4;
private readonly bool _encrypting;
@@ -30,13 +30,7 @@ namespace System.Security.Cryptography
: base(iv: null, BlockSizeBytes, BlockSizeBytes)
{
Debug.Assert(BitConverter.IsLittleEndian, "The logic of casting Span<int> to Span<byte> below assumes little endian");
-
- if (iv.IsEmpty)
- throw new CryptographicException(SR.Cryptography_MissingIV);
-
- // we only support the standard AES block size
- if (iv.Length != BlockSizeBytes)
- throw new CryptographicException(SR.Cryptography_InvalidIVSize);
+ Debug.Assert(iv.Length == BlockSizeBytes);
_encrypting = encrypting;
_Nr = GetNumberOfRounds(key);
@@ -331,7 +325,7 @@ namespace System.Security.Cryptography
return (BlockSizeBytes > key.Length ? BlockSizeBytes : key.Length) switch
{
16 => 10, // 128 bits
- 24 => 12, // 192 bits
+ // 24 => 12, // 192 bits is not supported by SubtleCrypto, so the managed implementation doesn't support it either
32 => 14, // 256 bits
_ => throw new CryptographicException(SR.Cryptography_InvalidKeySize)
};
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs
new file mode 100644
index 00000000000..5e0cfc68641
--- /dev/null
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs
@@ -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.
+
+using System.Diagnostics;
+using Internal.Cryptography;
+
+namespace System.Security.Cryptography
+{
+ internal sealed class AesSubtleCryptoTransform : BasicSymmetricCipher, ILiteSymmetricCipher
+ {
+ private const int BlockSizeBytes = AesImplementation.BlockSizeBytes;
+
+ private readonly bool _encrypting;
+
+ private readonly byte[] _key;
+ private byte[]? _lastBlockBuffer;
+
+ public AesSubtleCryptoTransform(byte[] key,
+ byte[] iv,
+ bool encrypting)
+ : base(iv, BlockSizeBytes, BlockSizeBytes)
+ {
+ _encrypting = encrypting;
+
+ // iv is guaranteed to be cloned before this method, but not key
+ _key = key.CloneByteArray();
+ }
+
+ public AesSubtleCryptoTransform(ReadOnlySpan<byte> key,
+ ReadOnlySpan<byte> iv,
+ bool encrypting)
+ : base(iv.ToArray(), BlockSizeBytes, BlockSizeBytes)
+ {
+ _encrypting = encrypting;
+
+ _key = key.ToArray();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // We need to always zeroize the following fields because they contain sensitive data
+ CryptographicOperations.ZeroMemory(_key);
+ CryptographicOperations.ZeroMemory(_lastBlockBuffer);
+ }
+
+ base.Dispose(disposing);
+ }
+
+ public override int Transform(ReadOnlySpan<byte> input, Span<byte> output) =>
+ Transform(input, output, isFinal: false);
+
+ public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
+ {
+ int bytesWritten = Transform(input, output, isFinal: true);
+ Reset();
+ return bytesWritten;
+ }
+
+ private int Transform(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
+ {
+ Debug.Assert(output.Length >= input.Length);
+ Debug.Assert(input.Length % BlockSizeInBytes == 0);
+
+ if (input.IsEmpty)
+ {
+ return 0;
+ }
+
+ // Note: SubtleCrypto always uses PKCS7 padding.
+
+ // In order to implement streaming on top of SubtleCrypto's "one shot" API, we have to do the following:
+ // 1. Remember the last block of cipher text to pass as the "IV" of the next block.
+ // 2. When encrypting a complete block, PKCS7 padding will always add one block of '0x10' padding bytes. We
+ // need to strip this padding block off in between Transform calls. This is done by Interop.BrowserCrypto.EncryptDecrypt.
+ // 3. When decrypting, we need to do the inverse: append an encrypted block of '0x10' padding bytes, so
+ // SubtleCrypto will decrypt input as a complete message. This is done by Interop.BrowserCrypto.EncryptDecrypt.
+
+ return _encrypting ?
+ EncryptBlock(input, output, isFinal) :
+ DecryptBlock(input, output, isFinal);
+ }
+
+ private int EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
+ {
+ int bytesWritten = EncryptDecrypt(input, output);
+
+ if (!isFinal)
+ {
+ SaveLastBlock(output.Slice(0, bytesWritten));
+ }
+
+ return bytesWritten;
+ }
+
+ private int DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
+ {
+ Span<byte> lastInputBlockCopy = stackalloc byte[BlockSizeBytes];
+ if (!isFinal)
+ {
+ // Save the lastInputBlock in a temp buffer first, in case input and output are overlapped
+ // and decrypting to the output overwrites the input.
+ ReadOnlySpan<byte> lastInputBlock = input.Slice(input.Length - BlockSizeBytes);
+ lastInputBlock.CopyTo(lastInputBlockCopy);
+ }
+
+ int numBytesWritten = EncryptDecrypt(input, output);
+
+ if (!isFinal)
+ {
+ SaveLastBlock(lastInputBlockCopy);
+ }
+
+ return numBytesWritten;
+ }
+
+ private void SaveLastBlock(ReadOnlySpan<byte> buffer)
+ {
+ Debug.Assert(buffer.Length > 0 && buffer.Length % BlockSizeBytes == 0);
+
+ ReadOnlySpan<byte> lastBlock = buffer.Slice(buffer.Length - BlockSizeBytes);
+ if (_lastBlockBuffer is null)
+ {
+ _lastBlockBuffer = lastBlock.ToArray();
+ }
+ else
+ {
+ Debug.Assert(_lastBlockBuffer.Length == BlockSizeBytes);
+ lastBlock.CopyTo(_lastBlockBuffer);
+ }
+ }
+
+ private unsafe int EncryptDecrypt(ReadOnlySpan<byte> input, Span<byte> output)
+ {
+ byte[] iv = _lastBlockBuffer ?? IV!;
+
+ fixed (byte* pKey = _key)
+ fixed (byte* pIV = iv)
+ fixed (byte* pInput = input)
+ fixed (byte* pOutput = output)
+ {
+ int bytesWritten = Interop.BrowserCrypto.EncryptDecrypt(
+ _encrypting ? 1 : 0,
+ pKey, _key.Length,
+ pIV, iv.Length,
+ pInput, input.Length,
+ pOutput, output.Length);
+
+ if (bytesWritten < 0)
+ throw new Exception(SR.Unknown_Error);
+
+ return bytesWritten;
+ }
+ }
+
+ //
+ // resets the state of the transform
+ //
+
+ void ILiteSymmetricCipher.Reset(ReadOnlySpan<byte> iv) => throw new NotImplementedException(); // never invoked
+
+ private void Reset()
+ {
+ CryptographicOperations.ZeroMemory(_lastBlockBuffer);
+ _lastBlockBuffer = null;
+ }
+ }
+}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs
index 819ae0eaba1..2b040191232 100644
--- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs
@@ -19,7 +19,7 @@ namespace System.Security.Cryptography
public HMACNativeHashProvider(string hashAlgorithmId, ReadOnlySpan<byte> key)
{
- Debug.Assert(HashProviderDispenser.CanUseSubtleCryptoImpl);
+ Debug.Assert(Interop.BrowserCrypto.CanUseSubtleCrypto);
(_hashAlgorithm, _hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId);
_key = key.ToArray();
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs
index 3b4f42b786d..8c604bb32b1 100644
--- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs
@@ -5,8 +5,6 @@ namespace System.Security.Cryptography
{
internal static partial class HashProviderDispenser
{
- internal static readonly bool CanUseSubtleCryptoImpl = Interop.BrowserCrypto.CanUseSubtleCryptoImpl() == 1;
-
public static HashProvider CreateHashProvider(string hashAlgorithmId)
{
switch (hashAlgorithmId)
@@ -15,7 +13,7 @@ namespace System.Security.Cryptography
case HashAlgorithmNames.SHA256:
case HashAlgorithmNames.SHA384:
case HashAlgorithmNames.SHA512:
- return CanUseSubtleCryptoImpl
+ return Interop.BrowserCrypto.CanUseSubtleCrypto
? new SHANativeHashProvider(hashAlgorithmId)
: new SHAManagedHashProvider(hashAlgorithmId);
}
@@ -30,7 +28,7 @@ namespace System.Security.Cryptography
ReadOnlySpan<byte> source,
Span<byte> destination)
{
- if (CanUseSubtleCryptoImpl)
+ if (Interop.BrowserCrypto.CanUseSubtleCrypto)
{
return HMACNativeHashProvider.MacDataOneShot(hashAlgorithmId, key, source, destination);
}
@@ -44,7 +42,7 @@ namespace System.Security.Cryptography
public static int HashData(string hashAlgorithmId, ReadOnlySpan<byte> source, Span<byte> destination)
{
- if (CanUseSubtleCryptoImpl)
+ if (Interop.BrowserCrypto.CanUseSubtleCrypto)
{
return SHANativeHashProvider.HashOneShot(hashAlgorithmId, source, destination);
}
@@ -65,7 +63,7 @@ namespace System.Security.Cryptography
case HashAlgorithmNames.SHA256:
case HashAlgorithmNames.SHA384:
case HashAlgorithmNames.SHA512:
- return CanUseSubtleCryptoImpl
+ return Interop.BrowserCrypto.CanUseSubtleCrypto
? new HMACNativeHashProvider(hashAlgorithmId, key)
: new HMACManagedHashProvider(hashAlgorithmId, key);
}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs
index 205f46e1cd0..6c5f70489af 100644
--- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs
@@ -18,7 +18,7 @@ namespace System.Security.Cryptography
public SHANativeHashProvider(string hashAlgorithmId)
{
- Debug.Assert(HashProviderDispenser.CanUseSubtleCryptoImpl);
+ Debug.Assert(Interop.BrowserCrypto.CanUseSubtleCrypto);
(_impl, _hashSizeInBytes) = HashAlgorithmToPal(hashAlgorithmId);
}
diff --git a/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs b/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs
index 32c253a1234..5e8eafbaa38 100644
--- a/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs
+++ b/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs
@@ -43,5 +43,20 @@ namespace System.Security.Cryptography.Tests
Assert.Throws<PlatformNotSupportedException>(() => aes.CreateDecryptor(s_iv, s_iv));
}
}
+
+ // Browser's SubtleCrypto doesn't support AES-192
+ [Fact]
+ public static void Aes_InvalidKeySize_192_Browser()
+ {
+ byte[] key192 = new byte[192 / 8];
+ using (Aes aes = Aes.Create())
+ {
+ Assert.False(aes.ValidKeySize(192));
+ Assert.Throws<CryptographicException>(() => aes.Key = key192);
+ Assert.Throws<CryptographicException>(() => aes.KeySize = 192);
+ Assert.Throws<ArgumentException>(() => aes.CreateEncryptor(key192, s_iv));
+ Assert.Throws<ArgumentException>(() => aes.CreateDecryptor(key192, s_iv));
+ }
+ }
}
}
diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
index 35da8f4d649..cd78c6a2240 100644
--- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
+++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
@@ -90,6 +90,7 @@ const linked_functions = [
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_sign",
+ "dotnet_browser_encrypt_decrypt",
/// mono-threads-wasm.c
#if USE_PTHREADS
diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts
index 92171e76562..674f1700d22 100644
--- a/src/mono/wasm/runtime/crypto-worker.ts
+++ b/src/mono/wasm/runtime/crypto-worker.ts
@@ -54,6 +54,35 @@ export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, k
return 1;
}
+const AesBlockSizeBytes = 16; // 128 bits
+
+export function dotnet_browser_encrypt_decrypt(isEncrypting: boolean, key_buffer: number, key_len: number, iv_buffer: number, iv_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number {
+ mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");
+
+ if (input_len <= 0 || input_len % AesBlockSizeBytes !== 0) {
+ throw "ENCRYPT DECRYPT: data was not a full block: " + input_len;
+ }
+
+ const msg = {
+ func: "encrypt_decrypt",
+ isEncrypting: isEncrypting,
+ key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)),
+ iv: Array.from(Module.HEAPU8.subarray(iv_buffer, iv_buffer + iv_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 result = JSON.parse(response);
+
+ if (result.length > output_len) {
+ console.info("dotnet_browser_encrypt_decrypt: about to throw!");
+ throw "ENCRYPT DECRYPT: Encrypt/Decrypt length exceeds output length: " + result.length + " > " + output_len;
+ }
+
+ Module.HEAPU8.set(result, output_buffer);
+ return result.length;
+}
+
export function init_crypto(): void {
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined"
&& typeof SharedArrayBuffer !== "undefined"
diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
index c5851007de1..a559c8f42de 100644
--- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
+++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
@@ -127,6 +127,7 @@ const linked_functions = [
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_sign",
+ "dotnet_browser_encrypt_decrypt",
/// mono-threads-wasm.c
#if USE_PTHREADS
diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts
index d91a67adcfe..436e0ba347a 100644
--- a/src/mono/wasm/runtime/exports.ts
+++ b/src/mono/wasm/runtime/exports.ts
@@ -71,7 +71,8 @@ import { diagnostics } from "./diagnostics";
import {
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
- dotnet_browser_sign
+ dotnet_browser_sign,
+ dotnet_browser_encrypt_decrypt
} from "./crypto-worker";
import { mono_wasm_cancel_promise_ref } from "./cancelable-promise";
import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket";
@@ -406,6 +407,7 @@ export const __linker_exports: any = {
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
dotnet_browser_sign,
+ dotnet_browser_encrypt_decrypt,
// threading exports, if threading is enabled
...mono_wasm_threads_exports,
diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
index 5e27dd59b5f..f6d66c82da6 100644
--- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
+++ b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
@@ -187,7 +187,7 @@ async function sign(type, key, data) {
}
function get_hash_name(type) {
- switch(type) {
+ switch (type) {
case 0: return "SHA-1";
case 1: return "SHA-256";
case 2: return "SHA-384";
@@ -197,6 +197,75 @@ function get_hash_name(type) {
}
}
+const AesBlockSizeBytes = 16; // 128 bits
+
+async function encrypt_decrypt(isEncrypting, key, iv, data) {
+ const algorithmName = "AES-CBC";
+ const keyUsage = isEncrypting ? ["encrypt"] : ["encrypt", "decrypt"];
+ const cryptoKey = await importKey(key, algorithmName, keyUsage);
+ const algorithm = {
+ name: algorithmName,
+ iv: new Uint8Array(iv)
+ };
+
+ const result = await (isEncrypting ?
+ crypto.subtle.encrypt(
+ algorithm,
+ cryptoKey,
+ new Uint8Array(data)) :
+ decrypt(
+ algorithm,
+ cryptoKey,
+ data));
+
+ let resultByteArray = new Uint8Array(result);
+ if (isEncrypting) {
+ // trim off the last block, which is always a padding block.
+ resultByteArray = resultByteArray.slice(0, resultByteArray.length - AesBlockSizeBytes);
+ }
+ return Array.from(resultByteArray);
+}
+
+async function decrypt(algorithm, cryptoKey, data) {
+ // crypto.subtle AES-CBC will only allow a PaddingMode of PKCS7, but we need to use
+ // PaddingMode None. To simulate this, we only decrypt full blocks of data, with an extra full
+ // padding block of 0x10 (16) bytes appended to data. crypto.subtle will see that padding block and return
+ // the fully decrypted message. To create the encrypted padding block, we encrypt an empty array using the
+ // last block of the cipher text as the IV. This will create a full block of padding bytes.
+
+ const paddingBlockIV = new Uint8Array(data).slice(data.length - AesBlockSizeBytes);
+ const empty = new Uint8Array();
+ const encryptedPaddingBlockResult = await crypto.subtle.encrypt(
+ {
+ name: algorithm.name,
+ iv: paddingBlockIV
+ },
+ cryptoKey,
+ empty
+ );
+
+ const encryptedPaddingBlock = new Uint8Array(encryptedPaddingBlockResult);
+ for (var i = 0; i < encryptedPaddingBlock.length; i++) {
+ data.push(encryptedPaddingBlock[i]);
+ }
+
+ return await crypto.subtle.decrypt(
+ algorithm,
+ cryptoKey,
+ new Uint8Array(data));
+}
+
+function importKey(key, algorithmName, keyUsage) {
+ return crypto.subtle.importKey(
+ "raw",
+ new Uint8Array(key),
+ {
+ name: algorithmName
+ },
+ false /* extractable */,
+ keyUsage);
+}
+
// Operation to perform.
async function async_call(msg) {
const req = JSON.parse(msg);
@@ -208,7 +277,12 @@ async function async_call(msg) {
else if (req.func === "sign") {
const signResult = await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data));
return JSON.stringify(signResult);
- } else {
+ }
+ else if (req.func === "encrypt_decrypt") {
+ const signResult = await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data);
+ return JSON.stringify(signResult);
+ }
+ else {
throw "CRYPTO: Unknown request: " + req.func;
}
}
diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c
index 60b665dced1..0c78013ac34 100644
--- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c
+++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c
@@ -21,6 +21,17 @@ extern int32_t dotnet_browser_sign(
uint8_t* output_buffer,
int32_t output_len);
+extern int32_t dotnet_browser_encrypt_decrypt(
+ int32_t encrypting,
+ uint8_t* key_buffer,
+ int32_t key_len,
+ uint8_t* iv_buffer,
+ int32_t iv_len,
+ uint8_t* input_buffer,
+ int32_t input_len,
+ uint8_t* output_buffer,
+ int32_t output_len);
+
extern int32_t dotnet_browser_can_use_subtle_crypto_impl(void);
int32_t SystemCryptoNativeBrowser_SimpleDigestHash(
@@ -45,6 +56,20 @@ int32_t SystemCryptoNativeBrowser_Sign(
return dotnet_browser_sign(hashAlgorithm, key_buffer, key_len, input_buffer, input_len, output_buffer, output_len);
}
+int32_t SystemCryptoNativeBrowser_EncryptDecrypt(
+ int32_t encrypting,
+ uint8_t* key_buffer,
+ int32_t key_len,
+ uint8_t* iv_buffer,
+ int32_t iv_len,
+ uint8_t* input_buffer,
+ int32_t input_len,
+ uint8_t* output_buffer,
+ int32_t output_len)
+{
+ return dotnet_browser_encrypt_decrypt(encrypting, key_buffer, key_len, iv_buffer, iv_len, input_buffer, input_len, output_buffer, output_len);
+}
+
int32_t SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl(void)
{
return dotnet_browser_can_use_subtle_crypto_impl();
diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h
index c0b598edb0d..2166a4a2177 100644
--- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h
+++ b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h
@@ -31,4 +31,15 @@ PALEXPORT int32_t SystemCryptoNativeBrowser_Sign(
uint8_t* output_buffer,
int32_t output_len);
+PALEXPORT int32_t SystemCryptoNativeBrowser_EncryptDecrypt(
+ int32_t encrypting,
+ uint8_t* key_buffer,
+ int32_t key_len,
+ uint8_t* iv_buffer,
+ int32_t iv_len,
+ uint8_t* input_buffer,
+ int32_t input_len,
+ uint8_t* output_buffer,
+ int32_t output_len);
+
PALEXPORT int32_t SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl(void);