Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ChaCha20 Poly1305 on Unix #52522

Merged
merged 8 commits into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
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
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
#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);
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
#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" />
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
</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;
vcsjones marked this conversation as resolved.
Show resolved Hide resolved

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of morbid curiosity, what happens if multiple threads call this at the same time on the same instance? Is there a potential for buffer overrun?

(I know this is copied from the existing AEAD code, so I'm not suggesting changes at this time. Just trying to calculate risk exposure.)

{
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