[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);
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).
// 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 =
using System;
using System.Diagnostics;
+using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles;
[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);
[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);
}
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()
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);
#include <openssl/x509v3.h>
#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
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);
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) \
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) \
#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
#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
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);
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)
{
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
/*
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);
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);
public bool AllowRenegotiation { get { throw null; } set { } }
public System.Collections.Generic.List<System.Net.Security.SslApplicationProtocol> 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 { } }
public bool AllowRenegotiation { get { throw null; } set { } }
public System.Collections.Generic.List<System.Net.Security.SslApplicationProtocol> 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 { } }
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<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
+ public sealed class CipherSuitesPolicy
+ {
+ [System.CLSCompliantAttribute(false)]
+ public CipherSuitesPolicy(System.Collections.Generic.IEnumerable<System.Net.Security.TlsCipherSuite> allowedCipherSuites) { }
+ [System.CLSCompliantAttribute(false)]
+ public System.Collections.Generic.IEnumerable<System.Net.Security.TlsCipherSuite> AllowedCipherSuites { get; }
+ }
[System.CLSCompliantAttribute(false)]
public enum TlsCipherSuite : ushort
{
<data name="net_conflicting_options" xml:space="preserve">
<value>The '{0}' option was already set in the SslStream constructor.</value>
</data>
+ <data name="net_ssl_ciphersuites_policy_not_supported" xml:space="preserve">
+ <value>CipherSuitesPolicy is not supported on this platform.</value>
+ </data>
</root>
<Compile Include="System\Net\Security\SslStream.Implementation.Adapters.cs" />
<Compile Include="System\Net\SslStreamContext.cs" />
<Compile Include="System\Net\Security\AuthenticatedStream.cs" />
+ <Compile Include="System\Net\Security\CipherSuitesPolicy.cs" />
<Compile Include="System\Net\Security\NetEventSource.Security.cs" />
<Compile Include="System\Net\Security\SecureChannel.cs" />
<Compile Include="System\Net\Security\SslSessionsCache.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
<Compile Include="System\Net\CertificateValidationPal.Windows.cs" />
+ <Compile Include="System\Net\Security\CipherSuitesPolicyPal.Windows.cs" />
<Compile Include="System\Net\Security\NegotiateStreamPal.Windows.cs" />
<Compile Include="System\Net\Security\NetEventSource.Security.Windows.cs" />
<Compile Include="System\Net\Security\SslStreamPal.Windows.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' AND '$(TargetsOSX)' != 'true' ">
<Compile Include="System\Net\CertificateValidationPal.Unix.cs" />
+ <Compile Include="System\Net\Security\CipherSuitesPolicyPal.Linux.cs" />
<Compile Include="System\Net\Security\SslStreamPal.Unix.cs" />
<Compile Include="System\Net\Security\SslConnectionInfo.Linux.cs" />
<Compile Include="System\Net\Security\StreamSizes.Unix.cs" />
<Compile Include="System\Net\Security\SslConnectionInfo.OSX.cs" />
<Compile Include="System\Net\Security\SslStreamPal.OSX.cs" />
<Compile Include="System\Net\Security\StreamSizes.OSX.cs" />
+ <Compile Include="System\Net\Security\CipherSuitesPolicyPal.OSX.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Win32.Primitives" />
<Reference Include="System.Collections.NonGeneric" />
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Diagnostics.Tracing" />
+ <Reference Include="System.Linq" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Resources.ResourceManager" />
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Specifies allowed cipher suites.
+ /// </summary>
+ public sealed partial class CipherSuitesPolicy
+ {
+ internal CipherSuitesPolicyPal Pal { get; private set; }
+
+ [CLSCompliant(false)]
+ public CipherSuitesPolicy(IEnumerable<TlsCipherSuite> allowedCipherSuites)
+ {
+ if (allowedCipherSuites == null)
+ {
+ throw new ArgumentNullException(nameof(allowedCipherSuites));
+ }
+
+ Pal = new CipherSuitesPolicyPal(allowedCipherSuites);
+ }
+
+ [CLSCompliant(false)]
+ public IEnumerable<TlsCipherSuite> 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;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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<TlsCipherSuite> _tlsCipherSuites = new List<TlsCipherSuite>();
+
+ internal IEnumerable<TlsCipherSuite> GetCipherSuites() => _tlsCipherSuites;
+
+ internal CipherSuitesPolicyPal(IEnumerable<TlsCipherSuite> 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();
+ }
+ }
+ }
+}
--- /dev/null
+// 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<TlsCipherSuite> allowedCipherSuites)
+ {
+ TlsCipherSuites = allowedCipherSuites.Select((cs) => (uint)cs).ToArray();
+ }
+
+ internal IEnumerable<TlsCipherSuite> GetCipherSuites()
+ {
+ return TlsCipherSuites.Select((cs) => (TlsCipherSuite)cs);
+ }
+ }
+}
--- /dev/null
+// 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<TlsCipherSuite> allowedCipherSuites)
+ {
+ throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported);
+ }
+
+ internal IEnumerable<TlsCipherSuite> GetCipherSuites() => null;
+ }
+}
try
{
+ int osStatus;
+
unsafe
{
_readCallback = ReadFromConnection;
_sslContext = CreateSslContext(credential, sslAuthenticationOptions.IsServer);
- int osStatus = Interop.AppleCrypto.SslSetIoCallbacks(
+ osStatus = Interop.AppleCrypto.SslSetIoCallbacks(
_sslContext,
_readCallback,
_writeCallback);
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.
// 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.
CertSelectionDelegate = localCallback;
CertificateRevocationCheckMode = sslClientAuthenticationOptions.CertificateRevocationCheckMode;
ClientCertificates = sslClientAuthenticationOptions.ClientCertificates;
+ CipherSuitesPolicy = sslClientAuthenticationOptions.CipherSuitesPolicy;
}
internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions)
// Server specific options.
CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode;
ServerCertificate = sslServerAuthenticationOptions.ServerCertificate;
+ CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy;
}
internal bool AllowRenegotiation { get; set; }
internal RemoteCertValidationCallback CertValidationDelegate { get; set; }
internal LocalCertSelectionCallback CertSelectionDelegate { get; set; }
internal ServerCertSelectionCallback ServerCertSelectionDelegate { get; set; }
+ internal CipherSuitesPolicy CipherSuitesPolicy { get; set; }
}
}
get => _enabledSslProtocols;
set => _enabledSslProtocols = value;
}
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
}
}
-
_encryptionPolicy = value;
}
}
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
}
}
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.");
+ }
}
}
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
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<TlsCipherSuite> s_tls13CipherSuiteLookup = new HashSet<TlsCipherSuite>(GetTls13CipherSuites());
private static HashSet<TlsCipherSuite> s_tls12CipherSuiteLookup = new HashSet<TlsCipherSuite>(GetTls12CipherSuites());
private static HashSet<TlsCipherSuite> s_tls10And11CipherSuiteLookup = new HashSet<TlsCipherSuite>(GetTls10And11CipherSuites());
- private static Dictionary<SslProtocols, HashSet<TlsCipherSuite>> _protocolCipherSuiteLookup = new Dictionary<SslProtocols, HashSet<TlsCipherSuite>>()
+ private static Dictionary<SslProtocols, HashSet<TlsCipherSuite>> s_protocolCipherSuiteLookup = new Dictionary<SslProtocols, HashSet<TlsCipherSuite>>()
{
{ SslProtocols.Tls12, s_tls12CipherSuiteLookup },
{ SslProtocols.Tls11, s_tls10And11CipherSuiteLookup },
{ SslProtocols.Tls, s_tls10And11CipherSuiteLookup },
};
+ private static Lazy<bool> s_cipherSuitePolicySupported = new Lazy<bool>(() =>
+ {
+ try
+ {
+ new CipherSuitesPolicy(Array.Empty<TlsCipherSuite>());
+ return true;
+ }
+ catch (PlatformNotSupportedException) { }
+
+ return false;
+ });
+
+ private static IReadOnlyList<TlsCipherSuite> SupportedNonTls13CipherSuites = GetSupportedNonTls13CipherSuites();
+
[ConditionalFact(nameof(IsKnownPlatformSupportingTls13))]
public void Tls13IsSupported_GetValue_ReturnsTrue()
{
ret.Succeeded();
Assert.True(
- _protocolCipherSuiteLookup[protocol].Contains(ret.CipherSuite),
+ s_protocolCipherSuiteLookup[protocol].Contains(ret.CipherSuite),
$"`{ret.CipherSuite}` is not recognized as {protocol} cipher suite");
}
}
}
+ [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<ArgumentNullException>(() => 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<TlsCipherSuite> 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<TlsCipherSuite> PickRandomValues(TlsCipherSuite[] all, int n, Random r)
+ {
+ var ret = new HashSet<TlsCipherSuite>();
+
+ while (ret.Count != n)
+ {
+ ret.Add(all[r.Next() % n]);
+ }
+
+ return ret;
+ }
+
+ private static void AllowOneOnOneSide(IEnumerable<TlsCipherSuite> cipherSuites,
+ Predicate<TlsCipherSuite> mustSucceed,
+ Action<TlsCipherSuite> 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();
yield return TlsCipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384;
}
+ private static IEnumerable<TlsCipherSuite> GetNonTls13CipherSuites()
+ {
+ var tls13cs = new HashSet<TlsCipherSuite>(GetTls13CipherSuites());
+ foreach (TlsCipherSuite cs in typeof(TlsCipherSuite).GetEnumValues())
+ {
+ if (!tls13cs.Contains(cs))
+ {
+ yield return cs;
+ }
+ }
+ }
+
+ private static IReadOnlyList<TlsCipherSuite> 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<TlsCipherSuite>();
+ 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<Exception> WaitForSecureConnection(VirtualNetwork connection, Func<Task> server, Func<Task> client)
{
Task serverTask = null;
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,
private class ConnectionParams
{
+ public CipherSuitesPolicy CipherSuitesPolicy = null;
public EncryptionPolicy EncryptionPolicy = EncryptionPolicy.RequireEncryption;
public SslProtocols SslProtocols = SslProtocols.None;
}