Skip to content

Commit 8f75cc9

Browse files
authored
Enable Rfc2898DeriveBytes on Browser WASM (#71768)
* Enable Rfc2898DeriveBytes on Browser WASM Marks the APIs as supported on Browser, and enables Rfc2898 tests on Browser WASM. Use SubtleCrypto deriveBits API to implement one shot Pbkdf2. * Mark HKDF as supported on Browser and enable tests Contributes to #40074
1 parent 4a38ac3 commit 8f75cc9

File tree

24 files changed

+250
-119
lines changed

24 files changed

+250
-119
lines changed

src/libraries/Common/src/Interop/Browser/System.Security.Cryptography.Native.Browser/Interop.SubtleCrypto.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,16 @@ internal static unsafe partial int EncryptDecrypt(
5252
int input_len,
5353
byte* output_buffer,
5454
int output_len);
55+
56+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_DeriveBits")]
57+
internal static unsafe partial int DeriveBits(
58+
byte* password_buffer,
59+
int password_len,
60+
byte* salt_buffer,
61+
int salt_len,
62+
int iterations,
63+
SimpleDigest hashAlgorithm,
64+
byte* output_buffer,
65+
int output_len);
5566
}
5667
}

src/libraries/Common/src/System/Security/Cryptography/HashOneShotHelpers.cs

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -98,28 +98,25 @@ internal static int MacData(
9898
ReadOnlySpan<byte> source,
9999
Span<byte> destination)
100100
{
101-
if (Helpers.HasHMAC)
102-
{
103-
if (hashAlgorithm == HashAlgorithmName.SHA256)
104-
{
105-
return HMACSHA256.HashData(key, source, destination);
106-
}
107-
else if (hashAlgorithm == HashAlgorithmName.SHA1)
108-
{
109-
return HMACSHA1.HashData(key, source, destination);
110-
}
111-
else if (hashAlgorithm == HashAlgorithmName.SHA512)
112-
{
113-
return HMACSHA512.HashData(key, source, destination);
114-
}
115-
else if (hashAlgorithm == HashAlgorithmName.SHA384)
116-
{
117-
return HMACSHA384.HashData(key, source, destination);
118-
}
119-
else if (hashAlgorithm == HashAlgorithmName.MD5)
120-
{
121-
return HMACMD5.HashData(key, source, destination);
122-
}
101+
if (hashAlgorithm == HashAlgorithmName.SHA256)
102+
{
103+
return HMACSHA256.HashData(key, source, destination);
104+
}
105+
else if (hashAlgorithm == HashAlgorithmName.SHA1)
106+
{
107+
return HMACSHA1.HashData(key, source, destination);
108+
}
109+
else if (hashAlgorithm == HashAlgorithmName.SHA512)
110+
{
111+
return HMACSHA512.HashData(key, source, destination);
112+
}
113+
else if (hashAlgorithm == HashAlgorithmName.SHA384)
114+
{
115+
return HMACSHA384.HashData(key, source, destination);
116+
}
117+
else if (Helpers.HasMD5 && hashAlgorithm == HashAlgorithmName.MD5)
118+
{
119+
return HMACMD5.HashData(key, source, destination);
123120
}
124121

125122
throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name));

src/libraries/Common/src/System/Security/Cryptography/Helpers.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,7 @@ namespace Internal.Cryptography
1111
internal static partial class Helpers
1212
{
1313
[UnsupportedOSPlatformGuard("browser")]
14-
internal static bool HasSymmetricEncryption { get; } =
15-
#if NETCOREAPP
16-
!OperatingSystem.IsBrowser();
17-
#else
18-
true;
19-
#endif
20-
21-
[UnsupportedOSPlatformGuard("browser")]
22-
internal static bool HasHMAC { get; } =
14+
internal static bool HasNonAesSymmetricEncryption =>
2315
#if NETCOREAPP
2416
!OperatingSystem.IsBrowser();
2517
#else
@@ -37,7 +29,7 @@ internal static partial class Helpers
3729
#if NETCOREAPP
3830
[UnsupportedOSPlatformGuard("android")]
3931
[UnsupportedOSPlatformGuard("browser")]
40-
public static bool IsRC2Supported => !OperatingSystem.IsAndroid();
32+
public static bool IsRC2Supported => !OperatingSystem.IsAndroid() && !OperatingSystem.IsBrowser();
4133
#else
4234
public static bool IsRC2Supported => true;
4335
#endif

src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,29 @@ internal static unsafe int Decrypt(
7474
{
7575
Debug.Assert(destination.Length >= encryptedData.Length);
7676

77-
if (!Helpers.HasSymmetricEncryption)
77+
// Don't check that algorithmIdentifier.Parameters is set here.
78+
// Maybe some future PBES3 will have one with a default.
79+
80+
if (algorithmIdentifier.Algorithm == Oids.PasswordBasedEncryptionScheme2)
81+
{
82+
return Pbes2Decrypt(
83+
algorithmIdentifier.Parameters,
84+
password,
85+
passwordBytes,
86+
encryptedData,
87+
destination);
88+
}
89+
90+
if (!Helpers.HasNonAesSymmetricEncryption)
7891
{
7992
throw new CryptographicException(
8093
SR.Format(
8194
SR.Cryptography_UnknownAlgorithmIdentifier,
8295
algorithmIdentifier.Algorithm));
8396
}
8497

85-
// Don't check that algorithmIdentifier.Parameters is set here.
86-
// Maybe some future PBES3 will have one with a default.
87-
8898
HashAlgorithmName digestAlgorithmName;
89-
SymmetricAlgorithm? cipher = null;
99+
SymmetricAlgorithm cipher;
90100

91101
bool pkcs12 = false;
92102

@@ -131,13 +141,6 @@ internal static unsafe int Decrypt(
131141
cipher.KeySize = 40;
132142
pkcs12 = true;
133143
break;
134-
case Oids.PasswordBasedEncryptionScheme2:
135-
return Pbes2Decrypt(
136-
algorithmIdentifier.Parameters,
137-
password,
138-
passwordBytes,
139-
encryptedData,
140-
destination);
141144
default:
142145
throw new CryptographicException(
143146
SR.Format(
@@ -146,7 +149,6 @@ internal static unsafe int Decrypt(
146149
}
147150

148151
Debug.Assert(digestAlgorithmName.Name != null);
149-
Debug.Assert(cipher != null);
150152

151153
using (cipher)
152154
{
@@ -237,14 +239,6 @@ internal static void InitiateEncryption(
237239
{
238240
Debug.Assert(pbeParameters != null);
239241

240-
if (!Helpers.HasSymmetricEncryption)
241-
{
242-
throw new CryptographicException(
243-
SR.Format(
244-
SR.Cryptography_UnknownAlgorithmIdentifier,
245-
pbeParameters.EncryptionAlgorithm));
246-
}
247-
248242
isPkcs12 = false;
249243

250244
switch (pbeParameters.EncryptionAlgorithm)
@@ -264,7 +258,7 @@ internal static void InitiateEncryption(
264258
cipher.KeySize = 256;
265259
encryptionAlgorithmOid = Oids.Aes256Cbc;
266260
break;
267-
case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12:
261+
case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12 when Helpers.HasNonAesSymmetricEncryption:
268262
cipher = TripleDES.Create();
269263
cipher.KeySize = 192;
270264
encryptionAlgorithmOid = Oids.Pkcs12PbeWithShaAnd3Key3Des;
@@ -393,12 +387,6 @@ internal static unsafe int Encrypt(
393387
Debug.Assert(pwdTmpBytes!.Length == 0);
394388
}
395389

396-
if (!Helpers.HasHMAC)
397-
{
398-
throw new CryptographicException(
399-
SR.Format(SR.Cryptography_AlgorithmNotSupported, "HMAC" + prf.Name));
400-
}
401-
402390
using (var pbkdf2 = new Rfc2898DeriveBytes(pwdTmpBytes, salt.ToArray(), iterationCount, prf))
403391
{
404392
derivedKey = pbkdf2.GetBytes(keySizeBytes);
@@ -540,8 +528,6 @@ private static unsafe int Pbes2Decrypt(
540528
Rfc2898DeriveBytes pbkdf2 =
541529
OpenPbkdf2(password, pbes2Params.KeyDerivationFunc.Parameters, out int? requestedKeyLength);
542530

543-
Debug.Assert(Helpers.HasHMAC);
544-
545531
using (pbkdf2)
546532
{
547533
// The biggest block size (for IV) we support is AES (128-bit / 16 byte)
@@ -580,12 +566,6 @@ private static SymmetricAlgorithm OpenCipher(
580566
{
581567
string? algId = encryptionScheme.Algorithm;
582568

583-
if (!Helpers.HasSymmetricEncryption)
584-
{
585-
throw new CryptographicException(
586-
SR.Format(SR.Cryptography_AlgorithmNotSupported, algId));
587-
}
588-
589569
if (algId == Oids.Aes128Cbc ||
590570
algId == Oids.Aes192Cbc ||
591571
algId == Oids.Aes256Cbc)
@@ -624,6 +604,12 @@ private static SymmetricAlgorithm OpenCipher(
624604
return aes;
625605
}
626606

607+
if (!Helpers.HasNonAesSymmetricEncryption)
608+
{
609+
throw new CryptographicException(
610+
SR.Format(SR.Cryptography_AlgorithmNotSupported, algId));
611+
}
612+
627613
if (algId == Oids.TripleDesCbc)
628614
{
629615
// https://tools.ietf.org/html/rfc8018#appendix-B.2.2
@@ -777,12 +763,6 @@ private static unsafe Rfc2898DeriveBytes OpenPbkdf2(
777763
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
778764
}
779765

780-
if (!Helpers.HasHMAC)
781-
{
782-
throw new CryptographicException(
783-
SR.Format(SR.Cryptography_AlgorithmNotSupported, "HMAC" + prf.Name));
784-
}
785-
786766
int iterationCount = NormalizeIterationCount(pbkdf2Params.IterationCount);
787767
ReadOnlyMemory<byte> saltMemory = pbkdf2Params.Salt.Specified.Value;
788768

src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,6 @@ protected virtual void HashCore(System.ReadOnlySpan<byte> source) { }
13041304
public override string ToString() { throw null; }
13051305
public static bool TryFromOid(string oidValue, out System.Security.Cryptography.HashAlgorithmName value) { throw null; }
13061306
}
1307-
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
13081307
public static partial class HKDF
13091308
{
13101309
public static byte[] DeriveKey(System.Security.Cryptography.HashAlgorithmName hashAlgorithmName, byte[] ikm, int outputLength, byte[]? salt = null, byte[]? info = null) { throw null; }
@@ -1720,7 +1719,6 @@ public RC2CryptoServiceProvider() { }
17201719
public override void GenerateIV() { }
17211720
public override void GenerateKey() { }
17221721
}
1723-
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
17241722
public partial class Rfc2898DeriveBytes : System.Security.Cryptography.DeriveBytes
17251723
{
17261724
[System.ObsoleteAttribute("The default hash algorithm and iteration counts in Rfc2898DeriveBytes constructors are outdated and insecure. Use a constructor that accepts the hash algorithm and the number of iterations.", DiagnosticId="SYSLIB0041", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]

src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@
566566
<Compile Include="System\Security\Cryptography\OidLookup.NoFallback.cs" />
567567
<Compile Include="System\Security\Cryptography\OpenSsl.NotSupported.cs" />
568568
<Compile Include="System\Security\Cryptography\PasswordDeriveBytes.NotSupported.cs" />
569-
<Compile Include="System\Security\Cryptography\Pbkdf2Implementation.Managed.cs" />
569+
<Compile Include="System\Security\Cryptography\Pbkdf2Implementation.Browser.cs" />
570570
<Compile Include="System\Security\Cryptography\RandomNumberGeneratorImplementation.Browser.cs" />
571571
<Compile Include="System\Security\Cryptography\RC2CryptoServiceProvider.NotSupported.cs" />
572572
<Compile Include="System\Security\Cryptography\RC2Implementation.NotSupported.cs" />

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ private unsafe int EncryptDecrypt(ReadOnlySpan<byte> input, Span<byte> output)
148148
pOutput, output.Length);
149149

150150
if (bytesWritten < 0)
151+
{
151152
throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, bytesWritten));
153+
}
152154

153155
return bytesWritten;
154156
}

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Runtime.Versioning;
6+
using Internal.Cryptography;
67

78
namespace System.Security.Cryptography
89
{
@@ -14,7 +15,6 @@ namespace System.Security.Cryptography
1415
/// phase to be skipped, and the master key to be used directly as the pseudorandom key.
1516
/// See <a href="https://tools.ietf.org/html/rfc5869">RFC5869</a> for more information.
1617
/// </remarks>
17-
[UnsupportedOSPlatform("browser")]
1818
public static class HKDF
1919
{
2020
/// <summary>
@@ -290,7 +290,9 @@ private static int HashLength(HashAlgorithmName hashAlgorithmName)
290290
}
291291
else if (hashAlgorithmName == HashAlgorithmName.MD5)
292292
{
293+
#pragma warning disable CA1416 // HMACMD5 is unsupported on browser, throwing is handled later when making the HMAC call
293294
return HMACMD5.HashSizeInBytes;
295+
#pragma warning restore CA1416
294296
}
295297
else
296298
{

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ private static unsafe void Sign(SimpleDigest hashName, ReadOnlySpan<byte> key, R
7070
{
7171
int res = Interop.BrowserCrypto.Sign(hashName, k, key.Length, src, data.Length, dest, destination.Length);
7272
if (res != 0)
73+
{
7374
throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, res));
75+
}
7476
}
7577
}
7678

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using Internal.Cryptography;
6+
7+
using SimpleDigest = Interop.BrowserCrypto.SimpleDigest;
8+
9+
namespace System.Security.Cryptography
10+
{
11+
internal static partial class Pbkdf2Implementation
12+
{
13+
public static void Fill(
14+
ReadOnlySpan<byte> password,
15+
ReadOnlySpan<byte> salt,
16+
int iterations,
17+
HashAlgorithmName hashAlgorithmName,
18+
Span<byte> destination)
19+
{
20+
Debug.Assert(!destination.IsEmpty);
21+
Debug.Assert(hashAlgorithmName.Name is not null);
22+
23+
if (Interop.BrowserCrypto.CanUseSubtleCrypto)
24+
{
25+
FillSubtleCrypto(password, salt, iterations, hashAlgorithmName, destination);
26+
}
27+
else
28+
{
29+
FillManaged(password, salt, iterations, hashAlgorithmName, destination);
30+
}
31+
}
32+
33+
private static unsafe void FillSubtleCrypto(
34+
ReadOnlySpan<byte> password,
35+
ReadOnlySpan<byte> salt,
36+
int iterations,
37+
HashAlgorithmName hashAlgorithmName,
38+
Span<byte> destination)
39+
{
40+
(SimpleDigest hashName, _) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmName.Name!);
41+
42+
fixed (byte* pPassword = password)
43+
fixed (byte* pSalt = salt)
44+
fixed (byte* pDestination = destination)
45+
{
46+
int result = Interop.BrowserCrypto.DeriveBits(
47+
pPassword, password.Length,
48+
pSalt, salt.Length,
49+
iterations,
50+
hashName,
51+
pDestination, destination.Length);
52+
53+
if (result != 0)
54+
{
55+
throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, result));
56+
}
57+
}
58+
}
59+
60+
private static void FillManaged(
61+
ReadOnlySpan<byte> password,
62+
ReadOnlySpan<byte> salt,
63+
int iterations,
64+
HashAlgorithmName hashAlgorithmName,
65+
Span<byte> destination)
66+
{
67+
using (Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(
68+
password.ToArray(),
69+
salt.ToArray(),
70+
iterations,
71+
hashAlgorithmName,
72+
clearPassword: true))
73+
{
74+
byte[] result = deriveBytes.GetBytes(destination.Length);
75+
result.AsSpan().CopyTo(destination);
76+
}
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)