Skip to content

Commit

Permalink
Enable Rfc2898DeriveBytes on Browser WASM (#71768)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
eerhardt committed Jul 9, 2022
1 parent 4a38ac3 commit 8f75cc9
Show file tree
Hide file tree
Showing 24 changed files with 250 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,16 @@ internal static unsafe partial int EncryptDecrypt(
int input_len,
byte* output_buffer,
int output_len);

[LibraryImport(Libraries.CryptoNative, EntryPoint = "SystemCryptoNativeBrowser_DeriveBits")]
internal static unsafe partial int DeriveBits(
byte* password_buffer,
int password_len,
byte* salt_buffer,
int salt_len,
int iterations,
SimpleDigest hashAlgorithm,
byte* output_buffer,
int output_len);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,25 @@ internal static int MacData(
ReadOnlySpan<byte> source,
Span<byte> destination)
{
if (Helpers.HasHMAC)
{
if (hashAlgorithm == HashAlgorithmName.SHA256)
{
return HMACSHA256.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.SHA1)
{
return HMACSHA1.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.SHA512)
{
return HMACSHA512.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.SHA384)
{
return HMACSHA384.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.MD5)
{
return HMACMD5.HashData(key, source, destination);
}
if (hashAlgorithm == HashAlgorithmName.SHA256)
{
return HMACSHA256.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.SHA1)
{
return HMACSHA1.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.SHA512)
{
return HMACSHA512.HashData(key, source, destination);
}
else if (hashAlgorithm == HashAlgorithmName.SHA384)
{
return HMACSHA384.HashData(key, source, destination);
}
else if (Helpers.HasMD5 && hashAlgorithm == HashAlgorithmName.MD5)
{
return HMACMD5.HashData(key, source, destination);
}

throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name));
Expand Down
12 changes: 2 additions & 10 deletions src/libraries/Common/src/System/Security/Cryptography/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@ namespace Internal.Cryptography
internal static partial class Helpers
{
[UnsupportedOSPlatformGuard("browser")]
internal static bool HasSymmetricEncryption { get; } =
#if NETCOREAPP
!OperatingSystem.IsBrowser();
#else
true;
#endif

[UnsupportedOSPlatformGuard("browser")]
internal static bool HasHMAC { get; } =
internal static bool HasNonAesSymmetricEncryption =>
#if NETCOREAPP
!OperatingSystem.IsBrowser();
#else
Expand All @@ -37,7 +29,7 @@ internal static partial class Helpers
#if NETCOREAPP
[UnsupportedOSPlatformGuard("android")]
[UnsupportedOSPlatformGuard("browser")]
public static bool IsRC2Supported => !OperatingSystem.IsAndroid();
public static bool IsRC2Supported => !OperatingSystem.IsAndroid() && !OperatingSystem.IsBrowser();
#else
public static bool IsRC2Supported => true;
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,29 @@ internal static unsafe int Decrypt(
{
Debug.Assert(destination.Length >= encryptedData.Length);

if (!Helpers.HasSymmetricEncryption)
// Don't check that algorithmIdentifier.Parameters is set here.
// Maybe some future PBES3 will have one with a default.

if (algorithmIdentifier.Algorithm == Oids.PasswordBasedEncryptionScheme2)
{
return Pbes2Decrypt(
algorithmIdentifier.Parameters,
password,
passwordBytes,
encryptedData,
destination);
}

if (!Helpers.HasNonAesSymmetricEncryption)
{
throw new CryptographicException(
SR.Format(
SR.Cryptography_UnknownAlgorithmIdentifier,
algorithmIdentifier.Algorithm));
}

// Don't check that algorithmIdentifier.Parameters is set here.
// Maybe some future PBES3 will have one with a default.

HashAlgorithmName digestAlgorithmName;
SymmetricAlgorithm? cipher = null;
SymmetricAlgorithm cipher;

bool pkcs12 = false;

Expand Down Expand Up @@ -131,13 +141,6 @@ internal static unsafe int Decrypt(
cipher.KeySize = 40;
pkcs12 = true;
break;
case Oids.PasswordBasedEncryptionScheme2:
return Pbes2Decrypt(
algorithmIdentifier.Parameters,
password,
passwordBytes,
encryptedData,
destination);
default:
throw new CryptographicException(
SR.Format(
Expand All @@ -146,7 +149,6 @@ internal static unsafe int Decrypt(
}

Debug.Assert(digestAlgorithmName.Name != null);
Debug.Assert(cipher != null);

using (cipher)
{
Expand Down Expand Up @@ -237,14 +239,6 @@ internal static void InitiateEncryption(
{
Debug.Assert(pbeParameters != null);

if (!Helpers.HasSymmetricEncryption)
{
throw new CryptographicException(
SR.Format(
SR.Cryptography_UnknownAlgorithmIdentifier,
pbeParameters.EncryptionAlgorithm));
}

isPkcs12 = false;

switch (pbeParameters.EncryptionAlgorithm)
Expand All @@ -264,7 +258,7 @@ internal static void InitiateEncryption(
cipher.KeySize = 256;
encryptionAlgorithmOid = Oids.Aes256Cbc;
break;
case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12:
case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12 when Helpers.HasNonAesSymmetricEncryption:
cipher = TripleDES.Create();
cipher.KeySize = 192;
encryptionAlgorithmOid = Oids.Pkcs12PbeWithShaAnd3Key3Des;
Expand Down Expand Up @@ -393,12 +387,6 @@ internal static unsafe int Encrypt(
Debug.Assert(pwdTmpBytes!.Length == 0);
}

if (!Helpers.HasHMAC)
{
throw new CryptographicException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, "HMAC" + prf.Name));
}

using (var pbkdf2 = new Rfc2898DeriveBytes(pwdTmpBytes, salt.ToArray(), iterationCount, prf))
{
derivedKey = pbkdf2.GetBytes(keySizeBytes);
Expand Down Expand Up @@ -540,8 +528,6 @@ private static unsafe int Pbes2Decrypt(
Rfc2898DeriveBytes pbkdf2 =
OpenPbkdf2(password, pbes2Params.KeyDerivationFunc.Parameters, out int? requestedKeyLength);

Debug.Assert(Helpers.HasHMAC);

using (pbkdf2)
{
// The biggest block size (for IV) we support is AES (128-bit / 16 byte)
Expand Down Expand Up @@ -580,12 +566,6 @@ private static SymmetricAlgorithm OpenCipher(
{
string? algId = encryptionScheme.Algorithm;

if (!Helpers.HasSymmetricEncryption)
{
throw new CryptographicException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, algId));
}

if (algId == Oids.Aes128Cbc ||
algId == Oids.Aes192Cbc ||
algId == Oids.Aes256Cbc)
Expand Down Expand Up @@ -624,6 +604,12 @@ private static SymmetricAlgorithm OpenCipher(
return aes;
}

if (!Helpers.HasNonAesSymmetricEncryption)
{
throw new CryptographicException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, algId));
}

if (algId == Oids.TripleDesCbc)
{
// https://tools.ietf.org/html/rfc8018#appendix-B.2.2
Expand Down Expand Up @@ -777,12 +763,6 @@ private static unsafe Rfc2898DeriveBytes OpenPbkdf2(
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}

if (!Helpers.HasHMAC)
{
throw new CryptographicException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, "HMAC" + prf.Name));
}

int iterationCount = NormalizeIterationCount(pbkdf2Params.IterationCount);
ReadOnlyMemory<byte> saltMemory = pbkdf2Params.Salt.Specified.Value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,6 @@ protected virtual void HashCore(System.ReadOnlySpan<byte> source) { }
public override string ToString() { throw null; }
public static bool TryFromOid(string oidValue, out System.Security.Cryptography.HashAlgorithmName value) { throw null; }
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static partial class HKDF
{
public static byte[] DeriveKey(System.Security.Cryptography.HashAlgorithmName hashAlgorithmName, byte[] ikm, int outputLength, byte[]? salt = null, byte[]? info = null) { throw null; }
Expand Down Expand Up @@ -1720,7 +1719,6 @@ public RC2CryptoServiceProvider() { }
public override void GenerateIV() { }
public override void GenerateKey() { }
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public partial class Rfc2898DeriveBytes : System.Security.Cryptography.DeriveBytes
{
[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}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@
<Compile Include="System\Security\Cryptography\OidLookup.NoFallback.cs" />
<Compile Include="System\Security\Cryptography\OpenSsl.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\PasswordDeriveBytes.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\Pbkdf2Implementation.Managed.cs" />
<Compile Include="System\Security\Cryptography\Pbkdf2Implementation.Browser.cs" />
<Compile Include="System\Security\Cryptography\RandomNumberGeneratorImplementation.Browser.cs" />
<Compile Include="System\Security\Cryptography\RC2CryptoServiceProvider.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\RC2Implementation.NotSupported.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ private unsafe int EncryptDecrypt(ReadOnlySpan<byte> input, Span<byte> output)
pOutput, output.Length);

if (bytesWritten < 0)
{
throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, bytesWritten));
}

return bytesWritten;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Runtime.Versioning;
using Internal.Cryptography;

namespace System.Security.Cryptography
{
Expand All @@ -14,7 +15,6 @@ namespace System.Security.Cryptography
/// phase to be skipped, and the master key to be used directly as the pseudorandom key.
/// See <a href="https://tools.ietf.org/html/rfc5869">RFC5869</a> for more information.
/// </remarks>
[UnsupportedOSPlatform("browser")]
public static class HKDF
{
/// <summary>
Expand Down Expand Up @@ -290,7 +290,9 @@ private static int HashLength(HashAlgorithmName hashAlgorithmName)
}
else if (hashAlgorithmName == HashAlgorithmName.MD5)
{
#pragma warning disable CA1416 // HMACMD5 is unsupported on browser, throwing is handled later when making the HMAC call
return HMACMD5.HashSizeInBytes;
#pragma warning restore CA1416
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ private static unsafe void Sign(SimpleDigest hashName, ReadOnlySpan<byte> key, R
{
int res = Interop.BrowserCrypto.Sign(hashName, k, key.Length, src, data.Length, dest, destination.Length);
if (res != 0)
{
throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, res));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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;

using SimpleDigest = Interop.BrowserCrypto.SimpleDigest;

namespace System.Security.Cryptography
{
internal static partial class Pbkdf2Implementation
{
public static void Fill(
ReadOnlySpan<byte> password,
ReadOnlySpan<byte> salt,
int iterations,
HashAlgorithmName hashAlgorithmName,
Span<byte> destination)
{
Debug.Assert(!destination.IsEmpty);
Debug.Assert(hashAlgorithmName.Name is not null);

if (Interop.BrowserCrypto.CanUseSubtleCrypto)
{
FillSubtleCrypto(password, salt, iterations, hashAlgorithmName, destination);
}
else
{
FillManaged(password, salt, iterations, hashAlgorithmName, destination);
}
}

private static unsafe void FillSubtleCrypto(
ReadOnlySpan<byte> password,
ReadOnlySpan<byte> salt,
int iterations,
HashAlgorithmName hashAlgorithmName,
Span<byte> destination)
{
(SimpleDigest hashName, _) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmName.Name!);

fixed (byte* pPassword = password)
fixed (byte* pSalt = salt)
fixed (byte* pDestination = destination)
{
int result = Interop.BrowserCrypto.DeriveBits(
pPassword, password.Length,
pSalt, salt.Length,
iterations,
hashName,
pDestination, destination.Length);

if (result != 0)
{
throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, result));
}
}
}

private static void FillManaged(
ReadOnlySpan<byte> password,
ReadOnlySpan<byte> salt,
int iterations,
HashAlgorithmName hashAlgorithmName,
Span<byte> destination)
{
using (Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(
password.ToArray(),
salt.ToArray(),
iterations,
hashAlgorithmName,
clearPassword: true))
{
byte[] result = deriveBytes.GetBytes(destination.Length);
result.AsSpan().CopyTo(destination);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ public static unsafe void Fill(
Debug.Assert(!destination.IsEmpty);
Debug.Assert(hashAlgorithmName.Name is not null);

if (!Helpers.HasHMAC)
{
throw new CryptographicException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, "HMAC" + hashAlgorithmName.Name));
}

using (Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(
password.ToArray(),
salt.ToArray(),
Expand Down
Loading

0 comments on commit 8f75cc9

Please sign in to comment.