From ae8f7a0ee7d5be0f9002cf6b04d52ac8cab2f8b7 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 12 Apr 2019 18:23:31 -0700 Subject: [PATCH] CipherSuitePolicy implementation (dotnet/corefx#36775) * CipherSuitePolicy implementation (Linux) * SSL_CIPHER_find * do not call TLS1.3 APIs on platforms which don't support it * Non-TLS1.3 specific tests are skipped when not enough cipher suites is enabled * clean ups * attempt to fix OSX * another attempt to fix OSX * missing define * address some feedback, try to fix test failures * portable build fix * do not call old set ciphers API when only TLS 1.3 is requested * apply feedback * add OSX implementation * fixes to OSX * explicit convert * use explicit SSLCipherSuite instead of uint16_t * random change to trigger CI * s/unsafe/fixed * fixes * random change to trigger CI * client ordering does not have to win * tests: AllowedCipherSuites, new CipherSuitesPolicy(null) * run AllowedCipherSuites tests only when CSP is supported * add summary on CipherSuitesPolicy * address feedback * move OS specific files to CipherSuitesPolicyPal * FALLBACK->LIGHTUP and remove local_ * do not call 1.1.1 function on non-portable build when lower openssl version is installed * get rid of warning that arg is unused * make CipherSuitesPolicyPal public members internal Commit migrated from https://github.com/dotnet/corefx/commit/07f443b6c9f27dd050ffb5eb3afa126a2b1bdddd --- .../Interop.Ssl.cs | 3 + .../Interop.OpenSsl.cs | 63 ++- .../Interop.Ssl.cs | 16 + .../Interop.SslCtxOptions.cs | 3 + .../pal_ssl.c | 38 +- .../pal_ssl.h | 11 +- .../opensslshim.h | 25 +- .../System.Security.Cryptography.Native/pal_ssl.c | 355 +++++------------ .../System.Security.Cryptography.Native/pal_ssl.h | 30 +- .../System.Net.Security/ref/System.Net.Security.cs | 9 + .../System.Net.Security/src/Resources/Strings.resx | 3 + .../src/System.Net.Security.csproj | 5 + .../src/System/Net/Security/CipherSuitesPolicy.cs | 41 ++ .../Net/Security/CipherSuitesPolicyPal.Linux.cs | 231 +++++++++++ .../Net/Security/CipherSuitesPolicyPal.OSX.cs | 24 ++ .../Net/Security/CipherSuitesPolicyPal.Windows.cs | 18 + .../Net/Security/Pal.OSX/SafeDeleteSslContext.cs | 27 +- .../Net/Security/SslAuthenticationOptions.cs | 3 + .../Net/Security/SslClientAuthenticationOptions.cs | 8 +- .../Net/Security/SslServerAuthenticationOptions.cs | 7 + .../Security/TlsCipherSuiteNameParser.ttinclude | 15 +- .../SslStreamNegotiatedCipherSuiteTest.cs | 431 ++++++++++++++++++++- 22 files changed, 1059 insertions(+), 307 deletions(-) create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs index ed79d49..d8e591c 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs @@ -146,6 +146,9 @@ internal static partial class Interop [DllImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslGetProtocolVersion")] internal static extern int SslGetProtocolVersion(SafeSslHandle sslHandle, out SslProtocols protocol); + [DllImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslSetEnabledCipherSuites")] + unsafe internal static extern int SslSetEnabledCipherSuites(SafeSslHandle sslHandle, uint* cipherSuites, int numCipherSuites); + internal static void SslSetAcceptClientCert(SafeSslHandle sslHandle) { int osStatus = AppleCryptoNative_SslSetAcceptClientCert(sslHandle); diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index c3c5105..98ccebd 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -64,31 +64,55 @@ internal static partial class Interop throw CreateSslException(SR.net_allocate_ssl_context_failed); } - // TLS 1.3 uses different ciphersuite restrictions than previous versions. - // It has no equivalent to a NoEncryption option. - if (policy == EncryptionPolicy.NoEncryption) + if (!Interop.Ssl.Tls13Supported) + { + if (protocols != SslProtocols.None && + CipherSuitesPolicyPal.WantsTls13(protocols)) + { + protocols = protocols & (~SslProtocols.Tls13); + } + } + else if (CipherSuitesPolicyPal.WantsTls13(protocols) && + CipherSuitesPolicyPal.ShouldOptOutOfTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy)) { if (protocols == SslProtocols.None) { + // we are using default settings but cipher suites policy says that TLS 1.3 + // is not compatible with our settings (i.e. we requested no encryption or disabled + // all TLS 1.3 cipher suites) protocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; } else { - protocols &= ~SslProtocols.Tls13; - - if (protocols == SslProtocols.None) - { - throw new SslException( - SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); - } + // user explicitly asks for TLS 1.3 but their policy is not compatible with TLS 1.3 + throw new SslException( + SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); } } + if (CipherSuitesPolicyPal.ShouldOptOutOfLowerThanTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy)) + { + if (!CipherSuitesPolicyPal.WantsTls13(protocols)) + { + // We cannot provide neither TLS 1.3 or non TLS 1.3, user disabled all cipher suites + throw new SslException( + SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + } + + protocols = SslProtocols.Tls13; + } // Configure allowed protocols. It's ok to use DangerousGetHandle here without AddRef/Release as we just // create the handle, it's rooted by the using, no one else has a reference to it, etc. Ssl.SetProtocolOptions(innerContext.DangerousGetHandle(), protocols); + // Sets policy and security level + if (!Ssl.SetEncryptionPolicy(innerContext, policy)) + { + throw new SslException( + SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + } + // The logic in SafeSslHandle.Disconnect is simple because we are doing a quiet // shutdown (we aren't negotiating for session close to enable later session // restoration). @@ -98,10 +122,23 @@ internal static partial class Interop // https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html Ssl.SslCtxSetQuietShutdown(innerContext); - if (!Ssl.SetEncryptionPolicy(innerContext, policy)) + byte[] cipherList = + CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy); + + byte[] cipherSuites = + CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy); + + unsafe { - Crypto.ErrClearError(); - throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + fixed (byte* cipherListStr = cipherList) + fixed (byte* cipherSuitesStr = cipherSuites) + { + if (!Ssl.SetCiphers(innerContext, cipherListStr, cipherSuitesStr)) + { + Crypto.ErrClearError(); + throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + } + } } bool hasCertificateAndKey = diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs index 4c686b2..323a1b9 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; @@ -135,6 +136,21 @@ internal static partial class Interop [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SslGetCurrentCipherId(SafeSslHandle ssl, out int cipherId); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetOpenSslCipherSuiteName")] + private static extern IntPtr GetOpenSslCipherSuiteName(SafeSslHandle ssl, int cipherSuite, out int isTls12OrLower); + + internal static string GetOpenSslCipherSuiteName(SafeSslHandle ssl, TlsCipherSuite cipherSuite, out bool isTls12OrLower) + { + string ret = Marshal.PtrToStringAnsi(GetOpenSslCipherSuiteName(ssl, (int)cipherSuite, out int isTls12OrLowerInt)); + isTls12OrLower = isTls12OrLowerInt != 0; + return ret; + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Tls13Supported")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool Tls13SupportedImpl(); + internal static readonly bool Tls13Supported = Tls13SupportedImpl(); + internal static SafeSharedX509NameStackHandle SslGetClientCAList(SafeSslHandle ssl) { Crypto.CheckValidOpenSslHandle(ssl); diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs index 4c4721f..f841734 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs @@ -30,6 +30,9 @@ internal static partial class Interop [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetVerify")] internal static extern void SslCtxSetVerify(SafeSslContextHandle ctx, SslCtxSetVerifyCallback callback); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetCiphers")] + internal static unsafe extern bool SetCiphers(SafeSslContextHandle ctx, byte* cipherList, byte* cipherSuites); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetEncryptionPolicy")] internal static extern bool SetEncryptionPolicy(SafeSslContextHandle ctx, EncryptionPolicy policy); } diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c index 6fe52e2..afa97e5 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c @@ -443,12 +443,44 @@ int32_t AppleCryptoNative_SslGetProtocolVersion(SSLContextRef sslContext, PAL_Ss return osStatus; } -int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint32_t* pCipherSuiteOut) +int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint16_t* pCipherSuiteOut) { if (pCipherSuiteOut == NULL) - *pCipherSuiteOut = 0; + { + return errSecParam; + } + + SSLCipherSuite cipherSuite; + OSStatus status = SSLGetNegotiatedCipher(sslContext, &cipherSuite); + *pCipherSuiteOut = (uint16_t)cipherSuite; + + return status; +} + +int32_t AppleCryptoNative_SslSetEnabledCipherSuites(SSLContextRef sslContext, const uint32_t* cipherSuites, int32_t numCipherSuites) +{ + // Max numCipherSuites is 2^16 (all possible cipher suites) + assert(numCipherSuites < (1 << 16)); - return SSLGetNegotiatedCipher(sslContext, pCipherSuiteOut); + if (sizeof(SSLCipherSuite) == sizeof(uint32_t)) + { + // macOS + return SSLSetEnabledCiphers(sslContext, cipherSuites, (size_t)numCipherSuites); + } + else + { + // iOS, tvOS, watchOS + SSLCipherSuite* cipherSuites16 = (SSLCipherSuite*)malloc(sizeof(SSLCipherSuite) * (size_t)numCipherSuites); + for (int i = 0; i < numCipherSuites; i++) + { + cipherSuites16[i] = (SSLCipherSuite)cipherSuites[i]; + } + + OSStatus status = SSLSetEnabledCiphers(sslContext, cipherSuites16, (size_t)numCipherSuites); + + free(cipherSuites16); + return status; + } } __attribute__((constructor)) static void InitializeAppleCryptoSslShim() diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h index 2337b10..e6c922c 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h @@ -230,7 +230,14 @@ Retrieve the TLS Cipher Suite which was negotiated for the current session. Returns the output of SSLGetNegotiatedCipher. Output: -pProtocol: The TLS CipherSuite value (from the RFC), e.g. ((uint32_t)0xC030) for +pProtocol: The TLS CipherSuite value (from the RFC), e.g. ((uint16_t)0xC030) for TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 */ -DLLEXPORT int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint32_t* pCipherSuiteOut); +DLLEXPORT int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint16_t* pCipherSuiteOut); + +/* +Sets enabled cipher suites for the current session. + +Returns the output of SSLSetEnabledCiphers. +*/ +DLLEXPORT int32_t AppleCryptoNative_SslSetEnabledCipherSuites(SSLContextRef sslContext, const uint32_t* cipherSuites, int32_t numCipherSuites); 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 59c0d23..585ff67 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -36,9 +36,16 @@ #include #include "pal_crypto_config.h" +#define OPENSSL_VERSION_1_1_1_RTM 0x10101000L #define OPENSSL_VERSION_1_1_0_RTM 0x10100000L #define OPENSSL_VERSION_1_0_2_RTM 0x10002000L +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_1_1_RTM +#define HAVE_OPENSSL_SET_CIPHERSUITES 1 +#else +#define HAVE_OPENSSL_SET_CIPHERSUITES 0 +#endif + #if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0_RTM // Remove problematic #defines @@ -77,6 +84,14 @@ int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP* group, const EC_POINT* int EC_POINT_set_affine_coordinates_GF2m( const EC_GROUP* group, EC_POINT* p, const BIGNUM* x, const BIGNUM* y, BN_CTX* ctx); #endif + +#if !HAVE_OPENSSL_SET_CIPHERSUITES +#undef HAVE_OPENSSL_SET_CIPHERSUITES +#define HAVE_OPENSSL_SET_CIPHERSUITES 1 +int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); +const SSL_CIPHER* SSL_CIPHER_find(SSL *ssl, const unsigned char *ptr); +#endif + #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_1_0_RTM typedef struct stack_st _STACK; int CRYPTO_add_lock(int* pointer, int amount, int type, const char* file, int line); @@ -422,9 +437,11 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi REQUIRED_FUNCTION(RSA_size) \ REQUIRED_FUNCTION(RSA_up_ref) \ REQUIRED_FUNCTION(RSA_verify) \ - REQUIRED_FUNCTION(SSL_CIPHER_description) \ + LIGHTUP_FUNCTION(SSL_CIPHER_find) \ REQUIRED_FUNCTION(SSL_CIPHER_get_bits) \ REQUIRED_FUNCTION(SSL_CIPHER_get_id) \ + LIGHTUP_FUNCTION(SSL_CIPHER_get_name) \ + LIGHTUP_FUNCTION(SSL_CIPHER_get_version) \ REQUIRED_FUNCTION(SSL_ctrl) \ REQUIRED_FUNCTION(SSL_set_quiet_shutdown) \ REQUIRED_FUNCTION(SSL_CTX_check_private_key) \ @@ -436,6 +453,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi LIGHTUP_FUNCTION(SSL_CTX_set_alpn_select_cb) \ REQUIRED_FUNCTION(SSL_CTX_set_cert_verify_callback) \ REQUIRED_FUNCTION(SSL_CTX_set_cipher_list) \ + LIGHTUP_FUNCTION(SSL_CTX_set_ciphersuites) \ REQUIRED_FUNCTION(SSL_CTX_set_client_cert_cb) \ REQUIRED_FUNCTION(SSL_CTX_set_quiet_shutdown) \ FALLBACK_FUNCTION(SSL_CTX_set_options) \ @@ -810,8 +828,10 @@ FOR_ALL_OPENSSL_FUNCTIONS #define sk_push OPENSSL_sk_push_ptr #define sk_value OPENSSL_sk_value_ptr #define SSL_CIPHER_get_bits SSL_CIPHER_get_bits_ptr -#define SSL_CIPHER_description SSL_CIPHER_description_ptr +#define SSL_CIPHER_find SSL_CIPHER_find_ptr #define SSL_CIPHER_get_id SSL_CIPHER_get_id_ptr +#define SSL_CIPHER_get_name SSL_CIPHER_get_name_ptr +#define SSL_CIPHER_get_version SSL_CIPHER_get_version_ptr #define SSL_ctrl SSL_ctrl_ptr #define SSL_set_quiet_shutdown SSL_set_quiet_shutdown_ptr #define SSL_CTX_check_private_key SSL_CTX_check_private_key_ptr @@ -822,6 +842,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_CTX_set_alpn_select_cb SSL_CTX_set_alpn_select_cb_ptr #define SSL_CTX_set_cert_verify_callback SSL_CTX_set_cert_verify_callback_ptr #define SSL_CTX_set_cipher_list SSL_CTX_set_cipher_list_ptr +#define SSL_CTX_set_ciphersuites SSL_CTX_set_ciphersuites_ptr #define SSL_CTX_set_client_cert_cb SSL_CTX_set_client_cert_cb_ptr #define SSL_CTX_set_options SSL_CTX_set_options_ptr #define SSL_CTX_set_quiet_shutdown SSL_CTX_set_quiet_shutdown_ptr diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c index fec8453..fd7815c 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c @@ -229,247 +229,6 @@ int32_t CryptoNative_SslSessionReused(SSL* ssl) return SSL_session_reused(ssl) == 1; } -static bool StringSpanEquals(const char* lhs, const char* rhs, size_t lhsLength) -{ - if (lhsLength != strlen(rhs)) - { - return false; - } - - return strncmp(lhs, rhs, lhsLength) == 0; -} - -static CipherAlgorithmType MapCipherAlgorithmType(const char* encryption, size_t encryptionLength) -{ - if (StringSpanEquals(encryption, "DES(56)", encryptionLength)) - return Des; - if (StringSpanEquals(encryption, "3DES(168)", encryptionLength)) - return TripleDes; - if (StringSpanEquals(encryption, "RC4(128)", encryptionLength)) - return Rc4; - if (StringSpanEquals(encryption, "RC2(128)", encryptionLength)) - return Rc2; - if (StringSpanEquals(encryption, "None", encryptionLength)) - return Null; - if (StringSpanEquals(encryption, "IDEA(128)", encryptionLength)) - return SSL_IDEA; - if (StringSpanEquals(encryption, "SEED(128)", encryptionLength)) - return SSL_SEED; - if (StringSpanEquals(encryption, "AES(128)", encryptionLength)) - return Aes128; - if (StringSpanEquals(encryption, "AES(256)", encryptionLength)) - return Aes256; - if (StringSpanEquals(encryption, "Camellia(128)", encryptionLength)) - return SSL_CAMELLIA128; - if (StringSpanEquals(encryption, "Camellia(256)", encryptionLength)) - return SSL_CAMELLIA256; - if (StringSpanEquals(encryption, "GOST89(256)", encryptionLength)) - return SSL_eGOST2814789CNT; - if (StringSpanEquals(encryption, "AESGCM(128)", encryptionLength)) - return Aes128; - if (StringSpanEquals(encryption, "AESGCM(256)", encryptionLength)) - return Aes256; - - return CipherAlgorithmType_None; -} - -static ExchangeAlgorithmType MapExchangeAlgorithmType(const char* keyExchange, size_t keyExchangeLength) -{ - if (StringSpanEquals(keyExchange, "RSA", keyExchangeLength)) - return RsaKeyX; - if (StringSpanEquals(keyExchange, "DH/RSA", keyExchangeLength)) - return DiffieHellman; - if (StringSpanEquals(keyExchange, "DH/DSS", keyExchangeLength)) - return DiffieHellman; - if (StringSpanEquals(keyExchange, "DH", keyExchangeLength)) - return DiffieHellman; - if (StringSpanEquals(keyExchange, "KRB5", keyExchangeLength)) - return SSL_kKRB5; - if (StringSpanEquals(keyExchange, "ECDH", keyExchangeLength)) - return SSL_ECDHE; - if (StringSpanEquals(keyExchange, "ECDH/RSA", keyExchangeLength)) - return SSL_ECDH; - if (StringSpanEquals(keyExchange, "ECDH/ECDSA", keyExchangeLength)) - return SSL_ECDSA; - if (StringSpanEquals(keyExchange, "PSK", keyExchangeLength)) - return SSL_kPSK; - if (StringSpanEquals(keyExchange, "GOST", keyExchangeLength)) - return SSL_kGOST; - if (StringSpanEquals(keyExchange, "SRP", keyExchangeLength)) - return SSL_kSRP; - - return ExchangeAlgorithmType_None; -} - -static void GetHashAlgorithmTypeAndSize(const char* mac, - size_t macLength, - HashAlgorithmType* dataHashAlg, - DataHashSize* hashKeySize) -{ - if (StringSpanEquals(mac, "MD5", macLength)) - { - *dataHashAlg = Md5; - *hashKeySize = MD5_HashKeySize; - return; - } - if (StringSpanEquals(mac, "SHA1", macLength)) - { - *dataHashAlg = Sha1; - *hashKeySize = SHA1_HashKeySize; - return; - } - if (StringSpanEquals(mac, "GOST94", macLength)) - { - *dataHashAlg = SSL_GOST94; - *hashKeySize = GOST_HashKeySize; - return; - } - if (StringSpanEquals(mac, "GOST89", macLength)) - { - *dataHashAlg = SSL_GOST89; - *hashKeySize = GOST_HashKeySize; - return; - } - if (StringSpanEquals(mac, "SHA256", macLength)) - { - *dataHashAlg = SSL_SHA256; - *hashKeySize = SHA256_HashKeySize; - return; - } - if (StringSpanEquals(mac, "SHA384", macLength)) - { - *dataHashAlg = SSL_SHA384; - *hashKeySize = SHA384_HashKeySize; - return; - } - if (StringSpanEquals(mac, "AEAD", macLength)) - { - *dataHashAlg = SSL_AEAD; - *hashKeySize = Default; - return; - } - - *dataHashAlg = HashAlgorithmType_None; - *hashKeySize = Default; -} - -/* -Given a keyName string like "Enc=XXX", parses the description string and returns the -'XXX' into value and valueLength return variables. - -Returns a value indicating whether the pattern starting with keyName was found in description. -*/ -static bool GetDescriptionValue( - const char* description, const char* keyName, size_t keyNameLength, const char** value, size_t* valueLength) -{ - // search for keyName in description - const char* keyNameStart = strstr(description, keyName); - if (keyNameStart != NULL) - { - // set valueStart to the beginning of the value - const char* valueStart = keyNameStart + keyNameLength; - size_t index = 0; - - // the value ends when we hit a space or the end of the string - while (valueStart[index] != ' ' && valueStart[index] != '\0') - { - index++; - } - - *value = valueStart; - *valueLength = index; - return true; - } - - return false; -} - -#define descriptionLength 256 - -/* -Parses the Kx, Enc, and Mac values out of the SSL_CIPHER_description and -maps the values to the corresponding .NET enum value. -*/ -static bool GetSslConnectionInfoFromDescription(const SSL_CIPHER* cipher, - CipherAlgorithmType* dataCipherAlg, - ExchangeAlgorithmType* keyExchangeAlg, - HashAlgorithmType* dataHashAlg, - DataHashSize* hashKeySize) -{ - char description[descriptionLength] = { 0 }; - SSL_CIPHER_description(cipher, description, descriptionLength - 1); // ensure description is NULL-terminated - - const char* keyExchange; - size_t keyExchangeLength; - if (!GetDescriptionValue(description, "Kx=", 3, &keyExchange, &keyExchangeLength)) - { - return false; - } - - const char* encryption; - size_t encryptionLength; - if (!GetDescriptionValue(description, "Enc=", 4, &encryption, &encryptionLength)) - { - return false; - } - - const char* mac; - size_t macLength; - if (!GetDescriptionValue(description, "Mac=", 4, &mac, &macLength)) - { - return false; - } - - *keyExchangeAlg = MapExchangeAlgorithmType(keyExchange, keyExchangeLength); - *dataCipherAlg = MapCipherAlgorithmType(encryption, encryptionLength); - GetHashAlgorithmTypeAndSize(mac, macLength, dataHashAlg, hashKeySize); - return true; -} - -int32_t CryptoNative_GetSslConnectionInfo(SSL* ssl, - CipherAlgorithmType* dataCipherAlg, - ExchangeAlgorithmType* keyExchangeAlg, - HashAlgorithmType* dataHashAlg, - int32_t* dataKeySize, - DataHashSize* hashKeySize) -{ - const SSL_CIPHER* cipher; - - if (!ssl || !dataCipherAlg || !keyExchangeAlg || !dataHashAlg || !dataKeySize || !hashKeySize) - { - goto err; - } - - cipher = SSL_get_current_cipher(ssl); - if (!cipher) - { - goto err; - } - - SSL_CIPHER_get_bits(cipher, dataKeySize); - - if (GetSslConnectionInfoFromDescription(cipher, dataCipherAlg, keyExchangeAlg, dataHashAlg, hashKeySize)) - { - return 1; - } - -err: - assert(false); - - if (dataCipherAlg) - *dataCipherAlg = CipherAlgorithmType_None; - if (keyExchangeAlg) - *keyExchangeAlg = ExchangeAlgorithmType_None; - if (dataHashAlg) - *dataHashAlg = HashAlgorithmType_None; - if (dataKeySize) - *dataKeySize = 0; - if (hashKeySize) - *hashKeySize = Default; - - return 0; -} - int32_t CryptoNative_SslWrite(SSL* ssl, const void* buf, int32_t num) { return SSL_write(ssl, buf, num); @@ -560,46 +319,110 @@ CryptoNative_SslCtxSetCertVerifyCallback(SSL_CTX* ctx, SslCtxSetCertVerifyCallba SSL_CTX_set_cert_verify_callback(ctx, callback, arg); } -// delimiter ":" is used to allow more than one strings -// below string is corresponding to "AllowNoEncryption" -#define SSL_TXT_Separator ":" -#define SSL_TXT_Exclusion "!" -#define SSL_TXT_AllIncludingNull SSL_TXT_ALL SSL_TXT_Separator SSL_TXT_eNULL -#define SSL_TXT_NotAnon SSL_TXT_Separator SSL_TXT_Exclusion SSL_TXT_aNULL - int32_t CryptoNative_SetEncryptionPolicy(SSL_CTX* ctx, EncryptionPolicy policy) { - const char* cipherString = NULL; - bool clearSecLevel = false; - switch (policy) { + case AllowNoEncryption: + case NoEncryption: + // No minimum security policy, same as OpenSSL 1.0 + SSL_CTX_set_security_level(ctx, 0); + return true; case RequireEncryption: - cipherString = SSL_TXT_ALL SSL_TXT_NotAnon; - break; + return true; + } - case AllowNoEncryption: - cipherString = SSL_TXT_AllIncludingNull; - clearSecLevel = true; - break; + return false; +} - case NoEncryption: - cipherString = SSL_TXT_eNULL; - clearSecLevel = true; - break; +int32_t CryptoNative_SetCiphers(SSL_CTX* ctx, const char* cipherList, const char* cipherSuites) +{ + int32_t ret = true; + + // for < TLS 1.3 + if (cipherList != NULL) + { + ret &= SSL_CTX_set_cipher_list(ctx, cipherList); + if (!ret) + { + return ret; + } + } + + // for TLS 1.3 +#if HAVE_OPENSSL_SET_CIPHERSUITES + if (CryptoNative_Tls13Supported() && cipherSuites != NULL) + { + ret &= SSL_CTX_set_ciphersuites(ctx, cipherSuites); } +#else + (void)cipherSuites; +#endif + + return ret; +} + +const char* CryptoNative_GetOpenSslCipherSuiteName(SSL* ssl, int32_t cipherSuite, int32_t* isTls12OrLower) +{ + unsigned char cs[2]; + const SSL_CIPHER* cipher; + const char* ret; + + *isTls12OrLower = 0; + cs[0] = (cipherSuite >> 8) & 0xFF; + cs[1] = cipherSuite & 0xFF; + cipher = SSL_CIPHER_find(ssl, cs); + + if (cipher == NULL) + return NULL; - assert(cipherString != NULL); + ret = SSL_CIPHER_get_name(cipher); - if (clearSecLevel) + if (ret == NULL) + return NULL; + + // we should get (NONE) only when cipher is NULL + assert(strcmp("(NONE)", ret) != 0); + + const char* version = SSL_CIPHER_get_version(cipher); + assert(version != NULL); + assert(strcmp(version, "unknown") != 0); + + // same rules apply for DTLS as for TLS so just shortcut + if (version[0] == 'D') + { + version++; + } + + // check if tls1.2 or lower + // check most common case first + if (strncmp("TLSv1", version, 5) == 0) + { + const char* tlsver = version + 5; + // true for TLSv1, TLSv1.0, TLSv1.1, TLS1.2, anything else is assumed to be newer + *isTls12OrLower = + tlsver[0] == 0 || + (tlsver[0] == '.' && tlsver[1] >= '0' && tlsver[1] <= '2' && tlsver[2] == 0); + } + else { - // No minimum security policy, same as OpenSSL 1.0 - SSL_CTX_set_security_level(ctx, 0); + // if we don't know it assume it is new + // worst case scenario OpenSSL will ignore it + *isTls12OrLower = + strncmp("SSLv", version, 4) == 0; } - return SSL_CTX_set_cipher_list(ctx, cipherString); + return ret; } +int32_t CryptoNative_Tls13Supported() +{ +#if HAVE_OPENSSL_SET_CIPHERSUITES + return API_EXISTS(SSL_CTX_set_ciphersuites); +#else + return false; +#endif +} void CryptoNative_SslCtxSetClientCertCallback(SSL_CTX* ctx, SslClientCertCallback callback) { diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h index 038e5de..2db6e76 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h @@ -205,19 +205,6 @@ Returns the protocol version string for the SSL instance. DLLEXPORT const char* CryptoNative_SslGetVersion(SSL* ssl); /* -Returns the connection information for the SSL instance. - -Returns 1 upon success, otherwise 0. -*/ - -DLLEXPORT int32_t CryptoNative_GetSslConnectionInfo(SSL* ssl, - CipherAlgorithmType* dataCipherAlg, - ExchangeAlgorithmType* keyExchangeAlg, - HashAlgorithmType* dataHashAlg, - int32_t* dataKeySize, - DataHashSize* hashKeySize); - -/* Shims the SSL_write method. Returns the positive number of bytes written when successful, 0 or a negative number @@ -338,11 +325,20 @@ CryptoNative_SslCtxSetCertVerifyCallback(SSL_CTX* ctx, SslCtxSetCertVerifyCallba /* Sets the specified encryption policy on the SSL_CTX. -Returns 1 if any cipher could be selected, and 0 if none were available. */ DLLEXPORT int32_t CryptoNative_SetEncryptionPolicy(SSL_CTX* ctx, EncryptionPolicy policy); /* +Sets ciphers (< TLS 1.3) and cipher suites (TLS 1.3) on the SSL_CTX +*/ +DLLEXPORT int32_t CryptoNative_SetCiphers(SSL_CTX* ctx, const char* cipherList, const char* cipherSuites); + +/* +Determines if TLS 1.3 is supported by this OpenSSL implementation +*/ +DLLEXPORT int32_t CryptoNative_Tls13Supported(void); + +/* Shims the SSL_CTX_set_client_cert_cb method */ DLLEXPORT void CryptoNative_SslCtxSetClientCertCallback(SSL_CTX* ctx, SslClientCertCallback callback); @@ -396,3 +392,9 @@ DLLEXPORT int32_t CryptoNative_SslSetTlsExtHostName(SSL* ssl, uint8_t* name); Shims the SSL_get_current_cipher and SSL_CIPHER_get_id. */ DLLEXPORT int32_t CryptoNative_SslGetCurrentCipherId(SSL* ssl, int32_t* cipherId); + +/* +Looks up a cipher by the IANA identifier, returns a shared string for the OpenSSL name for the cipher, +and emits a value indicating if the cipher belongs to the SSL2-TLS1.2 list, or the TLS1.3+ list. +*/ +DLLEXPORT const char* CryptoNative_GetOpenSslCipherSuiteName(SSL* ssl, int32_t cipherSuite, int32_t* isTls12OrLower); diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.cs b/src/libraries/System.Net.Security/ref/System.Net.Security.cs index 01cff41..eec818d 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs @@ -117,6 +117,7 @@ namespace System.Net.Security public bool AllowRenegotiation { get { throw null; } set { } } public System.Collections.Generic.List ApplicationProtocols { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509RevocationMode CertificateRevocationCheckMode { get { throw null; } set { } } + public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get { throw null; } set { } } public System.Security.Authentication.SslProtocols EnabledSslProtocols { get { throw null; } set { } } public System.Net.Security.EncryptionPolicy EncryptionPolicy { get { throw null; } set { } } @@ -130,6 +131,7 @@ namespace System.Net.Security public bool AllowRenegotiation { get { throw null; } set { } } public System.Collections.Generic.List ApplicationProtocols { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509RevocationMode CertificateRevocationCheckMode { get { throw null; } set { } } + public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get { throw null; } set { } } public bool ClientCertificateRequired { get { throw null; } set { } } public System.Security.Authentication.SslProtocols EnabledSslProtocols { get { throw null; } set { } } public System.Net.Security.EncryptionPolicy EncryptionPolicy { get { throw null; } set { } } @@ -213,6 +215,13 @@ namespace System.Net.Security public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } + public sealed class CipherSuitesPolicy + { + [System.CLSCompliantAttribute(false)] + public CipherSuitesPolicy(System.Collections.Generic.IEnumerable allowedCipherSuites) { } + [System.CLSCompliantAttribute(false)] + public System.Collections.Generic.IEnumerable AllowedCipherSuites { get; } + } [System.CLSCompliantAttribute(false)] public enum TlsCipherSuite : ushort { diff --git a/src/libraries/System.Net.Security/src/Resources/Strings.resx b/src/libraries/System.Net.Security/src/Resources/Strings.resx index 40c7686..7270ea8 100644 --- a/src/libraries/System.Net.Security/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Security/src/Resources/Strings.resx @@ -437,4 +437,7 @@ The '{0}' option was already set in the SslStream constructor. + + CipherSuitesPolicy is not supported on this platform. + diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index ec3a4f7..d01de81 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -23,6 +23,7 @@ + @@ -142,6 +143,7 @@ + @@ -318,6 +320,7 @@ + @@ -437,6 +440,7 @@ + @@ -446,6 +450,7 @@ + diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.cs b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.cs new file mode 100644 index 0000000..b07f85b --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Net.Security +{ + /// + /// Specifies allowed cipher suites. + /// + public sealed partial class CipherSuitesPolicy + { + internal CipherSuitesPolicyPal Pal { get; private set; } + + [CLSCompliant(false)] + public CipherSuitesPolicy(IEnumerable allowedCipherSuites) + { + if (allowedCipherSuites == null) + { + throw new ArgumentNullException(nameof(allowedCipherSuites)); + } + + Pal = new CipherSuitesPolicyPal(allowedCipherSuites); + } + + [CLSCompliant(false)] + public IEnumerable AllowedCipherSuites + { + get + { + // This method is only useful only for diagnostic purposes so perf is not important + // We do not want users to be able to cast result to something they can modify + foreach (TlsCipherSuite cs in Pal.GetCipherSuites()) + { + yield return cs; + } + } + } + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs new file mode 100644 index 0000000..a0044b7 --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs @@ -0,0 +1,231 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security.Authentication; +using System.Text; +using Ssl = Interop.Ssl; +using OpenSsl = Interop.OpenSsl; + +namespace System.Net.Security +{ + internal class CipherSuitesPolicyPal + { + private static readonly byte[] RequireEncryptionDefault = + Encoding.ASCII.GetBytes("DEFAULT\0"); + + private static readonly byte[] AllowNoEncryptionDefault = + Encoding.ASCII.GetBytes("ALL:eNULL\0"); + + private static readonly byte[] NoEncryptionDefault = + Encoding.ASCII.GetBytes("eNULL\0"); + + private byte[] _cipherSuites; + private byte[] _tls13CipherSuites; + private List _tlsCipherSuites = new List(); + + internal IEnumerable GetCipherSuites() => _tlsCipherSuites; + + internal CipherSuitesPolicyPal(IEnumerable allowedCipherSuites) + { + if (!Interop.Ssl.Tls13Supported) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + using (SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method)) + { + if (innerContext.IsInvalid) + { + throw OpenSsl.CreateSslException(SR.net_allocate_ssl_context_failed); + } + + using (SafeSslHandle ssl = SafeSslHandle.Create(innerContext, false)) + { + if (ssl.IsInvalid) + { + throw OpenSsl.CreateSslException(SR.net_allocate_ssl_context_failed); + } + + using (var tls13CipherSuites = new OpenSslStringBuilder()) + using (var cipherSuites = new OpenSslStringBuilder()) + { + foreach (TlsCipherSuite cs in allowedCipherSuites) + { + string name = Interop.Ssl.GetOpenSslCipherSuiteName( + ssl, + cs, + out bool isTls12OrLower); + + if (name == null) + { + // we do not have a corresponding name + // allowing less than user requested is OK + continue; + } + + _tlsCipherSuites.Add(cs); + (isTls12OrLower ? cipherSuites : tls13CipherSuites).AllowCipherSuite(name); + } + + _cipherSuites = cipherSuites.GetOpenSslString(); + _tls13CipherSuites = tls13CipherSuites.GetOpenSslString(); + } + } + } + } + + internal static bool ShouldOptOutOfTls13(CipherSuitesPolicy policy, EncryptionPolicy encryptionPolicy) + { + // if TLS 1.3 was explicitly requested the underlying code will throw + // if default option (SslProtocols.None) is used we will opt-out of TLS 1.3 + + if (encryptionPolicy == EncryptionPolicy.NoEncryption) + { + // TLS 1.3 uses different ciphersuite restrictions than previous versions. + // It has no equivalent to a NoEncryption option. + return true; + } + + if (policy == null) + { + // null means default, by default OpenSSL will choose if it wants to opt-out or not + return false; + } + + Debug.Assert( + policy.Pal._tls13CipherSuites.Length != 0 && + policy.Pal._tls13CipherSuites[policy.Pal._tls13CipherSuites.Length - 1] == 0, + "null terminated string expected"); + + // we should opt out only when policy is empty + return policy.Pal._tls13CipherSuites.Length == 1; + } + + internal static bool ShouldOptOutOfLowerThanTls13(CipherSuitesPolicy policy, EncryptionPolicy encryptionPolicy) + { + if (policy == null) + { + // null means default, by default OpenSSL will choose if it wants to opt-out or not + return false; + } + + Debug.Assert( + policy.Pal._cipherSuites.Length != 0 && + policy.Pal._cipherSuites[policy.Pal._cipherSuites.Length - 1] == 0, + "null terminated string expected"); + + // we should opt out only when policy is empty + return policy.Pal._cipherSuites.Length == 1; + } + + private static bool IsOnlyTls13(SslProtocols protocols) + => protocols == SslProtocols.Tls13; + + internal static bool WantsTls13(SslProtocols protocols) + => protocols == SslProtocols.None || (protocols & SslProtocols.Tls13) != 0; + + internal static byte[] GetOpenSslCipherList( + CipherSuitesPolicy policy, + SslProtocols protocols, + EncryptionPolicy encryptionPolicy) + { + if (IsOnlyTls13(protocols)) + { + // older cipher suites will be disabled through protocols + return null; + } + + if (policy == null) + { + return CipherListFromEncryptionPolicy(encryptionPolicy); + } + + if (encryptionPolicy == EncryptionPolicy.NoEncryption) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + return policy.Pal._cipherSuites; + } + + internal static byte[] GetOpenSslCipherSuites( + CipherSuitesPolicy policy, + SslProtocols protocols, + EncryptionPolicy encryptionPolicy) + { + if (!WantsTls13(protocols) || policy == null) + { + // do not call TLS 1.3 API, let OpenSSL choose what to do + return null; + } + + if (encryptionPolicy == EncryptionPolicy.NoEncryption) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + return policy.Pal._tls13CipherSuites; + } + + private static byte[] CipherListFromEncryptionPolicy(EncryptionPolicy policy) + { + switch (policy) + { + case EncryptionPolicy.RequireEncryption: + return RequireEncryptionDefault; + case EncryptionPolicy.AllowNoEncryption: + return AllowNoEncryptionDefault; + case EncryptionPolicy.NoEncryption: + return NoEncryptionDefault; + default: + Debug.Fail($"Unknown EncryptionPolicy value ({policy})"); + return null; + } + } + + private class OpenSslStringBuilder : StreamWriter + { + private const string SSL_TXT_Separator = ":"; + private static readonly byte[] EmptyString = new byte[1] { 0 }; + + private MemoryStream _ms; + private bool _first = true; + + public OpenSslStringBuilder() : base(new MemoryStream(), Encoding.ASCII) + { + _ms = (MemoryStream)BaseStream; + } + + public void AllowCipherSuite(string cipherSuite) + { + if (_first) + { + _first = false; + } + else + { + Write(SSL_TXT_Separator); + } + + Write(cipherSuite); + } + + public byte[] GetOpenSslString() + { + if (_first) + { + return EmptyString; + } + + Flush(); + _ms.WriteByte(0); + return _ms.ToArray(); + } + } + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.cs new file mode 100644 index 0000000..608364f --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Net.Security +{ + internal class CipherSuitesPolicyPal + { + internal uint[] TlsCipherSuites { get; private set; } + + internal CipherSuitesPolicyPal(IEnumerable allowedCipherSuites) + { + TlsCipherSuites = allowedCipherSuites.Select((cs) => (uint)cs).ToArray(); + } + + internal IEnumerable GetCipherSuites() + { + return TlsCipherSuites.Select((cs) => (TlsCipherSuite)cs); + } + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs new file mode 100644 index 0000000..9d0d614 --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Net.Security +{ + internal class CipherSuitesPolicyPal + { + internal CipherSuitesPolicyPal(IEnumerable allowedCipherSuites) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + internal IEnumerable GetCipherSuites() => null; + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs index cba6225..82a273a 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs @@ -29,6 +29,8 @@ namespace System.Net try { + int osStatus; + unsafe { _readCallback = ReadFromConnection; @@ -37,7 +39,7 @@ namespace System.Net _sslContext = CreateSslContext(credential, sslAuthenticationOptions.IsServer); - int osStatus = Interop.AppleCrypto.SslSetIoCallbacks( + osStatus = Interop.AppleCrypto.SslSetIoCallbacks( _sslContext, _readCallback, _writeCallback); @@ -47,6 +49,27 @@ namespace System.Net throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); } + if (sslAuthenticationOptions.CipherSuitesPolicy != null) + { + uint[] tlsCipherSuites = sslAuthenticationOptions.CipherSuitesPolicy.Pal.TlsCipherSuites; + + unsafe + { + fixed (uint* cipherSuites = tlsCipherSuites) + { + osStatus = Interop.AppleCrypto.SslSetEnabledCipherSuites( + _sslContext, + cipherSuites, + tlsCipherSuites.Length); + + if (osStatus != 0) + { + throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); + } + } + } + } + if (sslAuthenticationOptions.ApplicationProtocols != null) { // On OSX coretls supports only client side. For server, we will silently ignore the option. @@ -314,7 +337,7 @@ namespace System.Net // If we didn't find an unset protocol after the min, go all the way to the last one. if (maxProtocolId == (SslProtocols)(-1)) { - maxProtocolId = orderedSslProtocols[orderedSslProtocols.Length - 1]; + maxProtocolId = orderedSslProtocols[orderedSslProtocols.Length - 1]; } // Finally set this min and max. diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index 7b64a7f..13fd8be 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -27,6 +27,7 @@ namespace System.Net.Security CertSelectionDelegate = localCallback; CertificateRevocationCheckMode = sslClientAuthenticationOptions.CertificateRevocationCheckMode; ClientCertificates = sslClientAuthenticationOptions.ClientCertificates; + CipherSuitesPolicy = sslClientAuthenticationOptions.CipherSuitesPolicy; } internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions) @@ -48,6 +49,7 @@ namespace System.Net.Security // Server specific options. CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode; ServerCertificate = sslServerAuthenticationOptions.ServerCertificate; + CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy; } internal bool AllowRenegotiation { get; set; } @@ -64,6 +66,7 @@ namespace System.Net.Security internal RemoteCertValidationCallback CertValidationDelegate { get; set; } internal LocalCertSelectionCallback CertSelectionDelegate { get; set; } internal ServerCertSelectionCallback ServerCertSelectionDelegate { get; set; } + internal CipherSuitesPolicy CipherSuitesPolicy { get; set; } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs index bf68f1d..bffa7d4 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs @@ -65,6 +65,12 @@ namespace System.Net.Security get => _enabledSslProtocols; set => _enabledSslProtocols = value; } + + /// + /// Specifies cipher suites allowed to be used for TLS. + /// When set to null operating system default will be used. + /// Use extreme caution when changing this setting. + /// + public CipherSuitesPolicy CipherSuitesPolicy { get; set; } } } - diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs index 5b3935b..614dff0 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs @@ -64,6 +64,13 @@ namespace System.Net.Security _encryptionPolicy = value; } } + + /// + /// Specifies cipher suites allowed to be used for TLS. + /// When set to null operating system default will be used. + /// Use extreme caution when changing this setting. + /// + public CipherSuitesPolicy CipherSuitesPolicy { get; set; } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude b/src/libraries/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude index 2d510ef..65dd43c 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude +++ b/src/libraries/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude @@ -135,7 +135,20 @@ internal static class EnumHelpers public static string ToFrameworkName(HashAlgorithmType val) { - return val == HashAlgorithmType.Aead ? "None" : val.ToString(); + switch (val) + { + case HashAlgorithmType.Aead: + case HashAlgorithmType.None: + return "None"; + case HashAlgorithmType.Md5: + case HashAlgorithmType.Sha1: + case HashAlgorithmType.Sha256: + case HashAlgorithmType.Sha384: + case HashAlgorithmType.Sha512: + return val.ToString(); + default: + throw new Exception($"Value `HashAlgorithmType.{val}` does not have framework corresponding name. See TlsCipherSuiteNameParser.ttinclude: ToFrameworkName."); + } } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs index 4a249dd..18c77ff 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Net.Test.Common; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Net.Security.Tests @@ -18,20 +20,44 @@ namespace System.Net.Security.Tests public class NegotiatedCipherSuiteTest { +#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete + private const SslProtocols AllProtocols = + SslProtocols.Ssl2 | SslProtocols.Ssl3 | + SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; +#pragma warning restore CS0618 + + private const SslProtocols NonTls13Protocols = AllProtocols & (~SslProtocols.Tls13); + private static bool IsKnownPlatformSupportingTls13 => PlatformDetection.IsUbuntu1810OrHigher; - private static bool Tls13Supported { get; set; } = ProtocolsSupported(SslProtocols.Tls13); + private static bool CipherSuitesPolicySupported => s_cipherSuitePolicySupported.Value; + private static bool Tls13Supported { get; set; } = IsKnownPlatformSupportingTls13 || ProtocolsSupported(SslProtocols.Tls13); + private static bool CipherSuitesPolicyAndTls13Supported => Tls13Supported && CipherSuitesPolicySupported; private static HashSet s_tls13CipherSuiteLookup = new HashSet(GetTls13CipherSuites()); private static HashSet s_tls12CipherSuiteLookup = new HashSet(GetTls12CipherSuites()); private static HashSet s_tls10And11CipherSuiteLookup = new HashSet(GetTls10And11CipherSuites()); - private static Dictionary> _protocolCipherSuiteLookup = new Dictionary>() + private static Dictionary> s_protocolCipherSuiteLookup = new Dictionary>() { { SslProtocols.Tls12, s_tls12CipherSuiteLookup }, { SslProtocols.Tls11, s_tls10And11CipherSuiteLookup }, { SslProtocols.Tls, s_tls10And11CipherSuiteLookup }, }; + private static Lazy s_cipherSuitePolicySupported = new Lazy(() => + { + try + { + new CipherSuitesPolicy(Array.Empty()); + return true; + } + catch (PlatformNotSupportedException) { } + + return false; + }); + + private static IReadOnlyList SupportedNonTls13CipherSuites = GetSupportedNonTls13CipherSuites(); + [ConditionalFact(nameof(IsKnownPlatformSupportingTls13))] public void Tls13IsSupported_GetValue_ReturnsTrue() { @@ -70,7 +96,7 @@ namespace System.Net.Security.Tests ret.Succeeded(); Assert.True( - _protocolCipherSuiteLookup[protocol].Contains(ret.CipherSuite), + s_protocolCipherSuiteLookup[protocol].Contains(ret.CipherSuite), $"`{ret.CipherSuite}` is not recognized as {protocol} cipher suite"); } @@ -84,6 +110,366 @@ namespace System.Net.Security.Tests } } + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowSomeCipherSuitesWithNoEncryptionOption_Fails() + { + CheckPrereqsForNonTls13Tests(1); + var p = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(TlsCipherSuite.TLS_AES_128_GCM_SHA256, + SupportedNonTls13CipherSuites[0]), + EncryptionPolicy = EncryptionPolicy.NoEncryption, + }; + + NegotiatedParams ret = ConnectAndGetNegotiatedParams(p, p); + ret.Failed(); + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_NothingAllowed_Fails() + { + CipherSuitesPolicy csp = BuildPolicy(); + + var sp = new ConnectionParams(); + sp.CipherSuitesPolicy = csp; + + var cp = new ConnectionParams(); + cp.CipherSuitesPolicy = csp; + + NegotiatedParams ret = ConnectAndGetNegotiatedParams(sp, cp); + ret.Failed(); + } + + [ConditionalFact(nameof(CipherSuitesPolicyAndTls13Supported))] + public void CipherSuitesPolicy_AllowOneOnOneSideTls13_Success() + { + bool hasSucceededAtLeastOnce = false; + AllowOneOnOneSide(GetTls13CipherSuites(), + RequiredByTls13Spec, + (cs) => hasSucceededAtLeastOnce = true); + Assert.True(hasSucceededAtLeastOnce); + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowTwoOnBothSidesWithSingleOverlapNonTls13_Success() + { + CheckPrereqsForNonTls13Tests(3); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[1], + SupportedNonTls13CipherSuites[2]) + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + + ret.Succeeded(); + ret.CheckCipherSuite(SupportedNonTls13CipherSuites[1]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowTwoOnBothSidesWithNoOverlapNonTls13_Fails() + { + CheckPrereqsForNonTls13Tests(4); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[2], + SupportedNonTls13CipherSuites[3]) + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowSameTwoOnBothSidesLessPreferredIsTls13_Success() + { + CheckPrereqsForNonTls13Tests(1); + var p = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + TlsCipherSuite.TLS_AES_128_GCM_SHA256) + }; + + NegotiatedParams ret = ConnectAndGetNegotiatedParams(p, p); + ret.Succeeded(); + + // If both sides can speak TLS 1.3 they should speak it + if (Tls13Supported) + { + ret.CheckCipherSuite(TlsCipherSuite.TLS_AES_128_GCM_SHA256); + } + else + { + ret.CheckCipherSuite(SupportedNonTls13CipherSuites[0]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_TwoCipherSuitesWithAllOverlapping_Success() + { + CheckPrereqsForNonTls13Tests(2); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[1], + SupportedNonTls13CipherSuites[0]) + }; + + for (int i = 0; i < 2; i++) + { + bool isAClient = i == 0; + NegotiatedParams ret = isAClient ? + ConnectAndGetNegotiatedParams(b, a) : + ConnectAndGetNegotiatedParams(a, b); + + ret.Succeeded(); + Assert.True(ret.CipherSuite == SupportedNonTls13CipherSuites[0] || + ret.CipherSuite == SupportedNonTls13CipherSuites[1]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_ThreeCipherSuitesWithTwoOverlapping_Success() + { + CheckPrereqsForNonTls13Tests(4); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1], + SupportedNonTls13CipherSuites[2]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[3], + SupportedNonTls13CipherSuites[2], + SupportedNonTls13CipherSuites[1]) + }; + + for (int i = 0; i < 2; i++) + { + bool isAClient = i == 0; + NegotiatedParams ret = isAClient ? + ConnectAndGetNegotiatedParams(b, a) : + ConnectAndGetNegotiatedParams(a, b); + + ret.Succeeded(); + + Assert.True(ret.CipherSuite == SupportedNonTls13CipherSuites[1] || + ret.CipherSuite == SupportedNonTls13CipherSuites[2]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicyAndTls13Supported))] + public void CipherSuitesPolicy_OnlyTls13CipherSuiteAllowedButChosenProtocolsDoesNotAllowIt_Fails() + { + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(TlsCipherSuite.TLS_AES_128_GCM_SHA256), + SslProtocols = NonTls13Protocols, + }; + + var b = new ConnectionParams(); + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicyAndTls13Supported))] + public void CipherSuitesPolicy_OnlyTls13CipherSuiteAllowedOtherSideDoesNotAllowTls13_Fails() + { + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(TlsCipherSuite.TLS_AES_128_GCM_SHA256) + }; + + var b = new ConnectionParams() + { + SslProtocols = NonTls13Protocols + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_OnlyNonTls13CipherSuitesAllowedButChosenProtocolDoesNotAllowIt_Fails() + { + CheckPrereqsForNonTls13Tests(1); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0]), + SslProtocols = SslProtocols.Tls13, + }; + + var b = new ConnectionParams(); + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_OnlyNonTls13CipherSuiteAllowedButOtherSideDoesNotAllowIt_Fails() + { + CheckPrereqsForNonTls13Tests(1); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0]) + }; + + var b = new ConnectionParams() + { + SslProtocols = SslProtocols.Tls13 + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [Fact] + public void CipherSuitesPolicy_CtorWithNull_Fails() + { + Assert.Throws(() => new CipherSuitesPolicy(null)); + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowedCipherSuitesIncludesSubsetOfInput_Success() + { + TlsCipherSuite[] allCipherSuites = (TlsCipherSuite[])Enum.GetValues(typeof(TlsCipherSuite)); + var r = new Random(123); + int[] numOfCipherSuites = new int[] { 0, 1, 2, 5, 10, 15, 30 }; + + foreach (int n in numOfCipherSuites) + { + HashSet cipherSuites = PickRandomValues(allCipherSuites, n, r); + var csp = new CipherSuitesPolicy(cipherSuites); + Assert.NotNull(csp.AllowedCipherSuites); + Assert.InRange(csp.AllowedCipherSuites.Count(), 0, n); + + foreach (var cs in csp.AllowedCipherSuites) + { + Assert.True(cipherSuites.Contains(cs)); + } + } + } + + private HashSet PickRandomValues(TlsCipherSuite[] all, int n, Random r) + { + var ret = new HashSet(); + + while (ret.Count != n) + { + ret.Add(all[r.Next() % n]); + } + + return ret; + } + + private static void AllowOneOnOneSide(IEnumerable cipherSuites, + Predicate mustSucceed, + Action cipherSuitePicked = null) + { + foreach (TlsCipherSuite cs in cipherSuites) + { + CipherSuitesPolicy csp = BuildPolicy(cs); + + var paramsA = new ConnectionParams() + { + CipherSuitesPolicy = csp, + }; + + var paramsB = new ConnectionParams(); + int score = 0; // 1 for success 0 for fail. Sum should be even + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(paramsA, paramsB) : + ConnectAndGetNegotiatedParams(paramsB, paramsA); + + score += ret.HasSucceeded ? 1 : 0; + if (mustSucceed(cs) || ret.HasSucceeded) + { + // we do not always guarantee success but if it succeeds it + // must use the picked cipher suite + ret.Succeeded(); + ret.CheckCipherSuite(cs); + + if (cipherSuitePicked != null && i == 0) + { + cipherSuitePicked(cs); + } + } + } + + // we should either get 2 successes or 2 failures + Assert.True(score % 2 == 0); + } + } + + private static void CheckPrereqsForNonTls13Tests(int minCipherSuites) + { + if (SupportedNonTls13CipherSuites.Count < minCipherSuites) + { + // We do not want to accidentally make the tests pass due to the bug in the code + // This situation is rather unexpected but can happen on i.e. Alpine + // Make sure at least some tests run. + + if (Tls13Supported) + { + throw new SkipTestException($"Test requires that at least {minCipherSuites} non TLS 1.3 cipher suites are supported."); + } + else + { + throw new Exception($"Less than {minCipherSuites} cipher suites are supported: {string.Join(", ", SupportedNonTls13CipherSuites)}"); + } + } + } + private static bool ProtocolsSupported(SslProtocols protocols) { var defaultParams = new ConnectionParams(); @@ -165,6 +551,42 @@ namespace System.Net.Security.Tests yield return TlsCipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; } + private static IEnumerable GetNonTls13CipherSuites() + { + var tls13cs = new HashSet(GetTls13CipherSuites()); + foreach (TlsCipherSuite cs in typeof(TlsCipherSuite).GetEnumValues()) + { + if (!tls13cs.Contains(cs)) + { + yield return cs; + } + } + } + + private static IReadOnlyList GetSupportedNonTls13CipherSuites() + { + // This function is used to initialize static property. + // We do not want skipped tests to fail because of that. + if (!CipherSuitesPolicySupported) + return null; + + var ret = new List(); + AllowOneOnOneSide(GetNonTls13CipherSuites(), (cs) => false, (cs) => ret.Add(cs)); + + return ret; + } + + private static bool RequiredByTls13Spec(TlsCipherSuite cs) + { + // per spec only one MUST be implemented + return cs == TlsCipherSuite.TLS_AES_128_GCM_SHA256; + } + + private static CipherSuitesPolicy BuildPolicy(params TlsCipherSuite[] cipherSuites) + { + return new CipherSuitesPolicy(cipherSuites); + } + private static async Task WaitForSecureConnection(VirtualNetwork connection, Func server, Func client) { Task serverTask = null; @@ -253,10 +675,12 @@ namespace System.Net.Security.Tests serverOptions.ServerCertificate = Configuration.Certificates.GetSelfSignedServerCertificate(); serverOptions.EncryptionPolicy = serverParams.EncryptionPolicy; serverOptions.EnabledSslProtocols = serverParams.SslProtocols; + serverOptions.CipherSuitesPolicy = serverParams.CipherSuitesPolicy; var clientOptions = new SslClientAuthenticationOptions(); clientOptions.EncryptionPolicy = clientParams.EncryptionPolicy; clientOptions.EnabledSslProtocols = clientParams.SslProtocols; + clientOptions.CipherSuitesPolicy = clientParams.CipherSuitesPolicy; clientOptions.TargetHost = "test"; clientOptions.RemoteCertificateValidationCallback = new RemoteCertificateValidationCallback((object sender, @@ -293,6 +717,7 @@ namespace System.Net.Security.Tests private class ConnectionParams { + public CipherSuitesPolicy CipherSuitesPolicy = null; public EncryptionPolicy EncryptionPolicy = EncryptionPolicy.RequireEncryption; public SslProtocols SslProtocols = SslProtocols.None; } -- 2.7.4