diff --git a/include/grpc/grpc_security_constants.h b/include/grpc/grpc_security_constants.h index e50829409b3..a62f76753b9 100644 --- a/include/grpc/grpc_security_constants.h +++ b/include/grpc/grpc_security_constants.h @@ -139,6 +139,9 @@ typedef enum { */ typedef enum { UDS = 0, LOCAL_TCP } grpc_local_connect_type; +/** The TLS versions that are supported by the SSL stack. **/ +typedef enum { TLS1_2, TLS1_3 } grpc_tls_version; + #ifdef __cplusplus } #endif diff --git a/src/core/lib/security/credentials/ssl/ssl_credentials.cc b/src/core/lib/security/credentials/ssl/ssl_credentials.cc index 48d78f39583..3bb7790f5c9 100644 --- a/src/core/lib/security/credentials/ssl/ssl_credentials.cc +++ b/src/core/lib/security/credentials/ssl/ssl_credentials.cc @@ -117,6 +117,16 @@ void grpc_ssl_credentials::build_config( } } +void grpc_ssl_credentials::set_min_tls_version( + grpc_tls_version min_tls_version) { + config_.min_tls_version = min_tls_version; +} + +void grpc_ssl_credentials::set_max_tls_version( + grpc_tls_version max_tls_version) { + config_.max_tls_version = max_tls_version; +} + /* Deprecated in favor of grpc_ssl_credentials_create_ex. Will be removed * once all of its call sites are migrated to grpc_ssl_credentials_create_ex. */ grpc_channel_credentials* grpc_ssl_credentials_create( @@ -213,6 +223,16 @@ void grpc_ssl_server_credentials::build_config( config_.num_key_cert_pairs = num_key_cert_pairs; } +void grpc_ssl_server_credentials::set_min_tls_version( + grpc_tls_version min_tls_version) { + config_.min_tls_version = min_tls_version; +} + +void grpc_ssl_server_credentials::set_max_tls_version( + grpc_tls_version max_tls_version) { + config_.max_tls_version = max_tls_version; +} + grpc_ssl_server_certificate_config* grpc_ssl_server_certificate_config_create( const char* pem_root_certs, const grpc_ssl_pem_key_cert_pair* pem_key_cert_pairs, diff --git a/src/core/lib/security/credentials/ssl/ssl_credentials.h b/src/core/lib/security/credentials/ssl/ssl_credentials.h index 545a27f0be4..4c90813f8d2 100644 --- a/src/core/lib/security/credentials/ssl/ssl_credentials.h +++ b/src/core/lib/security/credentials/ssl/ssl_credentials.h @@ -38,6 +38,11 @@ class grpc_ssl_credentials : public grpc_channel_credentials { const char* target, const grpc_channel_args* args, grpc_channel_args** new_args) override; + // TODO(mattstev): Plumb to wrapped languages. Until then, setting the TLS + // version should be done for testing purposes only. + void set_min_tls_version(grpc_tls_version min_tls_version); + void set_max_tls_version(grpc_tls_version max_tls_version); + private: void build_config(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, @@ -77,6 +82,11 @@ class grpc_ssl_server_credentials final : public grpc_server_credentials { config); } + // TODO(mattstev): Plumb to wrapped languages. Until then, setting the TLS + // version should be done for testing purposes only. + void set_min_tls_version(grpc_tls_version min_tls_version); + void set_max_tls_version(grpc_tls_version max_tls_version); + const grpc_ssl_server_config& config() const { return config_; } private: diff --git a/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc b/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc index ae93406d7bb..ef97c028109 100644 --- a/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc +++ b/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc @@ -107,6 +107,8 @@ class grpc_ssl_channel_security_connector final } options.cipher_suites = grpc_get_ssl_cipher_suites(); options.session_cache = ssl_session_cache; + options.min_tls_version = grpc_get_tsi_tls_version(config->min_tls_version); + options.max_tls_version = grpc_get_tsi_tls_version(config->max_tls_version); const tsi_result result = tsi_create_ssl_client_handshaker_factory_with_options( &options, &client_handshaker_factory_); @@ -251,6 +253,10 @@ class grpc_ssl_server_security_connector options.cipher_suites = grpc_get_ssl_cipher_suites(); options.alpn_protocols = alpn_protocol_strings; options.num_alpn_protocols = static_cast(num_alpn_protocols); + options.min_tls_version = grpc_get_tsi_tls_version( + server_credentials->config().min_tls_version); + options.max_tls_version = grpc_get_tsi_tls_version( + server_credentials->config().max_tls_version); const tsi_result result = tsi_create_ssl_server_handshaker_factory_with_options( &options, &server_handshaker_factory_); diff --git a/src/core/lib/security/security_connector/ssl/ssl_security_connector.h b/src/core/lib/security/security_connector/ssl/ssl_security_connector.h index f11e8190a30..04c32bb2a8c 100644 --- a/src/core/lib/security/security_connector/ssl/ssl_security_connector.h +++ b/src/core/lib/security/security_connector/ssl/ssl_security_connector.h @@ -33,7 +33,10 @@ struct grpc_ssl_config { tsi_ssl_pem_key_cert_pair* pem_key_cert_pair; char* pem_root_certs; verify_peer_options verify_options; + grpc_tls_version min_tls_version = grpc_tls_version::TLS1_2; + grpc_tls_version max_tls_version = grpc_tls_version::TLS1_3; }; + /* Creates an SSL channel_security_connector. - request_metadata_creds is the credentials object which metadata will be sent with each request. This parameter can be NULL. @@ -62,6 +65,8 @@ struct grpc_ssl_server_config { char* pem_root_certs = nullptr; grpc_ssl_client_certificate_request_type client_certificate_request = GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE; + grpc_tls_version min_tls_version = grpc_tls_version::TLS1_2; + grpc_tls_version max_tls_version = grpc_tls_version::TLS1_3; }; /* Creates an SSL server_security_connector. - config is the SSL config to be used for the SSL channel establishment. diff --git a/src/core/lib/security/security_connector/ssl_utils.cc b/src/core/lib/security/security_connector/ssl_utils.cc index 3aa12a8f1d8..dab4641b216 100644 --- a/src/core/lib/security/security_connector/ssl_utils.cc +++ b/src/core/lib/security/security_connector/ssl_utils.cc @@ -67,6 +67,9 @@ static const char* cipher_suites = nullptr; // All cipher suites for default are compliant with HTTP2. GPR_GLOBAL_CONFIG_DEFINE_STRING( grpc_ssl_cipher_suites, + "TLS_AES_128_GCM_SHA256:" + "TLS_AES_256_GCM_SHA384:" + "TLS_CHACHA20_POLY1305_SHA256:" "ECDHE-ECDSA-AES128-GCM-SHA256:" "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES128-GCM-SHA256:" @@ -134,6 +137,18 @@ grpc_get_tsi_client_certificate_request_type( } } +tsi_tls_version grpc_get_tsi_tls_version(grpc_tls_version tls_version) { + switch (tls_version) { + case grpc_tls_version::TLS1_2: + return tsi_tls_version::TSI_TLS1_2; + case grpc_tls_version::TLS1_3: + return tsi_tls_version::TSI_TLS1_3; + default: + gpr_log(GPR_INFO, "Falling back to TLS 1.2."); + return tsi_tls_version::TSI_TLS1_2; + } +} + grpc_error* grpc_ssl_check_alpn(const tsi_peer* peer) { #if TSI_OPENSSL_ALPN_SUPPORT /* Check the ALPN if ALPN is supported. */ diff --git a/src/core/lib/security/security_connector/ssl_utils.h b/src/core/lib/security/security_connector/ssl_utils.h index b7522e8ab7c..258b4059d3a 100644 --- a/src/core/lib/security/security_connector/ssl_utils.h +++ b/src/core/lib/security/security_connector/ssl_utils.h @@ -73,6 +73,9 @@ grpc_get_tsi_client_certificate_request_type( grpc_security_level grpc_tsi_security_level_string_to_enum( const char* security_level); +/* Map grpc_tls_version to tsi_tls_version. */ +tsi_tls_version grpc_get_tsi_tls_version(grpc_tls_version tls_version); + /* Map grpc_security_level enum to a string. */ const char* grpc_security_level_to_string(grpc_security_level security_level); diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc index d2a5168c6c4..83cc98f381e 100644 --- a/src/core/tsi/ssl_transport_security.cc +++ b/src/core/tsi/ssl_transport_security.cc @@ -51,6 +51,7 @@ extern "C" { #include #include #include +#include #include #include } @@ -890,6 +891,50 @@ static int NullVerifyCallback(int /*preverify_ok*/, X509_STORE_CTX* /*ctx*/) { return 1; } +// Sets the min and max TLS version of |ssl_context| to |min_tls_version| and +// |max_tls_version|, respectively. Calling this method is a no-op when using +// OpenSSL versions < 1.1. +static tsi_result tsi_set_min_and_max_tls_versions( + SSL_CTX* ssl_context, tsi_tls_version min_tls_version, + tsi_tls_version max_tls_version) { + if (ssl_context == nullptr) { + gpr_log(GPR_INFO, + "Invalid nullptr argument to |tsi_set_min_and_max_tls_versions|."); + return TSI_INVALID_ARGUMENT; + } +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + // Set the min TLS version of the SSL context. + switch (min_tls_version) { + case tsi_tls_version::TSI_TLS1_2: + SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION); + break; +#if defined(TLS1_3_VERSION) + case tsi_tls_version::TSI_TLS1_3: + SSL_CTX_set_min_proto_version(ssl_context, TLS1_3_VERSION); + break; +#endif + default: + gpr_log(GPR_INFO, "TLS version is not supported."); + return TSI_FAILED_PRECONDITION; + } + // Set the max TLS version of the SSL context. + switch (max_tls_version) { + case tsi_tls_version::TSI_TLS1_2: + SSL_CTX_set_max_proto_version(ssl_context, TLS1_2_VERSION); + break; +#if defined(TLS1_3_VERSION) + case tsi_tls_version::TSI_TLS1_3: + SSL_CTX_set_max_proto_version(ssl_context, TLS1_3_VERSION); + break; +#endif + default: + gpr_log(GPR_INFO, "TLS version is not supported."); + return TSI_FAILED_PRECONDITION; + } +#endif + return TSI_OK; +} + /* --- tsi_ssl_root_certs_store methods implementation. ---*/ tsi_ssl_root_certs_store* tsi_ssl_root_certs_store_create( @@ -1301,7 +1346,7 @@ static const tsi_handshaker_result_vtable handshaker_result_vtable = { }; static tsi_result ssl_handshaker_result_create( - tsi_ssl_handshaker* handshaker, const unsigned char* unused_bytes, + tsi_ssl_handshaker* handshaker, unsigned char* unused_bytes, size_t unused_bytes_size, tsi_handshaker_result** handshaker_result) { if (handshaker == nullptr || handshaker_result == nullptr || (unused_bytes_size > 0 && unused_bytes == nullptr)) { @@ -1315,11 +1360,8 @@ static tsi_result ssl_handshaker_result_create( handshaker->ssl = nullptr; result->network_io = handshaker->network_io; handshaker->network_io = nullptr; - if (unused_bytes_size > 0) { - result->unused_bytes = - static_cast(gpr_malloc(unused_bytes_size)); - memcpy(result->unused_bytes, unused_bytes, unused_bytes_size); - } + /* Transfer ownership of |unused_bytes| to the handshaker result. */ + result->unused_bytes = unused_bytes; result->unused_bytes_size = unused_bytes_size; *handshaker_result = &result->base; return TSI_OK; @@ -1412,6 +1454,36 @@ static void ssl_handshaker_destroy(tsi_handshaker* self) { gpr_free(impl); } +// Removes the bytes remaining in |impl->SSL|'s read BIO and writes them to +// |bytes_remaining|. +static tsi_result ssl_bytes_remaining(tsi_ssl_handshaker* impl, + unsigned char** bytes_remaining, + size_t* bytes_remaining_size) { + if (impl == nullptr || bytes_remaining == nullptr || + bytes_remaining_size == nullptr) { + return TSI_INVALID_ARGUMENT; + } + // Atempt to read all of the bytes in SSL's read BIO. These bytes should + // contain application data records that were appended to a handshake record + // containing the ClientFinished or ServerFinished message. + size_t bytes_in_ssl = BIO_pending(SSL_get_rbio(impl->ssl)); + if (bytes_in_ssl == 0) return TSI_OK; + *bytes_remaining = static_cast(gpr_malloc(bytes_in_ssl)); + int bytes_read = BIO_read(SSL_get_rbio(impl->ssl), *bytes_remaining, + static_cast(bytes_in_ssl)); + // If an unexpected number of bytes were read, return an error status and free + // all of the bytes that were read. + if (bytes_read < 0 || static_cast(bytes_read) != bytes_in_ssl) { + gpr_log(GPR_ERROR, + "Failed to read the expected number of bytes from SSL object."); + gpr_free(*bytes_remaining); + *bytes_remaining = nullptr; + return TSI_INTERNAL_ERROR; + } + *bytes_remaining_size = static_cast(bytes_read); + return TSI_OK; +} + static tsi_result ssl_handshaker_next( tsi_handshaker* self, const unsigned char* received_bytes, size_t received_bytes_size, const unsigned char** bytes_to_send, @@ -1452,9 +1524,19 @@ static tsi_result ssl_handshaker_next( if (ssl_handshaker_get_result(impl) == TSI_HANDSHAKE_IN_PROGRESS) { *handshaker_result = nullptr; } else { - size_t unused_bytes_size = received_bytes_size - bytes_consumed; - const unsigned char* unused_bytes = - unused_bytes_size == 0 ? nullptr : received_bytes + bytes_consumed; + // Any bytes that remain in |impl->ssl|'s read BIO after the handshake is + // complete must be extracted and set to the unused bytes of the handshaker + // result. This indicates to the gRPC stack that there are bytes from the + // peer that must be processed. + unsigned char* unused_bytes = nullptr; + size_t unused_bytes_size = 0; + status = ssl_bytes_remaining(impl, &unused_bytes, &unused_bytes_size); + if (status != TSI_OK) return status; + if (unused_bytes_size > received_bytes_size) { + gpr_log(GPR_ERROR, "More unused bytes than received bytes."); + gpr_free(unused_bytes); + return TSI_INTERNAL_ERROR; + } status = ssl_handshaker_result_create(impl, unused_bytes, unused_bytes_size, handshaker_result); if (status == TSI_OK) { @@ -1807,11 +1889,14 @@ tsi_result tsi_create_ssl_client_handshaker_factory_with_options( return TSI_INVALID_ARGUMENT; } -#if defined(OPENSSL_NO_TLS1_2_METHOD) || OPENSSL_API_COMPAT >= 0x10100000L +#if OPENSSL_VERSION_NUMBER >= 0x10100000 ssl_context = SSL_CTX_new(TLS_method()); #else ssl_context = SSL_CTX_new(TLSv1_2_method()); #endif + result = tsi_set_min_and_max_tls_versions( + ssl_context, options->min_tls_version, options->max_tls_version); + if (result != TSI_OK) return result; if (ssl_context == nullptr) { gpr_log(GPR_ERROR, "Could not create ssl context."); return TSI_INVALID_ARGUMENT; @@ -1971,11 +2056,15 @@ tsi_result tsi_create_ssl_server_handshaker_factory_with_options( for (i = 0; i < options->num_key_cert_pairs; i++) { do { -#if defined(OPENSSL_NO_TLS1_2_METHOD) || OPENSSL_API_COMPAT >= 0x10100000L +#if OPENSSL_VERSION_NUMBER >= 0x10100000 impl->ssl_contexts[i] = SSL_CTX_new(TLS_method()); #else impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method()); #endif + result = tsi_set_min_and_max_tls_versions(impl->ssl_contexts[i], + options->min_tls_version, + options->max_tls_version); + if (result != TSI_OK) return result; if (impl->ssl_contexts[i] == nullptr) { gpr_log(GPR_ERROR, "Could not create ssl context."); result = TSI_OUT_OF_RESOURCES; diff --git a/src/core/tsi/ssl_transport_security.h b/src/core/tsi/ssl_transport_security.h index 5ace7ff35be..8476c83f8b0 100644 --- a/src/core/tsi/ssl_transport_security.h +++ b/src/core/tsi/ssl_transport_security.h @@ -21,6 +21,7 @@ #include +#include #include "absl/strings/string_view.h" #include "src/core/tsi/transport_security_interface.h" @@ -152,6 +153,10 @@ struct tsi_ssl_client_handshaker_options { /* skip server certificate verification. */ bool skip_server_certificate_verification; + /* The min and max TLS versions that will be negotiated by the handshaker. */ + tsi_tls_version min_tls_version; + tsi_tls_version max_tls_version; + tsi_ssl_client_handshaker_options() : pem_key_cert_pair(nullptr), pem_root_certs(nullptr), @@ -160,7 +165,9 @@ struct tsi_ssl_client_handshaker_options { alpn_protocols(nullptr), num_alpn_protocols(0), session_cache(nullptr), - skip_server_certificate_verification(false) {} + skip_server_certificate_verification(false), + min_tls_version(tsi_tls_version::TSI_TLS1_2), + max_tls_version(tsi_tls_version::TSI_TLS1_3) {} }; /* Creates a client handshaker factory. @@ -276,6 +283,9 @@ struct tsi_ssl_server_handshaker_options { const char* session_ticket_key; /* session_ticket_key_size is a size of session ticket encryption key. */ size_t session_ticket_key_size; + /* The min and max TLS versions that will be negotiated by the handshaker. */ + tsi_tls_version min_tls_version; + tsi_tls_version max_tls_version; tsi_ssl_server_handshaker_options() : pem_key_cert_pairs(nullptr), @@ -286,7 +296,9 @@ struct tsi_ssl_server_handshaker_options { alpn_protocols(nullptr), num_alpn_protocols(0), session_ticket_key(nullptr), - session_ticket_key_size(0) {} + session_ticket_key_size(0), + min_tls_version(tsi_tls_version::TSI_TLS1_2), + max_tls_version(tsi_tls_version::TSI_TLS1_3) {} }; /* Creates a server handshaker factory. diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h index 6e93870bf04..4608f400914 100644 --- a/src/core/tsi/transport_security_interface.h +++ b/src/core/tsi/transport_security_interface.h @@ -64,6 +64,11 @@ typedef enum { TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY, } tsi_client_certificate_request_type; +typedef enum { + TSI_TLS1_2, + TSI_TLS1_3, +} tsi_tls_version; + const char* tsi_result_to_string(tsi_result result); const char* tsi_security_level_to_string(tsi_security_level security_level); diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs index 4c1189320fa..c7ff3216ec8 100644 --- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs @@ -246,8 +246,10 @@ namespace Grpc.IntegrationTesting private void CheckRejected() { var ex = Assert.Throws(() => client.UnaryCall(new SimpleRequest { ResponseSize = 10 })); - Assert.AreEqual(StatusCode.Unavailable, ex.Status.StatusCode); - } + if (ex.Status.StatusCode != StatusCode.Unavailable & ex.Status.StatusCode != StatusCode.Unknown) { + Assert.Fail("Expect status to be either Unavailable or Unknown"); + } + } private async Task CheckAuthContextIsPopulated() { diff --git a/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py b/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py index a0948600a25..35d992a33d6 100644 --- a/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py +++ b/src/python/grpcio_tests/tests/unit/_server_ssl_cert_config_test.py @@ -161,8 +161,14 @@ class _ServerSSLCertReloadTest( else: with self.assertRaises(grpc.RpcError) as exception_context: client_stub.UnUn(request) - self.assertEqual(exception_context.exception.code(), - grpc.StatusCode.UNAVAILABLE) + # If TLS 1.2 is used, then the client receives an alert message + # before the handshake is complete, so the status is UNAVAILABLE. If + # TLS 1.3 is used, then the client receives the alert message after + # the handshake is complete, so the TSI handshaker returns the + # TSI_PROTOCOL_FAILURE result. This result does not have a + # corresponding status code, so this yields an UNKNOWN status. + self.assertTrue(exception_context.exception.code( + ) in [grpc.StatusCode.UNAVAILABLE, grpc.StatusCode.UNKNOWN]) def _do_one_shot_client_rpc(self, expect_success, diff --git a/test/core/end2end/end2end_tests.h b/test/core/end2end/end2end_tests.h index 6a12ca690e3..6be694c76c3 100644 --- a/test/core/end2end/end2end_tests.h +++ b/test/core/end2end/end2end_tests.h @@ -35,6 +35,7 @@ typedef struct grpc_end2end_test_config grpc_end2end_test_config; #define FEATURE_MASK_DOES_NOT_SUPPORT_NETWORK_STATUS_CHANGE 128 #define FEATURE_MASK_SUPPORTS_WORKAROUNDS 256 #define FEATURE_MASK_DOES_NOT_SUPPORT_SEND_CALL_CREDENTIALS 512 +#define FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST 1024 #define FAIL_AUTH_CHECK_SERVER_ARG_NAME "fail_auth_check" diff --git a/test/core/end2end/fixtures/h2_oauth2.cc b/test/core/end2end/fixtures/h2_oauth2.cc index ce37f89e478..2cc53d0cfa5 100644 --- a/test/core/end2end/fixtures/h2_oauth2.cc +++ b/test/core/end2end/fixtures/h2_oauth2.cc @@ -26,6 +26,7 @@ #include "src/core/lib/iomgr/iomgr.h" #include "src/core/lib/iomgr/load_file.h" #include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/security/credentials/ssl/ssl_credentials.h" #include "test/core/end2end/end2end_tests.h" #include "test/core/util/port.h" #include "test/core/util/test_config.h" @@ -40,6 +41,7 @@ static const char* client_identity = "Brainy Smurf"; struct fullstack_secure_fixture_data { std::string localaddr; + grpc_tls_version tls_version; }; static const grpc_metadata* find_metadata(const grpc_metadata* md, @@ -93,18 +95,32 @@ static void process_oauth2_failure(void* state, grpc_auth_context* /*ctx*/, } static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( - grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/) { + grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/, + grpc_tls_version tls_version) { grpc_end2end_test_fixture f; int port = grpc_pick_unused_port_or_die(); fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data(); memset(&f, 0, sizeof(f)); ffd->localaddr = grpc_core::JoinHostPort("localhost", port); + ffd->tls_version = tls_version; f.fixture_data = ffd; f.cq = grpc_completion_queue_create_for_next(nullptr); f.shutdown_cq = grpc_completion_queue_create_for_pluck(nullptr); return f; } +static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_secure_fullstack(client_args, server_args, + grpc_tls_version::TLS1_2); +} + +static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_secure_fullstack(client_args, server_args, + grpc_tls_version::TLS1_3); +} + static void chttp2_init_client_secure_fullstack( grpc_end2end_test_fixture* f, grpc_channel_args* client_args, grpc_channel_credentials* creds) { @@ -148,6 +164,15 @@ static void chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack( reinterpret_cast GRPC_SLICE_START_PTR(ca_slice); grpc_channel_credentials* ssl_creds = grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr); + if (f != nullptr && ssl_creds != nullptr) { + // Set the min and max TLS version. + grpc_ssl_credentials* creds = + reinterpret_cast(ssl_creds); + fullstack_secure_fixture_data* ffd = + static_cast(f->fixture_data); + creds->set_min_tls_version(ffd->tls_version); + creds->set_max_tls_version(ffd->tls_version); + } grpc_call_credentials* oauth2_creds = grpc_md_only_test_credentials_create( "authorization", oauth2_md, true /* is_async */); grpc_channel_credentials* ssl_oauth2_creds = @@ -213,6 +238,15 @@ static void chttp2_init_server_simple_ssl_secure_fullstack( grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert}; grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create( nullptr, &pem_key_cert_pair, 1, 0, nullptr); + if (f != nullptr && ssl_creds != nullptr) { + // Set the min and max TLS version. + grpc_ssl_server_credentials* creds = + reinterpret_cast(ssl_creds); + fullstack_secure_fixture_data* ffd = + static_cast(f->fixture_data); + creds->set_min_tls_version(ffd->tls_version); + creds->set_max_tls_version(ffd->tls_version); + } grpc_server_credentials_set_auth_metadata_processor( ssl_creds, test_processor_create(fail_server_auth_check(server_args))); chttp2_init_server_secure_fullstack(f, server_args, ssl_creds); @@ -223,12 +257,22 @@ static void chttp2_init_server_simple_ssl_secure_fullstack( /* All test configurations */ static grpc_end2end_test_config configs[] = { - {"chttp2/simple_ssl_with_oauth2_fullstack", + {"chttp2/simple_ssl_with_oauth2_fullstack_tls1_2", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, - "foo.test.google.fr", chttp2_create_fixture_secure_fullstack, + "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2, + chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack, + chttp2_init_server_simple_ssl_secure_fullstack, + chttp2_tear_down_secure_fullstack}, + {"chttp2/simple_ssl_with_oauth2_fullstack_tls1_3", + FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | + FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | + FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | + FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER | + FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST, + "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3, chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack, chttp2_init_server_simple_ssl_secure_fullstack, chttp2_tear_down_secure_fullstack}, diff --git a/test/core/end2end/fixtures/h2_ssl.cc b/test/core/end2end/fixtures/h2_ssl.cc index c44e0699a32..2d1ef42c995 100644 --- a/test/core/end2end/fixtures/h2_ssl.cc +++ b/test/core/end2end/fixtures/h2_ssl.cc @@ -27,6 +27,7 @@ #include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/iomgr/load_file.h" #include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/security/credentials/ssl/ssl_credentials.h" #include "src/core/lib/security/security_connector/ssl_utils_config.h" #include "test/core/end2end/end2end_tests.h" #include "test/core/util/port.h" @@ -38,16 +39,19 @@ struct fullstack_secure_fixture_data { std::string localaddr; + grpc_tls_version tls_version; }; static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( - grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/) { + grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/, + grpc_tls_version tls_version) { grpc_end2end_test_fixture f; int port = grpc_pick_unused_port_or_die(); fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data(); memset(&f, 0, sizeof(f)); ffd->localaddr = grpc_core::JoinHostPort("localhost", port); + ffd->tls_version = tls_version; f.fixture_data = ffd; f.cq = grpc_completion_queue_create_for_next(nullptr); @@ -56,6 +60,18 @@ static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( return f; } +static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_secure_fullstack(client_args, server_args, + grpc_tls_version::TLS1_2); +} + +static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_secure_fullstack(client_args, server_args, + grpc_tls_version::TLS1_3); +} + static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/, const grpc_metadata* /*md*/, size_t /*md_count*/, @@ -102,6 +118,15 @@ static void chttp2_init_client_simple_ssl_secure_fullstack( grpc_end2end_test_fixture* f, grpc_channel_args* client_args) { grpc_channel_credentials* ssl_creds = grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); + if (f != nullptr && ssl_creds != nullptr) { + // Set the min and max TLS version. + grpc_ssl_credentials* creds = + reinterpret_cast(ssl_creds); + fullstack_secure_fixture_data* ffd = + static_cast(f->fixture_data); + creds->set_min_tls_version(ffd->tls_version); + creds->set_max_tls_version(ffd->tls_version); + } grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), @@ -138,6 +163,15 @@ static void chttp2_init_server_simple_ssl_secure_fullstack( grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert}; grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create( nullptr, &pem_key_cert_pair, 1, 0, nullptr); + if (f != nullptr && ssl_creds != nullptr) { + // Set the min and max TLS version. + grpc_ssl_server_credentials* creds = + reinterpret_cast(ssl_creds); + fullstack_secure_fixture_data* ffd = + static_cast(f->fixture_data); + creds->set_min_tls_version(ffd->tls_version); + creds->set_max_tls_version(ffd->tls_version); + } grpc_slice_unref(cert_slice); grpc_slice_unref(key_slice); if (fail_server_auth_check(server_args)) { @@ -151,12 +185,22 @@ static void chttp2_init_server_simple_ssl_secure_fullstack( /* All test configurations */ static grpc_end2end_test_config configs[] = { - {"chttp2/simple_ssl_fullstack", + {"chttp2/simple_ssl_fullstack_tls1_2", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, - "foo.test.google.fr", chttp2_create_fixture_secure_fullstack, + "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2, + chttp2_init_client_simple_ssl_secure_fullstack, + chttp2_init_server_simple_ssl_secure_fullstack, + chttp2_tear_down_secure_fullstack}, + {"chttp2/simple_ssl_fullstack_tls1_3", + FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | + FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | + FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | + FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER | + FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST, + "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3, chttp2_init_client_simple_ssl_secure_fullstack, chttp2_init_server_simple_ssl_secure_fullstack, chttp2_tear_down_secure_fullstack}, diff --git a/test/core/end2end/fixtures/h2_ssl_cred_reload.cc b/test/core/end2end/fixtures/h2_ssl_cred_reload.cc index 96f796d0b27..bf78cda091a 100644 --- a/test/core/end2end/fixtures/h2_ssl_cred_reload.cc +++ b/test/core/end2end/fixtures/h2_ssl_cred_reload.cc @@ -27,6 +27,7 @@ #include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/iomgr/load_file.h" #include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/security/credentials/ssl/ssl_credentials.h" #include "src/core/lib/security/security_connector/ssl_utils_config.h" #include "test/core/end2end/end2end_tests.h" #include "test/core/util/port.h" @@ -38,6 +39,7 @@ struct fullstack_secure_fixture_data { std::string localaddr; + grpc_tls_version tls_version; bool server_credential_reloaded = false; }; @@ -77,12 +79,14 @@ ssl_server_certificate_config_callback( } static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( - grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/) { + grpc_channel_args* /*client_args*/, grpc_channel_args* /*server_args*/, + grpc_tls_version tls_version) { grpc_end2end_test_fixture f; int port = grpc_pick_unused_port_or_die(); fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data(); memset(&f, 0, sizeof(f)); ffd->localaddr = grpc_core::JoinHostPort("localhost", port); + ffd->tls_version = tls_version; f.fixture_data = ffd; f.cq = grpc_completion_queue_create_for_next(nullptr); @@ -91,6 +95,18 @@ static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack( return f; } +static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_secure_fullstack(client_args, server_args, + grpc_tls_version::TLS1_2); +} + +static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3( + grpc_channel_args* client_args, grpc_channel_args* server_args) { + return chttp2_create_fixture_secure_fullstack(client_args, server_args, + grpc_tls_version::TLS1_3); +} + static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/, const grpc_metadata* /*md*/, size_t /*md_count*/, @@ -138,6 +154,15 @@ static void chttp2_init_client_simple_ssl_secure_fullstack( grpc_end2end_test_fixture* f, grpc_channel_args* client_args) { grpc_channel_credentials* ssl_creds = grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); + if (f != nullptr && ssl_creds != nullptr) { + // Set the min and max TLS version. + grpc_ssl_credentials* creds = + reinterpret_cast(ssl_creds); + fullstack_secure_fixture_data* ffd = + static_cast(f->fixture_data); + creds->set_min_tls_version(ffd->tls_version); + creds->set_max_tls_version(ffd->tls_version); + } grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), @@ -168,6 +193,15 @@ static void chttp2_init_server_simple_ssl_secure_fullstack( ssl_server_certificate_config_callback, f->fixture_data); grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create_with_options(options); + if (f != nullptr && ssl_creds != nullptr) { + // Set the min and max TLS version. + grpc_ssl_server_credentials* creds = + reinterpret_cast(ssl_creds); + fullstack_secure_fixture_data* ffd = + static_cast(f->fixture_data); + creds->set_min_tls_version(ffd->tls_version); + creds->set_max_tls_version(ffd->tls_version); + } if (fail_server_auth_check(server_args)) { grpc_auth_metadata_processor processor = {process_auth_failure, nullptr, nullptr}; @@ -179,12 +213,22 @@ static void chttp2_init_server_simple_ssl_secure_fullstack( /* All test configurations */ static grpc_end2end_test_config configs[] = { - {"chttp2/simple_ssl_fullstack", + {"chttp2/simple_ssl_fullstack_tls1_2", FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, - "foo.test.google.fr", chttp2_create_fixture_secure_fullstack, + "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2, + chttp2_init_client_simple_ssl_secure_fullstack, + chttp2_init_server_simple_ssl_secure_fullstack, + chttp2_tear_down_secure_fullstack}, + {"chttp2/simple_ssl_fullstack_tls1_3", + FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | + FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | + FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL | + FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER | + FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST, + "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3, chttp2_init_client_simple_ssl_secure_fullstack, chttp2_init_server_simple_ssl_secure_fullstack, chttp2_tear_down_secure_fullstack}, diff --git a/test/core/end2end/tests/filter_call_init_fails.cc b/test/core/end2end/tests/filter_call_init_fails.cc index a5785d7338b..062c83632b5 100644 --- a/test/core/end2end/tests/filter_call_init_fails.cc +++ b/test/core/end2end/tests/filter_call_init_fails.cc @@ -508,7 +508,15 @@ void filter_call_init_fails(grpc_end2end_test_config config) { g_enable_client_channel_filter = true; test_client_channel_filter(config); g_enable_client_channel_filter = false; - if (config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL) { + // If the client handshake completes before the server handshake and the + // client is able to send application data before the server handshake + // completes, then testing the CLIENT_SUBCHANNEL filter will cause the server + // to hang waiting for the final handshake message from the client. This + // handshake message will never arrive because it would have been sent with + // the first application data message, which failed because of the filter. + if ((config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL) && + !(config.feature_mask & + FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST)) { gpr_log(GPR_INFO, "Testing CLIENT_SUBCHANNEL filter."); g_enable_client_subchannel_filter = true; test_client_subchannel_filter(config); diff --git a/test/core/tsi/ssl_transport_security_test.cc b/test/core/tsi/ssl_transport_security_test.cc index 26d4d477050..586200358ec 100644 --- a/test/core/tsi/ssl_transport_security_test.cc +++ b/test/core/tsi/ssl_transport_security_test.cc @@ -55,6 +55,9 @@ const size_t kSessionTicketEncryptionKeySize = 80; const size_t kSessionTicketEncryptionKeySize = 48; #endif +// Indicates the TLS version used for the test. +static tsi_tls_version test_tls_version = tsi_tls_version::TSI_TLS1_3; + typedef enum AlpnMode { NO_ALPN, ALPN_CLIENT_NO_SERVER, @@ -127,6 +130,8 @@ static void ssl_test_setup_handshakers(tsi_test_fixture* fixture) { if (ssl_fixture->session_cache != nullptr) { client_options.session_cache = ssl_fixture->session_cache; } + client_options.min_tls_version = test_tls_version; + client_options.max_tls_version = test_tls_version; GPR_ASSERT(tsi_create_ssl_client_handshaker_factory_with_options( &client_options, &ssl_fixture->client_handshaker_factory) == TSI_OK); @@ -159,6 +164,8 @@ static void ssl_test_setup_handshakers(tsi_test_fixture* fixture) { } server_options.session_ticket_key = ssl_fixture->session_ticket_key; server_options.session_ticket_key_size = ssl_fixture->session_ticket_key_size; + server_options.min_tls_version = test_tls_version; + server_options.max_tls_version = test_tls_version; GPR_ASSERT(tsi_create_ssl_server_handshaker_factory_with_options( &server_options, &ssl_fixture->server_handshaker_factory) == TSI_OK); @@ -317,10 +324,18 @@ static void ssl_test_check_handshaker_peers(tsi_test_fixture* fixture) { GPR_ASSERT(ssl_fixture->key_cert_lib != nullptr); ssl_key_cert_lib* key_cert_lib = ssl_fixture->key_cert_lib; tsi_peer peer; - bool expect_success = + // In TLS 1.3, the client-side handshake succeeds even if the client sends a + // bad certificate. In such a case, the server would fail the TLS handshake + // and send an alert to the client as the first application data message. In + // TLS 1.2, the client-side handshake will fail if the client sends a bad + // certificate. + bool expect_server_success = !(key_cert_lib->use_bad_server_cert || (key_cert_lib->use_bad_client_cert && ssl_fixture->force_client_auth)); - if (expect_success) { + bool expect_client_success = test_tls_version == tsi_tls_version::TSI_TLS1_2 + ? expect_server_success + : !key_cert_lib->use_bad_server_cert; + if (expect_client_success) { GPR_ASSERT(tsi_handshaker_result_extract_peer( ssl_fixture->base.client_result, &peer) == TSI_OK); check_session_reusage(ssl_fixture, &peer); @@ -338,7 +353,7 @@ static void ssl_test_check_handshaker_peers(tsi_test_fixture* fixture) { } else { GPR_ASSERT(ssl_fixture->base.client_result == nullptr); } - if (expect_success) { + if (expect_server_success) { GPR_ASSERT(tsi_handshaker_result_extract_peer( ssl_fixture->base.server_result, &peer) == TSI_OK); check_session_reusage(ssl_fixture, &peer); @@ -421,7 +436,7 @@ static tsi_test_fixture* ssl_tsi_test_fixture_create() { ssl_tsi_test_fixture* ssl_fixture = static_cast(gpr_zalloc(sizeof(*ssl_fixture))); tsi_test_fixture_init(&ssl_fixture->base); - ssl_fixture->base.test_unused_bytes = false; + ssl_fixture->base.test_unused_bytes = true; ssl_fixture->base.vtable = &vtable; /* Create ssl_key_cert_lib. */ ssl_key_cert_lib* key_cert_lib = @@ -494,6 +509,9 @@ void ssl_tsi_test_do_handshake_tiny_handshake_buffer() { gpr_log(GPR_INFO, "ssl_tsi_test_do_handshake_tiny_handshake_buffer"); tsi_test_fixture* fixture = ssl_tsi_test_fixture_create(); fixture->handshake_buffer_size = TSI_TEST_TINY_HANDSHAKE_BUFFER_SIZE; + // Handshake buffer is too small to hold both handshake messages and the + // unused bytes. + fixture->test_unused_bytes = false; tsi_test_do_handshake(fixture); tsi_test_fixture_destroy(fixture); } @@ -951,31 +969,39 @@ void ssl_tsi_test_extract_cert_chain() { int main(int argc, char** argv) { grpc::testing::TestEnvironment env(argc, argv); grpc_init(); - ssl_tsi_test_do_handshake_tiny_handshake_buffer(); - ssl_tsi_test_do_handshake_small_handshake_buffer(); - ssl_tsi_test_do_handshake(); - ssl_tsi_test_do_handshake_with_root_store(); - ssl_tsi_test_do_handshake_with_client_authentication(); - ssl_tsi_test_do_handshake_with_client_authentication_and_root_store(); - ssl_tsi_test_do_handshake_with_server_name_indication_exact_domain(); - ssl_tsi_test_do_handshake_with_server_name_indication_wild_star_domain(); - ssl_tsi_test_do_handshake_with_wrong_server_name_indication(); - ssl_tsi_test_do_handshake_with_bad_server_cert(); - ssl_tsi_test_do_handshake_with_bad_client_cert(); + const size_t number_tls_versions = 2; + const tsi_tls_version tls_versions[] = {tsi_tls_version::TSI_TLS1_2, + tsi_tls_version::TSI_TLS1_3}; + for (size_t i = 0; i < number_tls_versions; i++) { + // Set the TLS version to be used in the tests. + test_tls_version = tls_versions[i]; + // Run all the tests using that TLS version for both the client and server. + ssl_tsi_test_do_handshake_tiny_handshake_buffer(); + ssl_tsi_test_do_handshake_small_handshake_buffer(); + ssl_tsi_test_do_handshake(); + ssl_tsi_test_do_handshake_with_root_store(); + ssl_tsi_test_do_handshake_with_client_authentication(); + ssl_tsi_test_do_handshake_with_client_authentication_and_root_store(); + ssl_tsi_test_do_handshake_with_server_name_indication_exact_domain(); + ssl_tsi_test_do_handshake_with_server_name_indication_wild_star_domain(); + ssl_tsi_test_do_handshake_with_wrong_server_name_indication(); + ssl_tsi_test_do_handshake_with_bad_server_cert(); + ssl_tsi_test_do_handshake_with_bad_client_cert(); #ifdef OPENSSL_IS_BORINGSSL - // BoringSSL and OpenSSL have different behaviors on mismatched ALPN. - ssl_tsi_test_do_handshake_alpn_client_no_server(); - ssl_tsi_test_do_handshake_alpn_client_server_mismatch(); + // BoringSSL and OpenSSL have different behaviors on mismatched ALPN. + ssl_tsi_test_do_handshake_alpn_client_no_server(); + ssl_tsi_test_do_handshake_alpn_client_server_mismatch(); #endif - ssl_tsi_test_do_handshake_alpn_server_no_client(); - ssl_tsi_test_do_handshake_alpn_client_server_ok(); - ssl_tsi_test_do_handshake_session_cache(); - ssl_tsi_test_do_round_trip_for_all_configs(); - ssl_tsi_test_do_round_trip_odd_buffer_size(); - ssl_tsi_test_handshaker_factory_internals(); - ssl_tsi_test_duplicate_root_certificates(); - ssl_tsi_test_extract_x509_subject_names(); - ssl_tsi_test_extract_cert_chain(); + ssl_tsi_test_do_handshake_alpn_server_no_client(); + ssl_tsi_test_do_handshake_alpn_client_server_ok(); + ssl_tsi_test_do_handshake_session_cache(); + ssl_tsi_test_do_round_trip_for_all_configs(); + ssl_tsi_test_do_round_trip_odd_buffer_size(); + ssl_tsi_test_handshaker_factory_internals(); + ssl_tsi_test_duplicate_root_certificates(); + ssl_tsi_test_extract_x509_subject_names(); + ssl_tsi_test_extract_cert_chain(); + } grpc_shutdown(); return 0; }