diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs index fe2790b1ac1242..e7d0461346a20f 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs @@ -145,6 +145,21 @@ internal static void EvpCipherGetGcmTag(SafeEvpCipherCtxHandle ctx, Span 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 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( @@ -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 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( @@ -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, diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/configure.cmake b/src/libraries/Native/Unix/System.Security.Cryptography.Native/configure.cmake index fac8c16343df15..e4be90d163454d 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/configure.cmake +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/configure.cmake @@ -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) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c index d87f8538bb15e7..2dc35d69bf70c0 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c @@ -110,6 +110,7 @@ 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) @@ -117,6 +118,8 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpCipherFinalEx) DllImportEntry(CryptoNative_EvpCipherGetCcmTag) DllImportEntry(CryptoNative_EvpCipherGetGcmTag) + DllImportEntry(CryptoNative_EvpCipherGetAeadTag) + DllImportEntry(CryptoNative_EvpCipherSetAeadTag) DllImportEntry(CryptoNative_EvpCipherReset) DllImportEntry(CryptoNative_EvpCipherSetCcmNonceLength) DllImportEntry(CryptoNative_EvpCipherSetCcmTag) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index cf1c155a6e741e..8650fe562eceae 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -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. @@ -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) \ @@ -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 diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_crypto_config.h.in b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_crypto_config.h.in index 1aa48ba9d76b71..6592ac1551a7b8 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_crypto_config.h.in +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_crypto_config.h.in @@ -2,3 +2,4 @@ #cmakedefine01 HAVE_OPENSSL_EC2M #cmakedefine01 HAVE_OPENSSL_ALPN +#cmakedefine01 HAVE_OPENSSL_CHACHA20POLY1305 diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.c index 8bbc8a4feab0bd..40cfde7fea6f60 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.c @@ -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(); @@ -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; +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.h index a48c0dde383330..c0d652a5e67b61 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_cipher.h @@ -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 @@ -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); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index 5227b2935cb4a9..5e59c691c4b754 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -590,7 +590,7 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.Cipher.cs" /> - + diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Unix.cs new file mode 100644 index 00000000000000..824d37c34fa06a --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ChaCha20Poly1305.Unix.cs @@ -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 key) + { + _ctxHandle = Interop.Crypto.EvpCipherCreatePartial(GetCipher(key.Length * 8)); + + Interop.Crypto.CheckValidOpenSslHandle(_ctxHandle); + Interop.Crypto.EvpCipherSetKeyAndIV( + _ctxHandle, + key, + Span.Empty, + Interop.Crypto.EvpCipherDirection.NoChange); + } + + private void EncryptCore( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + Interop.Crypto.EvpCipherSetKeyAndIV( + _ctxHandle, + Span.Empty, + nonce, + Interop.Crypto.EvpCipherDirection.Encrypt); + + if (associatedData.Length != 0) + { + if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, Span.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 nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData) + { + Interop.Crypto.EvpCipherSetKeyAndIV( + _ctxHandle, + ReadOnlySpan.Empty, + nonce, + Interop.Crypto.EvpCipherDirection.Decrypt); + + if (associatedData.Length != 0) + { + if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, Span.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(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs index abd5a55508026f..55a894e4b18135 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/ChaCha20Poly1305Tests.cs @@ -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); }