Skip to content

Commit

Permalink
Add ChaCha20Poly1305 skeleton (dotnet#52030)
Browse files Browse the repository at this point in the history
- Also adds AesGcm.IsSupported and AesCcm.IsSupported
- The IsSupported APIs will return false on browser rather than throw PNSE

The current ChaCha20Poly1305 implementation only works on recent Win10 builds. However, we should be set up for somebody to add support for other OSes in the near future, assuming we can ride on top of other publicly-exposed implementations.
  • Loading branch information
GrabYourPitchforks authored May 5, 2021
1 parent 0ebcb68 commit a50dcc8
Show file tree
Hide file tree
Showing 28 changed files with 1,018 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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.Security.Cryptography;
using System.Threading;
using Internal.NativeCrypto;

namespace Internal.Cryptography
{
internal static class AeadBCryptHandles
{
private static SafeAlgorithmHandle? s_aesCcm;
private static SafeAlgorithmHandle? s_aesGcm;
private static SafeAlgorithmHandle? s_chaCha20Poly1305;

internal static SafeAlgorithmHandle AesCcm => GetCachedAlgorithmHandle(ref s_aesCcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_CCM);
internal static SafeAlgorithmHandle AesGcm => GetCachedAlgorithmHandle(ref s_aesGcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_GCM);

internal static bool IsChaCha20Poly1305Supported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 20142);
internal static SafeAlgorithmHandle ChaCha20Poly1305 => GetCachedAlgorithmHandle(ref s_chaCha20Poly1305, Cng.BCRYPT_CHACHA20_POLY1305_ALGORITHM);

private static SafeAlgorithmHandle GetCachedAlgorithmHandle(ref SafeAlgorithmHandle? handle, string algId, string? chainingMode = null)
{
// Do we already have a handle to this algorithm?
SafeAlgorithmHandle? existingHandle = Volatile.Read(ref handle);
if (existingHandle != null) { return existingHandle; }

// No cached handle exists; create a new handle. It's ok if multiple threads call
// this concurrently. Only one handle will "win" and the rest will be destroyed.
SafeAlgorithmHandle newHandle = Cng.BCryptOpenAlgorithmProvider(algId, null, Cng.OpenAlgorithmProviderFlags.NONE);
if (chainingMode != null)
{
newHandle.SetCipherMode(chainingMode);
}

existingHandle = Interlocked.CompareExchange(ref handle, newHandle, null);
if (existingHandle != null)
{
newHandle.Dispose();
return existingHandle;
}
else
{
return newHandle;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.Security.Cryptography;
using System.Threading;
using Internal.NativeCrypto;

namespace Internal.Cryptography
{
internal static class BCryptAeadHandleCache
{
private static SafeAlgorithmHandle? s_aesCcm;
private static SafeAlgorithmHandle? s_aesGcm;
private static SafeAlgorithmHandle? s_chaCha20Poly1305;

internal static SafeAlgorithmHandle AesCcm => GetCachedAlgorithmHandle(ref s_aesCcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_CCM);
internal static SafeAlgorithmHandle AesGcm => GetCachedAlgorithmHandle(ref s_aesGcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_GCM);

internal static bool IsChaCha20Poly1305Supported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 20142);
internal static SafeAlgorithmHandle ChaCha20Poly1305 => GetCachedAlgorithmHandle(ref s_chaCha20Poly1305, Cng.BCRYPT_CHACHA20_POLY1305_ALGORITHM);

private static SafeAlgorithmHandle GetCachedAlgorithmHandle(ref SafeAlgorithmHandle? handle, string algId, string? chainingMode = null)
{
// Do we already have a handle to this algorithm?
SafeAlgorithmHandle? existingHandle = Volatile.Read(ref handle);
if (existingHandle != null)
{
return existingHandle;
}

// No cached handle exists; create a new handle. It's ok if multiple threads call
// this concurrently. Only one handle will "win" and the rest will be destroyed.
SafeAlgorithmHandle newHandle = Cng.BCryptOpenAlgorithmProvider(algId, null, Cng.OpenAlgorithmProviderFlags.NONE);
if (chainingMode != null)
{
newHandle.SetCipherMode(chainingMode);
}

existingHandle = Interlocked.CompareExchange(ref handle, newHandle, null);
if (existingHandle != null)
{
newHandle.Dispose();
return existingHandle;
}
else
{
return newHandle;
}
}
}
}
1 change: 1 addition & 0 deletions src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public enum OpenAlgorithmProviderFlags : int

public const string BCRYPT_3DES_ALGORITHM = "3DES";
public const string BCRYPT_AES_ALGORITHM = "AES";
public const string BCRYPT_CHACHA20_POLY1305_ALGORITHM = "CHACHA20_POLY1305";
public const string BCRYPT_DES_ALGORITHM = "DES";
public const string BCRYPT_RC2_ALGORITHM = "RC2";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public sealed partial class AesCcm : System.IDisposable
{
public AesCcm(byte[] key) { }
public AesCcm(System.ReadOnlySpan<byte> key) { }
public static bool IsSupported { get { throw null; } }
public static System.Security.Cryptography.KeySizes NonceByteSizes { get { throw null; } }
public static System.Security.Cryptography.KeySizes TagByteSizes { get { throw null; } }
public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { }
Expand All @@ -32,6 +33,7 @@ public sealed partial class AesGcm : System.IDisposable
{
public AesGcm(byte[] key) { }
public AesGcm(System.ReadOnlySpan<byte> key) { }
public static bool IsSupported { get { throw null; } }
public static System.Security.Cryptography.KeySizes NonceByteSizes { get { throw null; } }
public static System.Security.Cryptography.KeySizes TagByteSizes { get { throw null; } }
public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { }
Expand Down Expand Up @@ -97,6 +99,18 @@ protected AsymmetricSignatureFormatter() { }
public abstract void SetHashAlgorithm(string strName);
public abstract void SetKey(System.Security.Cryptography.AsymmetricAlgorithm key);
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public sealed partial class ChaCha20Poly1305 : System.IDisposable
{
public ChaCha20Poly1305(byte[] key) { }
public ChaCha20Poly1305(System.ReadOnlySpan<byte> key) { }
public static bool IsSupported { get { throw null; } }
public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { }
public void Decrypt(System.ReadOnlySpan<byte> nonce, System.ReadOnlySpan<byte> ciphertext, System.ReadOnlySpan<byte> tag, System.Span<byte> plaintext, System.ReadOnlySpan<byte> associatedData = default(System.ReadOnlySpan<byte>)) { }
public void Dispose() { }
public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) { }
public void Encrypt(System.ReadOnlySpan<byte> nonce, System.ReadOnlySpan<byte> plaintext, System.Span<byte> ciphertext, System.Span<byte> tag, System.ReadOnlySpan<byte> associatedData = default(System.ReadOnlySpan<byte>)) { }
}
public partial class CryptoConfig
{
public CryptoConfig() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
M:System.Security.Cryptography.AesCcm.IsSupported
M:System.Security.Cryptography.AesGcm.IsSupported
M:System.Security.Cryptography.ChaCha20Poly1305.IsSupported
T:System.Security.Cryptography.CryptoConfig
T:System.Security.Cryptography.RandomNumberGenerator
T:System.Security.Cryptography.IncrementalHash
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS</DefineConstants>
Expand Down Expand Up @@ -27,6 +27,7 @@
<Compile Include="Internal\Cryptography\RC2Implementation.cs" />
<Compile Include="Internal\Cryptography\RijndaelImplementation.cs" />
<Compile Include="Internal\Cryptography\TripleDesImplementation.cs" />
<Compile Include="System\Security\Cryptography\AeadCommon.cs" />
<Compile Include="System\Security\Cryptography\Aes.cs" />
<Compile Include="System\Security\Cryptography\AesAEAD.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.cs" />
Expand All @@ -36,6 +37,7 @@
<Compile Include="System\Security\Cryptography\AsymmetricKeyExchangeFormatter.cs" />
<Compile Include="System\Security\Cryptography\AsymmetricSignatureDeformatter.cs" />
<Compile Include="System\Security\Cryptography\AsymmetricSignatureFormatter.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.cs" />
<Compile Include="System\Security\Cryptography\CryptoConfig.cs" />
<Compile Include="System\Security\Cryptography\DeriveBytes.cs" />
<Compile Include="System\Security\Cryptography\DES.cs" />
Expand Down Expand Up @@ -276,9 +278,10 @@
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="System\Security\Cryptography\AeadCommon.Windows.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.Windows.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.Windows.cs" />
<Compile Include="System\Security\Cryptography\AesAEAD.Windows.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.Windows.cs" />
<Compile Include="System\Security\Cryptography\CngKeyLite.cs" />
<Compile Include="System\Security\Cryptography\CngPkcs8.cs" />
<Compile Include="System\Security\Cryptography\DSACng.cs" />
Expand All @@ -304,6 +307,8 @@
Link="Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\AesBCryptModes.cs"
Link="Common\Interop\Windows\BCrypt\AesBCryptModes.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\BCryptAeadHandleCache.cs"
Link="Common\Interop\Windows\BCrypt\BCryptAeadHandleCache.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Cng.cs"
Link="Common\Interop\Windows\BCrypt\Cng.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Interop.BCryptImportKey.cs"
Expand Down Expand Up @@ -583,6 +588,7 @@
Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.Cipher.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.Unix.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.Unix.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.NotSupported.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true' and '$(UseAndroidCrypto)' != 'true' and '$(UseAppleCrypto)' != 'true'">
<Compile Include="Internal\Cryptography\DesImplementation.Unix.cs" />
Expand Down Expand Up @@ -673,6 +679,7 @@
<Compile Include="Internal\Cryptography\RC2Implementation.Android.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.Android.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.Android.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\ECDiffieHellman.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\ECDsa.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\RSA.Create.Android.cs" />
Expand All @@ -693,6 +700,9 @@
<Compile Include="Internal\Cryptography\RandomNumberGeneratorImplementation.cs" />
<Compile Include="Internal\Cryptography\RandomNumberGeneratorImplementation.Browser.cs" />
<Compile Include="Internal\Cryptography\SHAHashProvider.Browser.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\CryptoConfig.Browser.cs" />
<Compile Include="System\Security\Cryptography\RandomNumberGenerator.cs" />
<Compile Include="System\Security\Cryptography\IncrementalHash.cs" />
Expand All @@ -711,6 +721,7 @@
<Reference Include="System.Formats.Asn1" />
<Reference Include="System.Memory" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
<Reference Include="System.Runtime.Extensions" />
<Reference Include="System.Runtime.InteropServices" />
<Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@
using Internal.NativeCrypto;
using static Interop.BCrypt;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace System.Security.Cryptography
{
internal static partial class AesAEAD
internal static partial class AeadCommon
{
public static unsafe void Encrypt(
SafeAlgorithmHandle algorithm,
SafeKeyHandle keyHandle,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> associatedData,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> tag)
{
fixed (byte* plaintextBytes = plaintext)
fixed (byte* nonceBytes = nonce)
fixed (byte* ciphertextBytes = ciphertext)
fixed (byte* tagBytes = tag)
fixed (byte* associatedDataBytes = associatedData)
// bcrypt sometimes misbehaves when given nullptr buffers; ensure non-nullptr
fixed (byte* plaintextBytes = &GetNonNullPinnableReference(plaintext))
fixed (byte* nonceBytes = &GetNonNullPinnableReference(nonce))
fixed (byte* ciphertextBytes = &GetNonNullPinnableReference(ciphertext))
fixed (byte* tagBytes = &GetNonNullPinnableReference(tag))
fixed (byte* associatedDataBytes = &GetNonNullPinnableReference(associatedData))
{
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Create();
authInfo.pbNonce = nonceBytes;
Expand Down Expand Up @@ -55,7 +57,6 @@ public static unsafe void Encrypt(
}

public static unsafe void Decrypt(
SafeAlgorithmHandle algorithm,
SafeKeyHandle keyHandle,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> associatedData,
Expand All @@ -64,11 +65,12 @@ public static unsafe void Decrypt(
Span<byte> plaintext,
bool clearPlaintextOnFailure)
{
fixed (byte* plaintextBytes = plaintext)
fixed (byte* nonceBytes = nonce)
fixed (byte* ciphertextBytes = ciphertext)
fixed (byte* tagBytes = tag)
fixed (byte* associatedDataBytes = associatedData)
// bcrypt sometimes misbehaves when given nullptr buffers; ensure non-nullptr
fixed (byte* plaintextBytes = &GetNonNullPinnableReference(plaintext))
fixed (byte* nonceBytes = &GetNonNullPinnableReference(nonce))
fixed (byte* ciphertextBytes = &GetNonNullPinnableReference(ciphertext))
fixed (byte* tagBytes = &GetNonNullPinnableReference(tag))
fixed (byte* associatedDataBytes = &GetNonNullPinnableReference(associatedData))
{
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Create();
authInfo.pbNonce = nonceBytes;
Expand Down Expand Up @@ -108,5 +110,15 @@ public static unsafe void Decrypt(
}
}
}

// Implementations below based on internal MemoryMarshal.GetNonNullPinnableReference methods.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ref readonly byte GetNonNullPinnableReference(ReadOnlySpan<byte> buffer)
=> ref buffer.Length != 0 ? ref MemoryMarshal.GetReference(buffer) : ref Unsafe.AsRef<byte>((void*)1);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ref byte GetNonNullPinnableReference(Span<byte> buffer)
=> ref buffer.Length != 0 ? ref MemoryMarshal.GetReference(buffer) : ref Unsafe.AsRef<byte>((void*)1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Internal.Cryptography;

namespace System.Security.Cryptography
{
internal static partial class AeadCommon
{
public static void CheckArgumentsForNull(
byte[] nonce,
byte[] plaintext,
byte[] ciphertext,
byte[] tag)
{
if (nonce == null)
throw new ArgumentNullException(nameof(nonce));

if (plaintext == null)
throw new ArgumentNullException(nameof(plaintext));

if (ciphertext == null)
throw new ArgumentNullException(nameof(ciphertext));

if (tag == null)
throw new ArgumentNullException(nameof(tag));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,12 @@ namespace System.Security.Cryptography
{
internal static partial class AesAEAD
{
public static void CheckKeySize(int keySizeInBits)
public static void CheckKeySize(int keySizeInBytes)
{
if (keySizeInBits != 128 && keySizeInBits != 192 && keySizeInBits != 256)
if (keySizeInBytes != (128 / 8) && keySizeInBytes != (192 / 8) && keySizeInBytes != (256 / 8))
{
throw new CryptographicException(SR.Cryptography_InvalidKeySize);
}
}

public static void CheckArgumentsForNull(
byte[] nonce,
byte[] plaintext,
byte[] ciphertext,
byte[] tag)
{
if (nonce == null)
throw new ArgumentNullException(nameof(nonce));

if (plaintext == null)
throw new ArgumentNullException(nameof(plaintext));

if (ciphertext == null)
throw new ArgumentNullException(nameof(ciphertext));

if (tag == null)
throw new ArgumentNullException(nameof(tag));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private void ImportKey(ReadOnlySpan<byte> key)
_key = key.ToArray();
}

private void EncryptInternal(
private void EncryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Expand Down Expand Up @@ -97,7 +97,7 @@ private void EncryptInternal(
}
}

private void DecryptInternal(
private void DecryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
public partial class AesCcm
{
public static bool IsSupported => false;
}
}
Loading

0 comments on commit a50dcc8

Please sign in to comment.