From cdcee070c12ed57e02084354443dbd43acd5afb8 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 2 Jul 2025 17:20:03 -0700 Subject: [PATCH 01/13] Managed code to call OSSL AES-KW --- .../Interop.EVP.Cipher.cs | 9 +++ .../src/System.Security.Cryptography.csproj | 2 +- .../Cryptography/AesImplementation.Android.cs | 63 +++++++++++++++++++ .../Cryptography/AesImplementation.OpenSsl.cs | 63 +++++++++++++++++++ 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Android.cs 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 df82c65487de19..1a90ae1c95a371 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 @@ -258,6 +258,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Ccm")] internal static partial IntPtr EvpAes128Ccm(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128WrapPad")] + internal static partial IntPtr EvpAes128WrapPad(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Ecb")] internal static partial IntPtr EvpAes192Ecb(); @@ -276,6 +279,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Ccm")] internal static partial IntPtr EvpAes192Ccm(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192WrapPad")] + internal static partial IntPtr EvpAes192WrapPad(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Ecb")] internal static partial IntPtr EvpAes256Ecb(); @@ -294,6 +300,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Ccm")] internal static partial IntPtr EvpAes256Ccm(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256WrapPad")] + internal static partial IntPtr EvpAes256WrapPad(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDesCbc")] internal static partial IntPtr EvpDesCbc(); diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 310a4395a91e06..253a37d479b5dd 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1221,7 +1221,7 @@ Link="Common\System\Security\Cryptography\SlhDsaImplementation.NotSupported.cs" /> - + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Android.cs new file mode 100644 index 00000000000000..98fe89c6ac544c --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Android.cs @@ -0,0 +1,63 @@ +// 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 +{ + internal sealed partial class AesImplementation + { + private static UniversalCryptoTransform CreateTransformCore( + CipherMode cipherMode, + PaddingMode paddingMode, + ReadOnlySpan key, + byte[]? iv, + int blockSize, + int paddingSize, + int feedback, + bool encrypting) + { + // The algorithm pointer is a static pointer, so not having any cleanup code is correct. + IntPtr algorithm = GetAlgorithm(key.Length * 8, feedback * 8, cipherMode); + + BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, paddingSize, key, iv, encrypting); + return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting); + } + + private static OpenSslCipherLite CreateLiteCipher( + CipherMode cipherMode, + ReadOnlySpan key, + ReadOnlySpan iv, + int blockSize, + int paddingSize, + int feedback, + bool encrypting) + { + IntPtr algorithm = GetAlgorithm(key.Length * 8, feedback * 8, cipherMode); + return new OpenSslCipherLite(algorithm, blockSize, paddingSize, key, iv, encrypting); + } + + private static IntPtr GetAlgorithm(int keySize, int feedback, CipherMode cipherMode) => + (keySize, cipherMode) switch + { + // Neither OpenSSL nor Cng Aes support CTS mode. + + (128, CipherMode.CBC) => Interop.Crypto.EvpAes128Cbc(), + (128, CipherMode.ECB) => Interop.Crypto.EvpAes128Ecb(), + (128, CipherMode.CFB) when feedback == 8 => Interop.Crypto.EvpAes128Cfb8(), + (128, CipherMode.CFB) when feedback == 128 => Interop.Crypto.EvpAes128Cfb128(), + + (192, CipherMode.CBC) => Interop.Crypto.EvpAes192Cbc(), + (192, CipherMode.ECB) => Interop.Crypto.EvpAes192Ecb(), + (192, CipherMode.CFB) when feedback == 8 => Interop.Crypto.EvpAes192Cfb8(), + (192, CipherMode.CFB) when feedback == 128 => Interop.Crypto.EvpAes192Cfb128(), + + (256, CipherMode.CBC) => Interop.Crypto.EvpAes256Cbc(), + (256, CipherMode.ECB) => Interop.Crypto.EvpAes256Ecb(), + (256, CipherMode.CFB) when feedback == 8 => Interop.Crypto.EvpAes256Cfb8(), + (256, CipherMode.CFB) when feedback == 128 => Interop.Crypto.EvpAes256Cfb128(), + + _ => throw (keySize == 128 || keySize == 192 || keySize == 256 ? (Exception) + new NotSupportedException() : + new CryptographicException(SR.Cryptography_InvalidKeySize)), + }; + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 98fe89c6ac544c..daaad0086cab9c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -1,6 +1,10 @@ // 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.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + namespace System.Security.Cryptography { internal sealed partial class AesImplementation @@ -35,6 +39,56 @@ private static OpenSslCipherLite CreateLiteCipher( return new OpenSslCipherLite(algorithm, blockSize, paddingSize, key, iv, encrypting); } + protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) + { + int written = KeyWrap(source, destination, enc: 1); + Debug.Assert(written == destination.Length); + } + + protected override int DecryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) + { + return KeyWrap(source, destination, enc: 0); + } + + private int KeyWrap(ReadOnlySpan source, Span destination, int enc) + { + Debug.Assert(enc is 0 or 1); + + SafeEvpCipherCtxHandle ctx = GetKey().UseKey( + state: enc, + static (enc, key) => + { + int keySizeInBits = key.Length * 8; + + IntPtr algorithm = GetKeyWrapAlgorithm(keySizeInBits); + + return Interop.Crypto.EvpCipherCreate( + algorithm, + ref MemoryMarshal.GetReference(key), + key.Length * 8, + ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), + enc); + }); + + int written; + + using (ctx) + { + bool ret = Interop.Crypto.EvpCipherUpdate( + ctx, + destination, + out written, + source); + + if (!ret) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + } + + return written; + } + private static IntPtr GetAlgorithm(int keySize, int feedback, CipherMode cipherMode) => (keySize, cipherMode) switch { @@ -59,5 +113,14 @@ private static IntPtr GetAlgorithm(int keySize, int feedback, CipherMode cipherM new NotSupportedException() : new CryptographicException(SR.Cryptography_InvalidKeySize)), }; + + private static IntPtr GetKeyWrapAlgorithm(int keySize) => + keySize switch + { + 128 => Interop.Crypto.EvpAes128WrapPad(), + 192 => Interop.Crypto.EvpAes192WrapPad(), + 256 => Interop.Crypto.EvpAes256WrapPad(), + _ => throw new CryptographicException(SR.Cryptography_InvalidKeySize), + }; } } From 770275e908e082d93c8d74061bca72694bef6619 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 2 Jul 2025 18:39:44 -0700 Subject: [PATCH 02/13] Native code for OSSL AES-KW --- .../entrypoints.c | 3 +++ .../opensslshim.h | 6 +++++ .../pal_evp_cipher.c | 18 ++++++++++++++ .../pal_evp_cipher.h | 24 +++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 2eec9783488965..60c78e6c8bf203 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -100,18 +100,21 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpAes128Cfb8) DllImportEntry(CryptoNative_EvpAes128Ecb) DllImportEntry(CryptoNative_EvpAes128Gcm) + DllImportEntry(CryptoNative_EvpAes128WrapPad) DllImportEntry(CryptoNative_EvpAes192Cbc) DllImportEntry(CryptoNative_EvpAes192Ccm) DllImportEntry(CryptoNative_EvpAes192Cfb128) DllImportEntry(CryptoNative_EvpAes192Cfb8) DllImportEntry(CryptoNative_EvpAes192Ecb) DllImportEntry(CryptoNative_EvpAes192Gcm) + DllImportEntry(CryptoNative_EvpAes192WrapPad) DllImportEntry(CryptoNative_EvpAes256Cbc) DllImportEntry(CryptoNative_EvpAes256Ccm) DllImportEntry(CryptoNative_EvpAes256Cfb128) DllImportEntry(CryptoNative_EvpAes256Cfb8) DllImportEntry(CryptoNative_EvpAes256Ecb) DllImportEntry(CryptoNative_EvpAes256Gcm) + DllImportEntry(CryptoNative_EvpAes256WrapPad) DllImportEntry(CryptoNative_EvpChaCha20Poly1305) DllImportEntry(CryptoNative_EvpCipherCreate2) DllImportEntry(CryptoNative_EvpCipherCreatePartial) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 7d0412823d9dab..d5ff0360db3863 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -453,18 +453,21 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_aes_128_cfb8) \ REQUIRED_FUNCTION(EVP_aes_128_ecb) \ REQUIRED_FUNCTION(EVP_aes_128_gcm) \ + REQUIRED_FUNCTION(EVP_aes_128_wrap_pad) \ REQUIRED_FUNCTION(EVP_aes_192_cbc) \ REQUIRED_FUNCTION(EVP_aes_192_ccm) \ REQUIRED_FUNCTION(EVP_aes_192_cfb128) \ REQUIRED_FUNCTION(EVP_aes_192_cfb8) \ REQUIRED_FUNCTION(EVP_aes_192_ecb) \ REQUIRED_FUNCTION(EVP_aes_192_gcm) \ + REQUIRED_FUNCTION(EVP_aes_192_wrap_pad) \ REQUIRED_FUNCTION(EVP_aes_256_cbc) \ REQUIRED_FUNCTION(EVP_aes_256_ccm) \ REQUIRED_FUNCTION(EVP_aes_256_cfb128) \ REQUIRED_FUNCTION(EVP_aes_256_cfb8) \ REQUIRED_FUNCTION(EVP_aes_256_ecb) \ REQUIRED_FUNCTION(EVP_aes_256_gcm) \ + REQUIRED_FUNCTION(EVP_aes_256_wrap_pad) \ LIGHTUP_FUNCTION(EVP_chacha20_poly1305) \ LEGACY_FUNCTION(EVP_CIPHER_CTX_cleanup) \ REQUIRED_FUNCTION(EVP_CIPHER_CTX_ctrl) \ @@ -1025,18 +1028,21 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_aes_128_ecb EVP_aes_128_ecb_ptr #define EVP_aes_128_gcm EVP_aes_128_gcm_ptr #define EVP_aes_128_ccm EVP_aes_128_ccm_ptr +#define EVP_aes_128_wrap_pad EVP_aes_128_wrap_pad_ptr #define EVP_aes_192_cbc EVP_aes_192_cbc_ptr #define EVP_aes_192_cfb8 EVP_aes_192_cfb8_ptr #define EVP_aes_192_cfb128 EVP_aes_192_cfb128_ptr #define EVP_aes_192_ecb EVP_aes_192_ecb_ptr #define EVP_aes_192_gcm EVP_aes_192_gcm_ptr #define EVP_aes_192_ccm EVP_aes_192_ccm_ptr +#define EVP_aes_192_wrap_pad EVP_aes_192_wrap_pad_ptr #define EVP_aes_256_cbc EVP_aes_256_cbc_ptr #define EVP_aes_256_cfb8 EVP_aes_256_cfb8_ptr #define EVP_aes_256_cfb128 EVP_aes_256_cfb128_ptr #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_aes_256_wrap_pad EVP_aes_256_wrap_pad_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 diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c index 4cdfbce6bbcda5..7fca10fb261b04 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c @@ -275,6 +275,12 @@ const EVP_CIPHER* CryptoNative_EvpAes128Ccm(void) return EVP_aes_128_ccm(); } +const EVP_CIPHER* CryptoNative_EvpAes128WrapPad(void) +{ + // No error queue impact. + return EVP_aes_128_wrap_pad(); +} + const EVP_CIPHER* CryptoNative_EvpAes192Ecb(void) { // No error queue impact. @@ -311,6 +317,12 @@ const EVP_CIPHER* CryptoNative_EvpAes192Ccm(void) return EVP_aes_192_ccm(); } +const EVP_CIPHER* CryptoNative_EvpAes192WrapPad(void) +{ + // No error queue impact. + return EVP_aes_192_wrap_pad(); +} + const EVP_CIPHER* CryptoNative_EvpAes256Ecb(void) { // No error queue impact. @@ -347,6 +359,12 @@ const EVP_CIPHER* CryptoNative_EvpAes256Ccm(void) return EVP_aes_256_ccm(); } +const EVP_CIPHER* CryptoNative_EvpAes256WrapPad(void) +{ + // No error queue impact. + return EVP_aes_256_wrap_pad(); +} + const EVP_CIPHER* CryptoNative_EvpDesEcb(void) { // No error queue impact. diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h index 0899cea9dbe3af..d585c3a1d5e5ef 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h @@ -158,6 +158,14 @@ Direct shim to EVP_aes_128_ccm. */ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128Ccm(void); +/* +Function: +EvpAes128WrapPad + +Direct shim to EVP_aes_128_wrap_pad. +*/ +PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128WrapPad(void); + /* Function: EvpAes192Ecb @@ -206,6 +214,14 @@ Direct shim to EVP_aes_192_ccm. */ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192Ccm(void); +/* +Function: +EvpAes192WrapPad + +Direct shim to EVP_aes_192_wrap_pad. +*/ +PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192WrapPad(void); + /* Function: EvpAes256Ecb @@ -254,6 +270,14 @@ Direct shim to EVP_aes_256_ccm. */ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256Ccm(void); +/* +Function: +EvpAes256WrapPad + +Direct shim to EVP_aes_256_wrap_pad. +*/ +PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256WrapPad(void); + /* Function: EvpDes3Ecb From eeee0237a057d5ae08f26ca88a4bed7c560807f7 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 3 Jul 2025 09:03:47 -0700 Subject: [PATCH 03/13] Apply feedback --- .../Cryptography/AesImplementation.OpenSsl.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index daaad0086cab9c..db3f830931e2fe 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -84,6 +84,17 @@ ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), { throw Interop.Crypto.CreateOpenSslCryptographicException(); } + + Debug.Assert(written > 0); + + // Experimentation and code insepection show that EVP_CipherFinal_ex is not needed here, + // the work is done in EVP_CipherUpdate. + // Since AES-KW(P) involves multiple passes over the data, where the end of each pass + // stores a tag/checksum back in the beginning of the buffer, it makes sense that only + // one of Update or Final could write data, and they chose to go with Update. + // + // As the call to Final does not yield more data, and we're about to dispose the context, + // don't bother making the call. } return written; From 904163a10d6f64406391be38e2787f5497d8ff68 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 4 Jul 2025 12:34:23 -0400 Subject: [PATCH 04/13] Set EVP_CIPHER_CTX_FLAG_WRAP_ALLOW for OpenSSL 1.1 This also adds error checking when creating the context. --- .../Security/Cryptography/AesImplementation.OpenSsl.cs | 9 ++++++++- .../System.Security.Cryptography.Native/opensslshim.h | 2 ++ .../System.Security.Cryptography.Native/pal_evp_cipher.c | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index db3f830931e2fe..1549f7827a7beb 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -62,12 +62,19 @@ private int KeyWrap(ReadOnlySpan source, Span destination, int enc) IntPtr algorithm = GetKeyWrapAlgorithm(keySizeInBits); - return Interop.Crypto.EvpCipherCreate( + SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreate( algorithm, ref MemoryMarshal.GetReference(key), key.Length * 8, ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), enc); + + if (ctx.IsInvalid) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + return ctx; }); int written; diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index d5ff0360db3863..48e3a5db691738 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -475,6 +475,7 @@ extern bool g_libSslUses32BitTime; LEGACY_FUNCTION(EVP_CIPHER_CTX_init) \ FALLBACK_FUNCTION(EVP_CIPHER_CTX_new) \ FALLBACK_FUNCTION(EVP_CIPHER_CTX_reset) \ + REQUIRED_FUNCTION(EVP_CIPHER_CTX_set_flags) \ REQUIRED_FUNCTION(EVP_CIPHER_CTX_set_key_length) \ REQUIRED_FUNCTION(EVP_CIPHER_CTX_set_padding) \ RENAMED_FUNCTION(EVP_CIPHER_get_nid, EVP_CIPHER_nid) \ @@ -1050,6 +1051,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_CIPHER_CTX_init EVP_CIPHER_CTX_init_ptr #define EVP_CIPHER_CTX_new EVP_CIPHER_CTX_new_ptr #define EVP_CIPHER_CTX_reset EVP_CIPHER_CTX_reset_ptr +#define EVP_CIPHER_CTX_set_flags EVP_CIPHER_CTX_set_flags_ptr #define EVP_CIPHER_CTX_set_key_length EVP_CIPHER_CTX_set_key_length_ptr #define EVP_CIPHER_CTX_set_padding EVP_CIPHER_CTX_set_padding_ptr #define EVP_CIPHER_get_nid EVP_CIPHER_get_nid_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c index 7fca10fb261b04..d38aaa82599df8 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c @@ -30,6 +30,9 @@ CryptoNative_EvpCipherCreate2(const EVP_CIPHER* type, uint8_t* key, int32_t keyL return NULL; } + // Required for OpenSSL 1.1 AES-KWP, no-op in OpenSSL 3. + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + // Perform partial initialization so we can set the key lengths int ret = EVP_CipherInit_ex(ctx, type, NULL, NULL, NULL, 0); if (!ret) From 52caa3713350763c09cfd3de556472c7326e2160 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 4 Jul 2025 12:42:02 -0400 Subject: [PATCH 05/13] Add Dispose while we are here --- .../System/Security/Cryptography/AesImplementation.OpenSsl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 1549f7827a7beb..6c9437e18d9ef1 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -71,6 +71,7 @@ ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), if (ctx.IsInvalid) { + ctx.Dispose(); throw Interop.Crypto.CreateOpenSslCryptographicException(); } From 65e11697b30e37f726a5f9728d9c6b4b7a101fe1 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 7 Jul 2025 09:50:02 -0700 Subject: [PATCH 06/13] Protect portable with flag value assertion --- .../libs/System.Security.Cryptography.Native/pal_evp_cipher.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c index d38aaa82599df8..17562f965e6fda 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c @@ -8,6 +8,8 @@ #define SUCCESS 1 #define KEEP_CURRENT_DIRECTION -1 +c_static_assert(EVP_CIPHER_CTX_FLAG_WRAP_ALLOW == 1); + EVP_CIPHER_CTX* CryptoNative_EvpCipherCreate2(const EVP_CIPHER* type, uint8_t* key, int32_t keyLength, unsigned char* iv, int32_t enc) { From f6d9563fdf7a8affb14676aa0c2abcdcd25024f8 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 7 Aug 2025 13:44:51 -0400 Subject: [PATCH 07/13] Add OpenSSL version number to output --- .../opensslshim.c | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.c b/src/native/libs/System.Security.Cryptography.Native/opensslshim.c index d99f9fd913bffe..104540b43c58d5 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.c +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.c @@ -158,6 +158,8 @@ static void OpenLibraryOnce(void) static pthread_once_t g_openLibrary = PTHREAD_ONCE_INIT; +typedef unsigned long(*OpenSSL_version_num_ptr_private)(void); + int OpenLibrary(void) { pthread_once(&g_openLibrary, OpenLibraryOnce); @@ -184,6 +186,25 @@ void InitializeOpenSSLShim(void) // libcrypto.so.1.1.0/libssl.so.1.1.0 const void* v1_0_sentinel = dlsym(libssl, "SSL_state"); + if (v1_0_sentinel) + { + printf("OpenSSL version: 1.0.x\n"); + } + else + { + const OpenSSL_version_num_ptr_private opensslnum = (OpenSSL_version_num_ptr_private)dlsym(libssl, "OpenSSL_version_num"); + + if (opensslnum) + { + unsigned long ver = opensslnum(); + printf("OpenSSL version: 0x%lx\n", ver); + } + else + { + printf("OpenSSL version could not be determiend.\n"); + } + } + // Only permit a single assignment here so that two assemblies both triggering the initializer doesn't cause a // race where the fn_ptr is nullptr, then properly bound, then goes back to nullptr right before being used (then bound again). void* volatile tmp_ptr; @@ -234,7 +255,7 @@ void InitializeOpenSSLShim(void) #if defined(TARGET_ARM) && defined(TARGET_LINUX) c_static_assert_msg(sizeof(time_t) == 8, "Build requires 64-bit time_t."); - + // This value will represent a time in year 2038 if 64-bit time is used, // or 1901 if the lower 32 bits are interpreted as a 32-bit time_t value. time_t timeVal = (time_t)0x80000000U; From 49a9c6c348ebd7fd7213451a1198f3d7edcc3de8 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 7 Aug 2025 18:08:28 -0400 Subject: [PATCH 08/13] Revert "Add OpenSSL version number to output" This reverts commit f6d9563fdf7a8affb14676aa0c2abcdcd25024f8. --- .../opensslshim.c | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.c b/src/native/libs/System.Security.Cryptography.Native/opensslshim.c index 104540b43c58d5..d99f9fd913bffe 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.c +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.c @@ -158,8 +158,6 @@ static void OpenLibraryOnce(void) static pthread_once_t g_openLibrary = PTHREAD_ONCE_INIT; -typedef unsigned long(*OpenSSL_version_num_ptr_private)(void); - int OpenLibrary(void) { pthread_once(&g_openLibrary, OpenLibraryOnce); @@ -186,25 +184,6 @@ void InitializeOpenSSLShim(void) // libcrypto.so.1.1.0/libssl.so.1.1.0 const void* v1_0_sentinel = dlsym(libssl, "SSL_state"); - if (v1_0_sentinel) - { - printf("OpenSSL version: 1.0.x\n"); - } - else - { - const OpenSSL_version_num_ptr_private opensslnum = (OpenSSL_version_num_ptr_private)dlsym(libssl, "OpenSSL_version_num"); - - if (opensslnum) - { - unsigned long ver = opensslnum(); - printf("OpenSSL version: 0x%lx\n", ver); - } - else - { - printf("OpenSSL version could not be determiend.\n"); - } - } - // Only permit a single assignment here so that two assemblies both triggering the initializer doesn't cause a // race where the fn_ptr is nullptr, then properly bound, then goes back to nullptr right before being used (then bound again). void* volatile tmp_ptr; @@ -255,7 +234,7 @@ void InitializeOpenSSLShim(void) #if defined(TARGET_ARM) && defined(TARGET_LINUX) c_static_assert_msg(sizeof(time_t) == 8, "Build requires 64-bit time_t."); - + // This value will represent a time in year 2038 if 64-bit time is used, // or 1901 if the lower 32 bits are interpreted as a 32-bit time_t value. time_t timeVal = (time_t)0x80000000U; From 0e79c884268b1708419833aac2723b2cd5599f10 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 12 Feb 2026 11:33:09 -0800 Subject: [PATCH 09/13] Try Unsafe.NullRef instead of ROS.Empty --- .../System/Security/Cryptography/AesImplementation.OpenSsl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 6c9437e18d9ef1..2f896df08e064c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -66,7 +66,7 @@ private int KeyWrap(ReadOnlySpan source, Span destination, int enc) algorithm, ref MemoryMarshal.GetReference(key), key.Length * 8, - ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), + ref Unsafe.NullRef(), enc); if (ctx.IsInvalid) From 5b43f82c73aabe0de95b0bf561f7940c36e7aa99 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 12 Feb 2026 17:23:51 -0800 Subject: [PATCH 10/13] Undo the NullRef experiment (tested locally) --- .../System/Security/Cryptography/AesImplementation.OpenSsl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 2f896df08e064c..6c9437e18d9ef1 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -66,7 +66,7 @@ private int KeyWrap(ReadOnlySpan source, Span destination, int enc) algorithm, ref MemoryMarshal.GetReference(key), key.Length * 8, - ref Unsafe.NullRef(), + ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), enc); if (ctx.IsInvalid) From 602fa2b88095e74e14440c0c48bc248e366a49c1 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 13 Feb 2026 16:33:15 -0500 Subject: [PATCH 11/13] Fix heap corruption when OpenSSL zeros past destination buffer --- .../Cryptography/AesImplementation.OpenSsl.cs | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 6c9437e18d9ef1..16599b1ad9f60a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -82,18 +82,36 @@ ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), using (ctx) { - bool ret = Interop.Crypto.EvpCipherUpdate( - ctx, - destination, - out written, - source); - - if (!ret) + // OpenSSL AES-KWP requires that the destination be at least as large as the source length. + // This is because when there is a depadding failure, it zeros the destination using the source's length. + // https://github.com/openssl/openssl/blob/eb962a78b5cda85b9ff80d2bd5981021a1a7b9cc/crypto/modes/wrap128.c#L296 + // If the destination is too short then it will zero past the buffer on failure. + using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally(source.Length, destination, skipClearIfNotRented: true)) { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } + bool ret = Interop.Crypto.EvpCipherUpdate( + ctx, + lease.Span, + out written, + source); + + if (!ret) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } - Debug.Assert(written > 0); + if (lease.IsRented) + { + if (written > destination.Length) + { + Debug.Fail("Wrote more bytes than expected"); + throw new CryptographicException(); + } + + lease.Span.Slice(0, written).CopyTo(destination); + } + + Debug.Assert(written > 0); + } // Experimentation and code insepection show that EVP_CipherFinal_ex is not needed here, // the work is done in EVP_CipherUpdate. From 88489b43ae20bd5b0cd1298720903915d6dd76c2 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 13 Feb 2026 17:35:24 -0500 Subject: [PATCH 12/13] Even more buffer space! --- .../Cryptography/AesImplementation.OpenSsl.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 16599b1ad9f60a..deeae6e753998d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -82,11 +82,12 @@ ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), using (ctx) { - // OpenSSL AES-KWP requires that the destination be at least as large as the source length. - // This is because when there is a depadding failure, it zeros the destination using the source's length. - // https://github.com/openssl/openssl/blob/eb962a78b5cda85b9ff80d2bd5981021a1a7b9cc/crypto/modes/wrap128.c#L296 - // If the destination is too short then it will zero past the buffer on failure. - using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally(source.Length, destination, skipClearIfNotRented: true)) + // OpenSSL AES-KWP requires that the destination be at least as large as the source length plus the block size. + const int AesBlockSizeBytes = 16; + using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally( + checked(source.Length + AesBlockSizeBytes), + destination, + skipClearIfNotRented: true)) { bool ret = Interop.Crypto.EvpCipherUpdate( ctx, From 378e6a5fdcbcacb7efc8a1539b3c663c29d271cc Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 13 Feb 2026 14:50:55 -0800 Subject: [PATCH 13/13] Update src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System/Security/Cryptography/AesImplementation.OpenSsl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index deeae6e753998d..38c622efb2b1ce 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -114,7 +114,7 @@ ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), Debug.Assert(written > 0); } - // Experimentation and code insepection show that EVP_CipherFinal_ex is not needed here, + // Experimentation and code inspection show that EVP_CipherFinal_ex is not needed here, // the work is done in EVP_CipherUpdate. // Since AES-KW(P) involves multiple passes over the data, where the end of each pass // stores a tag/checksum back in the beginning of the buffer, it makes sense that only