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 2100b3f0530ce..0000000000000 --- 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 similarity index 52% rename from src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SimpleDigestHash.cs rename to src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs index d664276a2788f..f0330945edaf5 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 enum SimpleDigest 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 unsafe partial int SimpleDigestHash( 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 27ee7aec3c8fe..8bf77c7e73b3d 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 @@ public static void RandomKeyRoundtrip_128() } [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 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_3() } [Fact] + [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKnownTransform_CBC192_NoPadding() { TestAesTransformDirectKey( @@ -525,6 +527,7 @@ public static void VerifyKnownTransform_CFB8_192_NoPadding() } [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 f2a63a2cbb2d3..dc8940d6b38d9 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 @@ public static void LegalKeySizes() 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 @@ public static void VerifyKeyGeneration_128() } [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 7821a8e03a536..cd48ee4e2228e 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" /> - - + + @@ -583,6 +582,9 @@ + + + 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 57658ac9ad65b..b28378d436244 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 @@ public abstract class Aes : SymmetricAlgorithm protected Aes() { LegalBlockSizesValue = s_legalBlockSizes.CloneKeySizesArray(); - LegalKeySizesValue = s_legalKeySizes.CloneKeySizesArray(); + LegalKeySizesValue = AesImplementation.s_legalKeySizes.CloneKeySizesArray(); BlockSizeValue = 128; FeedbackSizeValue = 8; @@ -31,6 +31,5 @@ protected Aes() } 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 5e3f9c4eb0e9d..3523b919f71e2 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 @@ private static UniversalCryptoTransform CreateTransformCore( 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 @@ private static ILiteSymmetricCipher CreateLiteCipher( { 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 0000000000000..78b74ac82abe7 --- /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 6354214dffb4b..0ae66acc18e57 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 @@ public AesManagedTransform(ReadOnlySpan key, : base(iv: null, BlockSizeBytes, BlockSizeBytes) { Debug.Assert(BitConverter.IsLittleEndian, "The logic of casting Span to Span 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 @@ private static int GetNumberOfRounds(ReadOnlySpan key) 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 0000000000000..5e0cfc68641de --- /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 key, + ReadOnlySpan 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 input, Span output) => + Transform(input, output, isFinal: false); + + public override int TransformFinal(ReadOnlySpan input, Span output) + { + int bytesWritten = Transform(input, output, isFinal: true); + Reset(); + return bytesWritten; + } + + private int Transform(ReadOnlySpan input, Span 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 input, Span output, bool isFinal) + { + int bytesWritten = EncryptDecrypt(input, output); + + if (!isFinal) + { + SaveLastBlock(output.Slice(0, bytesWritten)); + } + + return bytesWritten; + } + + private int DecryptBlock(ReadOnlySpan input, Span output, bool isFinal) + { + Span 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 lastInputBlock = input.Slice(input.Length - BlockSizeBytes); + lastInputBlock.CopyTo(lastInputBlockCopy); + } + + int numBytesWritten = EncryptDecrypt(input, output); + + if (!isFinal) + { + SaveLastBlock(lastInputBlockCopy); + } + + return numBytesWritten; + } + + private void SaveLastBlock(ReadOnlySpan buffer) + { + Debug.Assert(buffer.Length > 0 && buffer.Length % BlockSizeBytes == 0); + + ReadOnlySpan 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 input, Span 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 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 819ae0eaba122..2b04019123240 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 @@ internal sealed class HMACNativeHashProvider : HashProvider public HMACNativeHashProvider(string hashAlgorithmId, ReadOnlySpan 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 3b4f42b786d97..8c604bb32b122 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 @@ public static HashProvider CreateHashProvider(string hashAlgorithmId) 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 @@ public static unsafe int MacData( ReadOnlySpan source, Span destination) { - if (CanUseSubtleCryptoImpl) + if (Interop.BrowserCrypto.CanUseSubtleCrypto) { return HMACNativeHashProvider.MacDataOneShot(hashAlgorithmId, key, source, destination); } @@ -44,7 +42,7 @@ public static unsafe int MacData( public static int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { - if (CanUseSubtleCryptoImpl) + if (Interop.BrowserCrypto.CanUseSubtleCrypto) { return SHANativeHashProvider.HashOneShot(hashAlgorithmId, source, destination); } @@ -65,7 +63,7 @@ public static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, Read 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 205f46e1cd0ec..6c5f70489affa 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 @@ internal sealed class SHANativeHashProvider : HashProvider 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 32c253a1234d3..5e8eafbaa381a 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 @@ public static void AesThrows_PlatformNotSupported_CipherMode_Browser() Assert.Throws(() => 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(() => aes.Key = key192); + Assert.Throws(() => aes.KeySize = 192); + Assert.Throws(() => aes.CreateEncryptor(key192, s_iv)); + Assert.Throws(() => 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 35da8f4d64983..cd78c6a22401f 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 92171e765620b..674f1700d2272 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 c5851007de15f..a559c8f42de2a 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 d91a67adcfe35..436e0ba347aa9 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 5e27dd59b5f3f..f6d66c82da6d5 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 60b665dced17a..0c78013ac3433 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 c0b598edb0d9e..2166a4a217777 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);