Skip to content

Commit

Permalink
Support ChaCha20 Poly1305 on Unix
Browse files Browse the repository at this point in the history
Like AES-GCM and AES-CCM, the macOS build uses OpenSSL for the implementation.

Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
  • Loading branch information
vcsjones and am11 committed May 11, 2021
1 parent acdc536 commit 31c5a7c
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,21 @@ internal static void EvpCipherGetGcmTag(SafeEvpCipherCtxHandle ctx, Span<byte> t
}
}

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpCipherGetAeadTag")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EvpCipherGetAeadTag(
SafeEvpCipherCtxHandle ctx,
ref byte tag,
int tagLength);

internal static void EvpCipherGetAeadTag(SafeEvpCipherCtxHandle ctx, Span<byte> tag)
{
if (!EvpCipherGetAeadTag(ctx, ref MemoryMarshal.GetReference(tag), tag.Length))
{
throw CreateOpenSslCryptographicException();
}
}

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpCipherSetGcmTag")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EvpCipherSetGcmTag(
Expand All @@ -160,6 +175,21 @@ internal static void EvpCipherSetGcmTag(SafeEvpCipherCtxHandle ctx, ReadOnlySpan
}
}

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpCipherSetAeadTag")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EvpCipherSetAeadTag(
SafeEvpCipherCtxHandle ctx,
ref byte tag,
int tagLength);

internal static void EvpCipherSetAeadTag(SafeEvpCipherCtxHandle ctx, ReadOnlySpan<byte> tag)
{
if (!EvpCipherSetAeadTag(ctx, ref MemoryMarshal.GetReference(tag), tag.Length))
{
throw CreateOpenSslCryptographicException();
}
}

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpCipherGetCcmTag")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EvpCipherGetCcmTag(
Expand Down Expand Up @@ -280,6 +310,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpRC2Ecb")]
internal static extern IntPtr EvpRC2Ecb();

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpChaCha20Poly1305")]
internal static extern IntPtr EvpChaCha20Poly1305();

internal enum EvpCipherDirection : int
{
NoChange = -1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ check_function_exists(
SSL_get0_alpn_selected
HAVE_OPENSSL_ALPN)

check_function_exists(
EVP_chacha20_poly1305
HAVE_OPENSSL_CHACHA20POLY1305
)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/pal_crypto_config.h.in
${CMAKE_CURRENT_BINARY_DIR}/pal_crypto_config.h)
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,16 @@ static const Entry s_cryptoNative[] =
DllImportEntry(CryptoNative_EvpAes256Cfb8)
DllImportEntry(CryptoNative_EvpAes256Ecb)
DllImportEntry(CryptoNative_EvpAes256Gcm)
DllImportEntry(CryptoNative_EvpChaCha20Poly1305)
DllImportEntry(CryptoNative_EvpCipherCreate2)
DllImportEntry(CryptoNative_EvpCipherCreatePartial)
DllImportEntry(CryptoNative_EvpCipherCtxSetPadding)
DllImportEntry(CryptoNative_EvpCipherDestroy)
DllImportEntry(CryptoNative_EvpCipherFinalEx)
DllImportEntry(CryptoNative_EvpCipherGetCcmTag)
DllImportEntry(CryptoNative_EvpCipherGetGcmTag)
DllImportEntry(CryptoNative_EvpCipherGetAeadTag)
DllImportEntry(CryptoNative_EvpCipherSetAeadTag)
DllImportEntry(CryptoNative_EvpCipherReset)
DllImportEntry(CryptoNative_EvpCipherSetCcmNonceLength)
DllImportEntry(CryptoNative_EvpCipherSetCcmTag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX* ctx,
void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsigned int* len);
#endif

#if !HAVE_OPENSSL_CHACHA20POLY1305
#undef HAVE_OPENSSL_CHACHA20POLY1305
#define HAVE_OPENSSL_CHACHA20POLY1305 1
const EVP_CIPHER* EVP_chacha20_poly1305(void);
#define EVP_CTRL_AEAD_GET_TAG 0x10
#define EVP_CTRL_AEAD_SET_TAG 0x11
#endif

#define API_EXISTS(fn) (fn != NULL)

// List of all functions from the libssl that are used in the System.Security.Cryptography.Native.
Expand Down Expand Up @@ -281,6 +289,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
REQUIRED_FUNCTION(EVP_aes_256_cfb8) \
REQUIRED_FUNCTION(EVP_aes_256_ecb) \
REQUIRED_FUNCTION(EVP_aes_256_gcm) \
LIGHTUP_FUNCTION(EVP_chacha20_poly1305) \
LEGACY_FUNCTION(EVP_CIPHER_CTX_cleanup) \
REQUIRED_FUNCTION(EVP_CIPHER_CTX_ctrl) \
FALLBACK_FUNCTION(EVP_CIPHER_CTX_free) \
Expand Down Expand Up @@ -714,6 +723,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define EVP_aes_256_ecb EVP_aes_256_ecb_ptr
#define EVP_aes_256_gcm EVP_aes_256_gcm_ptr
#define EVP_aes_256_ccm EVP_aes_256_ccm_ptr
#define EVP_chacha20_poly1305 EVP_chacha20_poly1305_ptr
#define EVP_CIPHER_CTX_cleanup EVP_CIPHER_CTX_cleanup_ptr
#define EVP_CIPHER_CTX_ctrl EVP_CIPHER_CTX_ctrl_ptr
#define EVP_CIPHER_CTX_free EVP_CIPHER_CTX_free_ptr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

#cmakedefine01 HAVE_OPENSSL_EC2M
#cmakedefine01 HAVE_OPENSSL_ALPN
#cmakedefine01 HAVE_OPENSSL_CHACHA20POLY1305
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,30 @@ int32_t CryptoNative_EvpCipherSetCcmTag(EVP_CIPHER_CTX* ctx, uint8_t* tag, int32
return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tagLength, tag);
}

int32_t CryptoNative_EvpCipherGetAeadTag(EVP_CIPHER_CTX* ctx, uint8_t* tag, int32_t tagLength)
{
#if HAVE_OPENSSL_CHACHA20POLY1305
return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tagLength, tag);
#else
(void)ctx;
(void)tag;
(void)tagLength;
return 0;
#endif
}

int32_t CryptoNative_EvpCipherSetAeadTag(EVP_CIPHER_CTX* ctx, uint8_t* tag, int32_t tagLength)
{
#if HAVE_OPENSSL_CHACHA20POLY1305
return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tagLength, tag);
#else
(void)ctx;
(void)tag;
(void)tagLength;
return 0;
#endif
}

const EVP_CIPHER* CryptoNative_EvpAes128Ecb()
{
return EVP_aes_128_ecb();
Expand Down Expand Up @@ -332,3 +356,15 @@ const EVP_CIPHER* CryptoNative_EvpRC2Cbc()
{
return EVP_rc2_cbc();
}

const EVP_CIPHER* CryptoNative_EvpChaCha20Poly1305()
{
#if HAVE_OPENSSL_CHACHA20POLY1305
if (API_EXISTS(EVP_chacha20_poly1305))
{
return EVP_chacha20_poly1305();
}
#endif

return NULL;
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ Sets tag for authenticated decryption
*/
PALEXPORT int32_t CryptoNative_EvpCipherSetCcmTag(EVP_CIPHER_CTX* ctx, uint8_t* tag, int32_t tagLength);

/*
Function:
EvpCipherGetAeadTag
Retrieves tag for authenticated encryption
*/
PALEXPORT int32_t CryptoNative_EvpCipherGetAeadTag(EVP_CIPHER_CTX* ctx, uint8_t* tag, int32_t tagLength);

/*
Function:
EvpCipherSetAeadTag
Sets tag for authenticated decryption
*/
PALEXPORT int32_t CryptoNative_EvpCipherSetAeadTag(EVP_CIPHER_CTX* ctx, uint8_t* tag, int32_t tagLength);

/*
Function:
EvpAes128Ecb
Expand Down Expand Up @@ -309,3 +325,12 @@ EvpRC2Cbc
Direct shim to EVP_des_rc2_cbc.
*/
PALEXPORT const EVP_CIPHER* CryptoNative_EvpRC2Cbc(void);

/*
Function:
EvpChaCha20Poly1305
Direct shim to EVP_chacha20_poly1305. Returns NULL if not available
on the current platform.
*/
PALEXPORT const EVP_CIPHER* CryptoNative_EvpChaCha20Poly1305(void);
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,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" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true' and '$(UseAndroidCrypto)' != 'true' and '$(UseAppleCrypto)' != 'true'">
<Compile Include="Internal\Cryptography\DesImplementation.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 System.Diagnostics.CodeAnalysis;
using Microsoft.Win32.SafeHandles;

namespace System.Security.Cryptography
{
public sealed partial class ChaCha20Poly1305
{
public static bool IsSupported { get; } = Interop.Crypto.EvpChaCha20Poly1305() != IntPtr.Zero;

private SafeEvpCipherCtxHandle _ctxHandle;

[MemberNotNull(nameof(_ctxHandle))]
private void ImportKey(ReadOnlySpan<byte> key)
{
_ctxHandle = Interop.Crypto.EvpCipherCreatePartial(GetCipher(key.Length * 8));

Interop.Crypto.CheckValidOpenSslHandle(_ctxHandle);
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
key,
Span<byte>.Empty,
Interop.Crypto.EvpCipherDirection.NoChange);
}

private void EncryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> tag,
ReadOnlySpan<byte> associatedData = default)
{
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
Span<byte>.Empty,
nonce,
Interop.Crypto.EvpCipherDirection.Encrypt);

if (associatedData.Length != 0)
{
if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, Span<byte>.Empty, out _, associatedData))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
}

if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, ciphertext, out int ciphertextBytesWritten, plaintext))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}

if (!Interop.Crypto.EvpCipherFinalEx(
_ctxHandle,
ciphertext.Slice(ciphertextBytesWritten),
out int bytesWritten))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}

ciphertextBytesWritten += bytesWritten;

if (ciphertextBytesWritten != ciphertext.Length)
{
Debug.Fail($"ChaCha20Poly1305 encrypt wrote {ciphertextBytesWritten} of {ciphertext.Length} bytes.");
throw new CryptographicException();
}

Interop.Crypto.EvpCipherGetAeadTag(_ctxHandle, tag);
}

private void DecryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Span<byte> plaintext,
ReadOnlySpan<byte> associatedData)
{
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
ReadOnlySpan<byte>.Empty,
nonce,
Interop.Crypto.EvpCipherDirection.Decrypt);

if (associatedData.Length != 0)
{
if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, Span<byte>.Empty, out _, associatedData))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
}

if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, plaintext, out int plaintextBytesWritten, ciphertext))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}

Interop.Crypto.EvpCipherSetAeadTag(_ctxHandle, tag);

if (!Interop.Crypto.EvpCipherFinalEx(
_ctxHandle,
plaintext.Slice(plaintextBytesWritten),
out int bytesWritten))
{
CryptographicOperations.ZeroMemory(plaintext);
throw new CryptographicException(SR.Cryptography_AuthTagMismatch);
}

plaintextBytesWritten += bytesWritten;

if (plaintextBytesWritten != plaintext.Length)
{
Debug.Fail($"ChaCha20Poly1305 decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes.");
throw new CryptographicException();
}
}

private static IntPtr GetCipher(int keySizeInBits)
{
switch (keySizeInBits)
{
case 256: return Interop.Crypto.EvpChaCha20Poly1305();
default:
Debug.Fail("Key size should already be validated");
return IntPtr.Zero;
}
}

public void Dispose()
{
_ctxHandle.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,11 @@ public static void CheckIsSupported()
// The test queries the OS directly to ensure our version check is correct.
expectedIsSupported = CngUtility.IsAlgorithmSupported("CHACHA20_POLY1305");
}
else if (PlatformDetection.IsOSX || PlatformDetection.IsOpenSslSupported)
{
const int OpenSslChaChaMinimumVersion = 0x1010000F;
expectedIsSupported = SafeEvpPKeyHandle.OpenSslVersion >= OpenSslChaChaMinimumVersion;
}

Assert.Equal(expectedIsSupported, ChaCha20Poly1305.IsSupported);
}
Expand Down

0 comments on commit 31c5a7c

Please sign in to comment.