diff --git a/BUILD b/BUILD index 7bb959170e1..57a0401f7ea 100644 --- a/BUILD +++ b/BUILD @@ -3096,12 +3096,14 @@ grpc_cc_library( "//src/core:lib/security/security_connector/ssl_utils_config.cc", "//src/core:tsi/ssl/key_logging/ssl_key_logging.cc", "//src/core:tsi/ssl_transport_security.cc", + "//src/core:tsi/ssl_transport_security_utils.cc", ], hdrs = [ "//src/core:lib/security/security_connector/ssl_utils.h", "//src/core:lib/security/security_connector/ssl_utils_config.h", "//src/core:tsi/ssl/key_logging/ssl_key_logging.h", "//src/core:tsi/ssl_transport_security.h", + "//src/core:tsi/ssl_transport_security_utils.h", ], external_deps = [ "absl/base:core_headers", diff --git a/CMakeLists.txt b/CMakeLists.txt index 440abc1cd4f..b5c6d87ab3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1153,6 +1153,9 @@ if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx ssl_transport_security_test) endif() + if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) + add_dependencies(buildtests_cxx ssl_transport_security_utils_test) + endif() if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx stack_tracer_test) endif() @@ -2361,6 +2364,7 @@ add_library(grpc src/core/tsi/ssl/session_cache/ssl_session_cache.cc src/core/tsi/ssl/session_cache/ssl_session_openssl.cc src/core/tsi/ssl_transport_security.cc + src/core/tsi/ssl_transport_security_utils.cc src/core/tsi/transport_security.cc src/core/tsi/transport_security_grpc.cc ) @@ -18749,6 +18753,45 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) ) +endif() +endif() +if(gRPC_BUILD_TESTS) +if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) + + add_executable(ssl_transport_security_utils_test + test/core/tsi/ssl_transport_security_utils_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc + ) + + target_include_directories(ssl_transport_security_utils_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} + ) + + target_link_libraries(ssl_transport_security_utils_test + ${_gRPC_BASELIB_LIBRARIES} + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ZLIB_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util + ) + + endif() endif() if(gRPC_BUILD_TESTS) diff --git a/Makefile b/Makefile index 26b74f4e647..d129f51d6dd 100644 --- a/Makefile +++ b/Makefile @@ -1659,6 +1659,7 @@ LIBGRPC_SRC = \ src/core/tsi/ssl/session_cache/ssl_session_cache.cc \ src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \ src/core/tsi/ssl_transport_security.cc \ + src/core/tsi/ssl_transport_security_utils.cc \ src/core/tsi/transport_security.cc \ src/core/tsi/transport_security_grpc.cc \ @@ -3255,6 +3256,7 @@ src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc: $(OPENSSL_DEP) src/core/tsi/ssl/session_cache/ssl_session_cache.cc: $(OPENSSL_DEP) src/core/tsi/ssl/session_cache/ssl_session_openssl.cc: $(OPENSSL_DEP) src/core/tsi/ssl_transport_security.cc: $(OPENSSL_DEP) +src/core/tsi/ssl_transport_security_utils.cc: $(OPENSSL_DEP) endif .PHONY: all strip tools dep_error openssl_dep_error openssl_dep_message git_update stop buildtests buildtests_c buildtests_cxx test test_c test_cxx install install_c install_cxx install_csharp install-static install-certs strip strip-shared strip-static strip_c strip-shared_c strip-static_c strip_cxx strip-shared_cxx strip-static_cxx dep_c dep_cxx bins_dep_c bins_dep_cxx clean diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index a38cd258763..3debebdb0e2 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -1039,6 +1039,7 @@ libs: - src/core/tsi/ssl/session_cache/ssl_session.h - src/core/tsi/ssl/session_cache/ssl_session_cache.h - src/core/tsi/ssl_transport_security.h + - src/core/tsi/ssl_transport_security_utils.h - src/core/tsi/ssl_types.h - src/core/tsi/transport_security.h - src/core/tsi/transport_security_grpc.h @@ -1746,6 +1747,7 @@ libs: - src/core/tsi/ssl/session_cache/ssl_session_cache.cc - src/core/tsi/ssl/session_cache/ssl_session_openssl.cc - src/core/tsi/ssl_transport_security.cc + - src/core/tsi/ssl_transport_security_utils.cc - src/core/tsi/transport_security.cc - src/core/tsi/transport_security_grpc.cc deps: @@ -10803,6 +10805,19 @@ targets: - linux - posix - mac +- name: ssl_transport_security_utils_test + gtest: true + build: test + language: c++ + headers: [] + src: + - test/core/tsi/ssl_transport_security_utils_test.cc + deps: + - grpc_test_util + platforms: + - linux + - posix + - mac - name: stack_tracer_test gtest: true build: test diff --git a/config.m4 b/config.m4 index 8bfc67c1735..626e3687e37 100644 --- a/config.m4 +++ b/config.m4 @@ -783,6 +783,7 @@ if test "$PHP_GRPC" != "no"; then src/core/tsi/ssl/session_cache/ssl_session_cache.cc \ src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \ src/core/tsi/ssl_transport_security.cc \ + src/core/tsi/ssl_transport_security_utils.cc \ src/core/tsi/transport_security.cc \ src/core/tsi/transport_security_grpc.cc \ src/php/ext/grpc/byte_buffer.c \ diff --git a/config.w32 b/config.w32 index 712e29de2ab..76aefbd9ed0 100644 --- a/config.w32 +++ b/config.w32 @@ -749,6 +749,7 @@ if (PHP_GRPC != "no") { "src\\core\\tsi\\ssl\\session_cache\\ssl_session_cache.cc " + "src\\core\\tsi\\ssl\\session_cache\\ssl_session_openssl.cc " + "src\\core\\tsi\\ssl_transport_security.cc " + + "src\\core\\tsi\\ssl_transport_security_utils.cc " + "src\\core\\tsi\\transport_security.cc " + "src\\core\\tsi\\transport_security_grpc.cc " + "src\\php\\ext\\grpc\\byte_buffer.c " + diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index e15cb638a8b..e76cdb438ca 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -1020,6 +1020,7 @@ Pod::Spec.new do |s| 'src/core/tsi/ssl/session_cache/ssl_session.h', 'src/core/tsi/ssl/session_cache/ssl_session_cache.h', 'src/core/tsi/ssl_transport_security.h', + 'src/core/tsi/ssl_transport_security_utils.h', 'src/core/tsi/ssl_types.h', 'src/core/tsi/transport_security.h', 'src/core/tsi/transport_security_grpc.h', @@ -1907,6 +1908,7 @@ Pod::Spec.new do |s| 'src/core/tsi/ssl/session_cache/ssl_session.h', 'src/core/tsi/ssl/session_cache/ssl_session_cache.h', 'src/core/tsi/ssl_transport_security.h', + 'src/core/tsi/ssl_transport_security_utils.h', 'src/core/tsi/ssl_types.h', 'src/core/tsi/transport_security.h', 'src/core/tsi/transport_security_grpc.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index fe267a73d83..5dc33ecf241 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -1687,6 +1687,8 @@ Pod::Spec.new do |s| 'src/core/tsi/ssl/session_cache/ssl_session_openssl.cc', 'src/core/tsi/ssl_transport_security.cc', 'src/core/tsi/ssl_transport_security.h', + 'src/core/tsi/ssl_transport_security_utils.cc', + 'src/core/tsi/ssl_transport_security_utils.h', 'src/core/tsi/ssl_types.h', 'src/core/tsi/transport_security.cc', 'src/core/tsi/transport_security.h', @@ -2538,6 +2540,7 @@ Pod::Spec.new do |s| 'src/core/tsi/ssl/session_cache/ssl_session.h', 'src/core/tsi/ssl/session_cache/ssl_session_cache.h', 'src/core/tsi/ssl_transport_security.h', + 'src/core/tsi/ssl_transport_security_utils.h', 'src/core/tsi/ssl_types.h', 'src/core/tsi/transport_security.h', 'src/core/tsi/transport_security_grpc.h', diff --git a/grpc.gemspec b/grpc.gemspec index f45663d0677..cbcae9e3290 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -1598,6 +1598,8 @@ Gem::Specification.new do |s| s.files += %w( src/core/tsi/ssl/session_cache/ssl_session_openssl.cc ) s.files += %w( src/core/tsi/ssl_transport_security.cc ) s.files += %w( src/core/tsi/ssl_transport_security.h ) + s.files += %w( src/core/tsi/ssl_transport_security_utils.cc ) + s.files += %w( src/core/tsi/ssl_transport_security_utils.h ) s.files += %w( src/core/tsi/ssl_types.h ) s.files += %w( src/core/tsi/transport_security.cc ) s.files += %w( src/core/tsi/transport_security.h ) diff --git a/grpc.gyp b/grpc.gyp index c5da8cbfc7e..7af6ae01910 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -1071,6 +1071,7 @@ 'src/core/tsi/ssl/session_cache/ssl_session_cache.cc', 'src/core/tsi/ssl/session_cache/ssl_session_openssl.cc', 'src/core/tsi/ssl_transport_security.cc', + 'src/core/tsi/ssl_transport_security_utils.cc', 'src/core/tsi/transport_security.cc', 'src/core/tsi/transport_security_grpc.cc', ], diff --git a/package.xml b/package.xml index fd574363123..bd51bae7e38 100644 --- a/package.xml +++ b/package.xml @@ -1580,6 +1580,8 @@ + + diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc index 1554ee39fd6..720c1b039a8 100644 --- a/src/core/tsi/ssl_transport_security.cc +++ b/src/core/tsi/ssl_transport_security.cc @@ -58,6 +58,7 @@ #include "src/core/lib/gpr/useful.h" #include "src/core/tsi/ssl/key_logging/ssl_key_logging.h" #include "src/core/tsi/ssl/session_cache/ssl_session_cache.h" +#include "src/core/tsi/ssl_transport_security_utils.h" #include "src/core/tsi/ssl_types.h" #include "src/core/tsi/transport_security.h" @@ -195,31 +196,6 @@ static void init_openssl(void) { /* --- Ssl utils. ---*/ -static const char* ssl_error_string(int error) { - switch (error) { - case SSL_ERROR_NONE: - return "SSL_ERROR_NONE"; - case SSL_ERROR_ZERO_RETURN: - return "SSL_ERROR_ZERO_RETURN"; - case SSL_ERROR_WANT_READ: - return "SSL_ERROR_WANT_READ"; - case SSL_ERROR_WANT_WRITE: - return "SSL_ERROR_WANT_WRITE"; - case SSL_ERROR_WANT_CONNECT: - return "SSL_ERROR_WANT_CONNECT"; - case SSL_ERROR_WANT_ACCEPT: - return "SSL_ERROR_WANT_ACCEPT"; - case SSL_ERROR_WANT_X509_LOOKUP: - return "SSL_ERROR_WANT_X509_LOOKUP"; - case SSL_ERROR_SYSCALL: - return "SSL_ERROR_SYSCALL"; - case SSL_ERROR_SSL: - return "SSL_ERROR_SSL"; - default: - return "Unknown error"; - } -} - /* TODO(jboeuf): Remove when we are past the debugging phase with this code. */ static void ssl_log_where_info(const SSL* ssl, int where, int flag, const char* msg) { @@ -523,71 +499,6 @@ static tsi_result peer_from_x509(X509* cert, int include_certificate_type, return result; } -/* Logs the SSL error stack. */ -static void log_ssl_error_stack(void) { - unsigned long err; - while ((err = ERR_get_error()) != 0) { - char details[256]; - ERR_error_string_n(static_cast(err), details, sizeof(details)); - gpr_log(GPR_ERROR, "%s", details); - } -} - -/* Performs an SSL_read and handle errors. */ -static tsi_result do_ssl_read(SSL* ssl, unsigned char* unprotected_bytes, - size_t* unprotected_bytes_size) { - GPR_ASSERT(*unprotected_bytes_size <= INT_MAX); - ERR_clear_error(); - int read_from_ssl = SSL_read(ssl, unprotected_bytes, - static_cast(*unprotected_bytes_size)); - if (read_from_ssl <= 0) { - read_from_ssl = SSL_get_error(ssl, read_from_ssl); - switch (read_from_ssl) { - case SSL_ERROR_ZERO_RETURN: /* Received a close_notify alert. */ - case SSL_ERROR_WANT_READ: /* We need more data to finish the frame. */ - *unprotected_bytes_size = 0; - return TSI_OK; - case SSL_ERROR_WANT_WRITE: - gpr_log( - GPR_ERROR, - "Peer tried to renegotiate SSL connection. This is unsupported."); - return TSI_UNIMPLEMENTED; - case SSL_ERROR_SSL: - gpr_log(GPR_ERROR, "Corruption detected."); - log_ssl_error_stack(); - return TSI_DATA_CORRUPTED; - default: - gpr_log(GPR_ERROR, "SSL_read failed with error %s.", - ssl_error_string(read_from_ssl)); - return TSI_PROTOCOL_FAILURE; - } - } - *unprotected_bytes_size = static_cast(read_from_ssl); - return TSI_OK; -} - -/* Performs an SSL_write and handle errors. */ -static tsi_result do_ssl_write(SSL* ssl, unsigned char* unprotected_bytes, - size_t unprotected_bytes_size) { - GPR_ASSERT(unprotected_bytes_size <= INT_MAX); - ERR_clear_error(); - int ssl_write_result = SSL_write(ssl, unprotected_bytes, - static_cast(unprotected_bytes_size)); - if (ssl_write_result < 0) { - ssl_write_result = SSL_get_error(ssl, ssl_write_result); - if (ssl_write_result == SSL_ERROR_WANT_READ) { - gpr_log(GPR_ERROR, - "Peer tried to renegotiate SSL connection. This is unsupported."); - return TSI_UNIMPLEMENTED; - } else { - gpr_log(GPR_ERROR, "SSL_write failed with error %s.", - ssl_error_string(ssl_write_result)); - return TSI_INTERNAL_ERROR; - } - } - return TSI_OK; -} - /* Loads an in-memory PEM certificate chain into the SSL context. */ static tsi_result ssl_ctx_use_certificate_chain(SSL_CTX* context, const char* pem_cert_chain, @@ -1058,130 +969,33 @@ static tsi_result ssl_protector_protect(tsi_frame_protector* self, size_t* protected_output_frames_size) { tsi_ssl_frame_protector* impl = reinterpret_cast(self); - int read_from_ssl; - size_t available; - tsi_result result = TSI_OK; - - /* First see if we have some pending data in the SSL BIO. */ - int pending_in_ssl = static_cast(BIO_pending(impl->network_io)); - if (pending_in_ssl > 0) { - *unprotected_bytes_size = 0; - GPR_ASSERT(*protected_output_frames_size <= INT_MAX); - read_from_ssl = BIO_read(impl->network_io, protected_output_frames, - static_cast(*protected_output_frames_size)); - if (read_from_ssl < 0) { - gpr_log(GPR_ERROR, - "Could not read from BIO even though some data is pending"); - return TSI_INTERNAL_ERROR; - } - *protected_output_frames_size = static_cast(read_from_ssl); - return TSI_OK; - } - /* Now see if we can send a complete frame. */ - available = impl->buffer_size - impl->buffer_offset; - if (available > *unprotected_bytes_size) { - /* If we cannot, just copy the data in our internal buffer. */ - memcpy(impl->buffer + impl->buffer_offset, unprotected_bytes, - *unprotected_bytes_size); - impl->buffer_offset += *unprotected_bytes_size; - *protected_output_frames_size = 0; - return TSI_OK; - } - - /* If we can, prepare the buffer, send it to SSL_write and read. */ - memcpy(impl->buffer + impl->buffer_offset, unprotected_bytes, available); - result = do_ssl_write(impl->ssl, impl->buffer, impl->buffer_size); - if (result != TSI_OK) return result; - - GPR_ASSERT(*protected_output_frames_size <= INT_MAX); - read_from_ssl = BIO_read(impl->network_io, protected_output_frames, - static_cast(*protected_output_frames_size)); - if (read_from_ssl < 0) { - gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write."); - return TSI_INTERNAL_ERROR; - } - *protected_output_frames_size = static_cast(read_from_ssl); - *unprotected_bytes_size = available; - impl->buffer_offset = 0; - return TSI_OK; + return grpc_core::SslProtectorProtect( + unprotected_bytes, impl->buffer_size, impl->buffer_offset, impl->buffer, + impl->ssl, impl->network_io, unprotected_bytes_size, + protected_output_frames, protected_output_frames_size); } static tsi_result ssl_protector_protect_flush( tsi_frame_protector* self, unsigned char* protected_output_frames, size_t* protected_output_frames_size, size_t* still_pending_size) { - tsi_result result = TSI_OK; tsi_ssl_frame_protector* impl = reinterpret_cast(self); - int read_from_ssl = 0; - int pending; - - if (impl->buffer_offset != 0) { - result = do_ssl_write(impl->ssl, impl->buffer, impl->buffer_offset); - if (result != TSI_OK) return result; - impl->buffer_offset = 0; - } - - pending = static_cast(BIO_pending(impl->network_io)); - GPR_ASSERT(pending >= 0); - *still_pending_size = static_cast(pending); - if (*still_pending_size == 0) return TSI_OK; - - GPR_ASSERT(*protected_output_frames_size <= INT_MAX); - read_from_ssl = BIO_read(impl->network_io, protected_output_frames, - static_cast(*protected_output_frames_size)); - if (read_from_ssl <= 0) { - gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write."); - return TSI_INTERNAL_ERROR; - } - *protected_output_frames_size = static_cast(read_from_ssl); - pending = static_cast(BIO_pending(impl->network_io)); - GPR_ASSERT(pending >= 0); - *still_pending_size = static_cast(pending); - return TSI_OK; + return grpc_core::SslProtectorProtectFlush( + impl->buffer_offset, impl->buffer, impl->ssl, impl->network_io, + protected_output_frames, protected_output_frames_size, + still_pending_size); } static tsi_result ssl_protector_unprotect( tsi_frame_protector* self, const unsigned char* protected_frames_bytes, size_t* protected_frames_bytes_size, unsigned char* unprotected_bytes, size_t* unprotected_bytes_size) { - tsi_result result = TSI_OK; - int written_into_ssl = 0; - size_t output_bytes_size = *unprotected_bytes_size; - size_t output_bytes_offset = 0; tsi_ssl_frame_protector* impl = reinterpret_cast(self); - - /* First, try to read remaining data from ssl. */ - result = do_ssl_read(impl->ssl, unprotected_bytes, unprotected_bytes_size); - if (result != TSI_OK) return result; - if (*unprotected_bytes_size == output_bytes_size) { - /* We have read everything we could and cannot process any more input. */ - *protected_frames_bytes_size = 0; - return TSI_OK; - } - output_bytes_offset = *unprotected_bytes_size; - unprotected_bytes += output_bytes_offset; - *unprotected_bytes_size = output_bytes_size - output_bytes_offset; - - /* Then, try to write some data to ssl. */ - GPR_ASSERT(*protected_frames_bytes_size <= INT_MAX); - written_into_ssl = BIO_write(impl->network_io, protected_frames_bytes, - static_cast(*protected_frames_bytes_size)); - if (written_into_ssl < 0) { - gpr_log(GPR_ERROR, "Sending protected frame to ssl failed with %d", - written_into_ssl); - return TSI_INTERNAL_ERROR; - } - *protected_frames_bytes_size = static_cast(written_into_ssl); - - /* Now try to read some data again. */ - result = do_ssl_read(impl->ssl, unprotected_bytes, unprotected_bytes_size); - if (result == TSI_OK) { - /* Don't forget to output the total number of bytes read. */ - *unprotected_bytes_size += output_bytes_offset; - } - return result; + return grpc_core::SslProtectorUnprotect( + protected_frames_bytes, impl->ssl, impl->network_io, + protected_frames_bytes_size, unprotected_bytes, unprotected_bytes_size); } static void ssl_protector_destroy(tsi_frame_protector* self) { @@ -1499,9 +1313,10 @@ static tsi_result ssl_handshaker_do_handshake(tsi_ssl_handshaker* impl, char err_str[256]; ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str)); gpr_log(GPR_ERROR, "Handshake failed with fatal error %s: %s.", - ssl_error_string(ssl_result), err_str); + grpc_core::SslErrorString(ssl_result), err_str); if (error != nullptr) { - *error = absl::StrCat(ssl_error_string(ssl_result), ": ", err_str); + *error = absl::StrCat(grpc_core::SslErrorString(ssl_result), ": ", + err_str); } impl->result = TSI_PROTOCOL_FAILURE; return impl->result; @@ -1742,7 +1557,7 @@ static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client, if (ssl_result != SSL_ERROR_WANT_READ) { gpr_log(GPR_ERROR, "Unexpected error received from first SSL_do_handshake call: %s", - ssl_error_string(ssl_result)); + grpc_core::SslErrorString(ssl_result)); SSL_free(ssl); BIO_free(network_io); return TSI_INTERNAL_ERROR; @@ -2049,7 +1864,7 @@ tsi_result tsi_create_ssl_client_handshaker_factory_with_options( ssl_context = SSL_CTX_new(TLSv1_2_method()); #endif if (ssl_context == nullptr) { - log_ssl_error_stack(); + grpc_core::LogSslErrorStack(); gpr_log(GPR_ERROR, "Could not create ssl context."); return TSI_INVALID_ARGUMENT; } @@ -2254,7 +2069,7 @@ tsi_result tsi_create_ssl_server_handshaker_factory_with_options( impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method()); #endif if (impl->ssl_contexts[i] == nullptr) { - log_ssl_error_stack(); + grpc_core::LogSslErrorStack(); gpr_log(GPR_ERROR, "Could not create ssl context."); result = TSI_OUT_OF_RESOURCES; break; diff --git a/src/core/tsi/ssl_transport_security.h b/src/core/tsi/ssl_transport_security.h index 47d699177ea..a2b7a097b1a 100644 --- a/src/core/tsi/ssl_transport_security.h +++ b/src/core/tsi/ssl_transport_security.h @@ -28,6 +28,7 @@ #include #include "src/core/tsi/ssl/key_logging/ssl_key_logging.h" +#include "src/core/tsi/ssl_transport_security_utils.h" #include "src/core/tsi/transport_security_interface.h" /* Value for the TSI_CERTIFICATE_TYPE_PEER_PROPERTY property for X509 certs. */ diff --git a/src/core/tsi/ssl_transport_security_utils.cc b/src/core/tsi/ssl_transport_security_utils.cc new file mode 100644 index 00000000000..50a5d933f40 --- /dev/null +++ b/src/core/tsi/ssl_transport_security_utils.cc @@ -0,0 +1,250 @@ +/* + * + * Copyright 2022 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include "src/core/tsi/ssl_transport_security_utils.h" + +#include +#include +#include + +#include "src/core/tsi/transport_security_interface.h" + +namespace grpc_core { + +const char* SslErrorString(int error) { + switch (error) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + default: + return "Unknown error"; + } +} + +void LogSslErrorStack(void) { + unsigned long err; + while ((err = ERR_get_error()) != 0) { + char details[256]; + ERR_error_string_n(static_cast(err), details, sizeof(details)); + gpr_log(GPR_ERROR, "%s", details); + } +} + +tsi_result DoSslWrite(SSL* ssl, unsigned char* unprotected_bytes, + size_t unprotected_bytes_size) { + GPR_ASSERT(unprotected_bytes_size <= INT_MAX); + ERR_clear_error(); + int ssl_write_result = SSL_write(ssl, unprotected_bytes, + static_cast(unprotected_bytes_size)); + if (ssl_write_result < 0) { + ssl_write_result = SSL_get_error(ssl, ssl_write_result); + if (ssl_write_result == SSL_ERROR_WANT_READ) { + gpr_log(GPR_ERROR, + "Peer tried to renegotiate SSL connection. This is unsupported."); + return TSI_UNIMPLEMENTED; + } else { + gpr_log(GPR_ERROR, "SSL_write failed with error %s.", + SslErrorString(ssl_write_result)); + return TSI_INTERNAL_ERROR; + } + } + return TSI_OK; +} + +tsi_result DoSslRead(SSL* ssl, unsigned char* unprotected_bytes, + size_t* unprotected_bytes_size) { + GPR_ASSERT(*unprotected_bytes_size <= INT_MAX); + ERR_clear_error(); + int read_from_ssl = SSL_read(ssl, unprotected_bytes, + static_cast(*unprotected_bytes_size)); + if (read_from_ssl <= 0) { + read_from_ssl = SSL_get_error(ssl, read_from_ssl); + switch (read_from_ssl) { + case SSL_ERROR_ZERO_RETURN: /* Received a close_notify alert. */ + case SSL_ERROR_WANT_READ: /* We need more data to finish the frame. */ + *unprotected_bytes_size = 0; + return TSI_OK; + case SSL_ERROR_WANT_WRITE: + gpr_log( + GPR_ERROR, + "Peer tried to renegotiate SSL connection. This is unsupported."); + return TSI_UNIMPLEMENTED; + case SSL_ERROR_SSL: + gpr_log(GPR_ERROR, "Corruption detected."); + LogSslErrorStack(); + return TSI_DATA_CORRUPTED; + default: + gpr_log(GPR_ERROR, "SSL_read failed with error %s.", + SslErrorString(read_from_ssl)); + return TSI_PROTOCOL_FAILURE; + } + } + *unprotected_bytes_size = static_cast(read_from_ssl); + return TSI_OK; +} + +/* --- tsi_frame_protector util methods implementation. ---*/ +tsi_result SslProtectorProtect(const unsigned char* unprotected_bytes, + const size_t buffer_size, size_t& buffer_offset, + unsigned char* buffer, SSL* ssl, BIO* network_io, + size_t* unprotected_bytes_size, + unsigned char* protected_output_frames, + size_t* protected_output_frames_size) { + int read_from_ssl; + size_t available; + tsi_result result = TSI_OK; + + /* First see if we have some pending data in the SSL BIO. */ + int pending_in_ssl = static_cast(BIO_pending(network_io)); + if (pending_in_ssl > 0) { + *unprotected_bytes_size = 0; + GPR_ASSERT(*protected_output_frames_size <= INT_MAX); + read_from_ssl = BIO_read(network_io, protected_output_frames, + static_cast(*protected_output_frames_size)); + if (read_from_ssl < 0) { + gpr_log(GPR_ERROR, + "Could not read from BIO even though some data is pending"); + return TSI_INTERNAL_ERROR; + } + *protected_output_frames_size = static_cast(read_from_ssl); + return TSI_OK; + } + + /* Now see if we can send a complete frame. */ + available = buffer_size - buffer_offset; + if (available > *unprotected_bytes_size) { + /* If we cannot, just copy the data in our internal buffer. */ + memcpy(buffer + buffer_offset, unprotected_bytes, *unprotected_bytes_size); + buffer_offset += *unprotected_bytes_size; + *protected_output_frames_size = 0; + return TSI_OK; + } + + /* If we can, prepare the buffer, send it to SSL_write and read. */ + memcpy(buffer + buffer_offset, unprotected_bytes, available); + result = DoSslWrite(ssl, buffer, buffer_size); + if (result != TSI_OK) return result; + + GPR_ASSERT(*protected_output_frames_size <= INT_MAX); + read_from_ssl = BIO_read(network_io, protected_output_frames, + static_cast(*protected_output_frames_size)); + if (read_from_ssl < 0) { + gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write."); + return TSI_INTERNAL_ERROR; + } + *protected_output_frames_size = static_cast(read_from_ssl); + *unprotected_bytes_size = available; + buffer_offset = 0; + return TSI_OK; +} + +tsi_result SslProtectorProtectFlush(size_t& buffer_offset, + unsigned char* buffer, SSL* ssl, + BIO* network_io, + unsigned char* protected_output_frames, + size_t* protected_output_frames_size, + size_t* still_pending_size) { + tsi_result result = TSI_OK; + int read_from_ssl = 0; + int pending; + + if (buffer_offset != 0) { + result = DoSslWrite(ssl, buffer, buffer_offset); + if (result != TSI_OK) return result; + buffer_offset = 0; + } + + pending = static_cast(BIO_pending(network_io)); + GPR_ASSERT(pending >= 0); + *still_pending_size = static_cast(pending); + if (*still_pending_size == 0) return TSI_OK; + + GPR_ASSERT(*protected_output_frames_size <= INT_MAX); + read_from_ssl = BIO_read(network_io, protected_output_frames, + static_cast(*protected_output_frames_size)); + if (read_from_ssl <= 0) { + gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write."); + return TSI_INTERNAL_ERROR; + } + *protected_output_frames_size = static_cast(read_from_ssl); + pending = static_cast(BIO_pending(network_io)); + GPR_ASSERT(pending >= 0); + *still_pending_size = static_cast(pending); + return TSI_OK; +} + +tsi_result SslProtectorUnprotect(const unsigned char* protected_frames_bytes, + SSL* ssl, BIO* network_io, + size_t* protected_frames_bytes_size, + unsigned char* unprotected_bytes, + size_t* unprotected_bytes_size) { + tsi_result result = TSI_OK; + int written_into_ssl = 0; + size_t output_bytes_size = *unprotected_bytes_size; + size_t output_bytes_offset = 0; + + /* First, try to read remaining data from ssl. */ + result = DoSslRead(ssl, unprotected_bytes, unprotected_bytes_size); + if (result != TSI_OK) return result; + if (*unprotected_bytes_size == output_bytes_size) { + /* We have read everything we could and cannot process any more input. */ + *protected_frames_bytes_size = 0; + return TSI_OK; + } + output_bytes_offset = *unprotected_bytes_size; + unprotected_bytes += output_bytes_offset; + *unprotected_bytes_size = output_bytes_size - output_bytes_offset; + + /* Then, try to write some data to ssl. */ + GPR_ASSERT(*protected_frames_bytes_size <= INT_MAX); + written_into_ssl = BIO_write(network_io, protected_frames_bytes, + static_cast(*protected_frames_bytes_size)); + if (written_into_ssl < 0) { + gpr_log(GPR_ERROR, "Sending protected frame to ssl failed with %d", + written_into_ssl); + return TSI_INTERNAL_ERROR; + } + *protected_frames_bytes_size = static_cast(written_into_ssl); + + /* Now try to read some data again. */ + result = DoSslRead(ssl, unprotected_bytes, unprotected_bytes_size); + if (result == TSI_OK) { + /* Don't forget to output the total number of bytes read. */ + *unprotected_bytes_size += output_bytes_offset; + } + return result; +} + +} // namespace grpc_core diff --git a/src/core/tsi/ssl_transport_security_utils.h b/src/core/tsi/ssl_transport_security_utils.h new file mode 100644 index 00000000000..becee21356d --- /dev/null +++ b/src/core/tsi/ssl_transport_security_utils.h @@ -0,0 +1,147 @@ +/* + * + * Copyright 2022 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H +#define GRPC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H + +#include + +#include + +#include "absl/strings/string_view.h" + +#include + +#include "src/core/tsi/ssl/key_logging/ssl_key_logging.h" +#include "src/core/tsi/transport_security_interface.h" + +namespace grpc_core { + +// Converts an SSL error status code to a readable string. +// +// error: the SSL error status code. +// +// return: the corresponding status string. +const char* SslErrorString(int error); + +// Logs the SSL error stack. +void LogSslErrorStack(void); + +// Performs an SSL_write and handle errors. +// +// ssl: the SSL object to write to. +// unprotected_bytes: the buffer containing the bytes for writing to |ssl|. +// unprotected_bytes_size: the size of the buffer |unprotected_bytes|. +// +// return: TSI_OK if the write operation succeeds or corresponding TSI errors. +tsi_result DoSslWrite(SSL* ssl, unsigned char* unprotected_bytes, + size_t unprotected_bytes_size); + +// Performs an SSL_read and handle errors. +// +// ssl: the SSL object to read from. +// unprotected_bytes: the buffer to which this function will populate the read +// result from |ssl|. +// unprotected_bytes_size: the maximum size of the buffer |unprotected_bytes|. +// This will be populated with the size of the bytes +// read from |ssl| if this function returns TSI_OK. +// +// return: TSI_OK if the write operation succeeds or corresponding TSI errors. +tsi_result DoSslRead(SSL* ssl, unsigned char* unprotected_bytes, + size_t* unprotected_bytes_size); + +// Builds a maximum-size TLS frame if there is enough (|buffer_offset| + +// |unprotected_bytes_size| >= |buffers_size|) data. Otherwise it copies the +// |unprotected_bytes| into |buffer| and returns TSI_OK. +// +// unprotected_bytes: pointing to the buffer containing the plaintext to be +// protected. +// buffer_size: the size of |buffer|. If |buffer_offset| equals |buffer_size|, +// then we have enough data to create a TLS frame. +// buffer_offset: the offset of |buffer|. The data in |buffer| up to +// |buffer_offset| are valid data. This will be updated whenever +// new data are copied into |buffer|. +// buffer: the buffer holding the data that has not been sent for protecting. +// ssl: the |SSL| object that protects the data. +// network_io: the |BIO| object associated with |ssl|. +// unprotected_bytes_size: the size of the unprotected plaintext. This will be +// populated with the size of data that is consumed by +// this function. Caller can use this to see the size of +// unconsumed data in |unprotected_bytes|. +// protected_output_frames: the TLS frames built out of the plaintext. +// protected_output_frames_size: the size of the TLS frames built. +// +// return: TSI_OK if either successfully created a TSI frame or copied the +// |unprotected_data| into |buffer|. Returns corresponding TSI errors +// otherwise. +tsi_result SslProtectorProtect(const unsigned char* unprotected_bytes, + const size_t buffer_size, size_t& buffer_offset, + unsigned char* buffer, SSL* ssl, BIO* network_io, + std::size_t* unprotected_bytes_size, + unsigned char* protected_output_frames, + size_t* protected_output_frames_size); + +// Builds a TLS frame out of the remaining plaintext bytes that's left in +// buffer. Populates the size of the remianing TLS frame to +// |still_pending_size|. +// +// buffer_size: the size of |buffer|. If |buffer_offset| equals |buffer_size|, +// then we have enough data to create a TLS frame. +// buffer_offset: the offset of |buffer|. The data in |buffer| up to +// |buffer_offset| are valid data. This will be updated whenever +// new data are copied into |buffer|. +// buffer: the buffer holding the data that has not been sent for protecting. +// ssl: the |SSL| object that protects the data. +// network_io: the |BIO| object associated with |ssl|. +// protected_output_frames: the TLS frames built out of the plaintext. +// protected_output_frames_size: the size of the TLS frames built. +// still_pending_size: the size of the bytes that remains in |network_io|. +// +// return: TSI_OK if successfully created a TSI frame. Returns corresponding TSI +// errors otherwise. +tsi_result SslProtectorProtectFlush(size_t& buffer_offset, + unsigned char* buffer, SSL* ssl, + BIO* network_io, + unsigned char* protected_output_frames, + size_t* protected_output_frames_size, + size_t* still_pending_size); + +// Extracts the plaintext from a TLS frame. +// +// protected_frames_bytes: the TLS frame to extract plaintext from. +// ssl: the |SSL| object that protects the data. +// network_io: the |BIO| object associated with |ssl|. +// unprotected_bytes_size: the size of the unprotected plaintext. This will be +// populated with the size of data that is consumed by +// this function. Caller can use this to see the size of +// unconsumed data in |unprotected_bytes|. +// protected_output_frames: the TLS frames built out of the plaintext. +// protected_output_frames_size: the size of the TLS frames built. +// +// return: TSI_OK if either successfully created a TSI frame or copied the +// |unprotected_data| into |buffer|. Returns corresponding TSI errors +// otherwise. +tsi_result SslProtectorUnprotect(const unsigned char* protected_frames_bytes, + SSL* ssl, BIO* network_io, + size_t* protected_frames_bytes_size, + unsigned char* unprotected_bytes, + size_t* unprotected_bytes_size); + +} // namespace grpc_core + +#endif // GRPC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index 681beb2cfd0..2959512df71 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -758,6 +758,7 @@ CORE_SOURCE_FILES = [ 'src/core/tsi/ssl/session_cache/ssl_session_cache.cc', 'src/core/tsi/ssl/session_cache/ssl_session_openssl.cc', 'src/core/tsi/ssl_transport_security.cc', + 'src/core/tsi/ssl_transport_security_utils.cc', 'src/core/tsi/transport_security.cc', 'src/core/tsi/transport_security_grpc.cc', 'third_party/abseil-cpp/absl/base/internal/cycleclock.cc', diff --git a/test/core/tsi/BUILD b/test/core/tsi/BUILD index 6beaebce6dc..f6e469a7efd 100644 --- a/test/core/tsi/BUILD +++ b/test/core/tsi/BUILD @@ -57,6 +57,19 @@ grpc_cc_test( ], ) +grpc_cc_test( + name = "ssl_transport_security_utils_test", + srcs = ["ssl_transport_security_utils_test.cc"], + external_deps = ["gtest"], + language = "C++", + tags = ["no_windows"], + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:grpc_test_util", + ], +) + grpc_cc_test( name = "ssl_transport_security_test", srcs = ["ssl_transport_security_test.cc"], diff --git a/test/core/tsi/ssl_transport_security_utils_test.cc b/test/core/tsi/ssl_transport_security_utils_test.cc new file mode 100644 index 00000000000..332c517e92a --- /dev/null +++ b/test/core/tsi/ssl_transport_security_utils_test.cc @@ -0,0 +1,436 @@ +// +// Copyright 2022 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/tsi/ssl_transport_security_utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" + +#include "src/core/tsi/transport_security.h" +#include "src/core/tsi/transport_security_interface.h" +#include "test/core/util/test_config.h" + +namespace grpc_core { +namespace testing { + +using ::testing::ContainerEq; +using ::testing::NotNull; +using ::testing::TestWithParam; +using ::testing::ValuesIn; + +constexpr std::size_t kMaxPlaintextBytesPerTlsRecord = 16384; +constexpr std::size_t kTlsRecordOverhead = 100; +constexpr std::array kTestPlainTextSizeArray = { + 1, 1000, kMaxPlaintextBytesPerTlsRecord, + kMaxPlaintextBytesPerTlsRecord + 1000}; + +struct FrameProtectorUtilTestData { + std::size_t plaintext_size; + std::size_t expected_encrypted_bytes_size; +}; + +// Generates the testing data |FrameProtectorUtilTestData|. +std::vector GenerateTestData() { + std::vector data; + for (std::size_t plaintext_size : kTestPlainTextSizeArray) { + std::size_t expected_size = plaintext_size + kTlsRecordOverhead; + if (plaintext_size > kMaxPlaintextBytesPerTlsRecord) { + expected_size = kMaxPlaintextBytesPerTlsRecord + kTlsRecordOverhead; + } + data.push_back({plaintext_size, expected_size}); + } + return data; +} + +class FlowTest : public TestWithParam { + protected: + static void SetUpTestSuite() { +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + OPENSSL_init_ssl(/*opts=*/0, /*settings=*/nullptr); +#else + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +#endif + } + + // Used for debugging. + static int VerifySucceed(X509_STORE_CTX* /*store_ctx*/, void* /*arg*/) { + return 1; + } + + // Drives two SSL objects to finish a complete handshake with the hard-coded + // credentials and outputs those two SSL objects to `out_client` and + // `out_server` respectively. + static absl::Status DoHandshake(SSL** out_client, SSL** out_server) { + if (out_client == nullptr || out_server == nullptr) { + return absl::InvalidArgumentError( + "Client and server SSL object must not be null."); + } + std::string cert_pem = + "-----BEGIN CERTIFICATE-----\n" + "MIICZzCCAdCgAwIBAgIIN18/ctj3wpAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\n" + "ChMOR29vZ2xlIFRFU1RJTkcxDzANBgNVBAMTBnRlc3RDQTAeFw0xNTAxMDEwMDAw\n" + "MDBaFw0yNTAxMDEwMDAwMDBaMC8xFzAVBgNVBAoTDkdvb2dsZSBURVNUSU5HMRQw\n" + "EgYDVQQDDAt0ZXN0X2NlcnRfMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" + "20oOyI+fNCCeHJ3DNjGooPPP43Q6emhVvuWD8ppta582Rgxq/4j1bl9cPHdoCdyy\n" + "HsWFVUZzscj2qhClmlBAMEA595OU2NX2d81nSih5dwZWLMRQkEIzyxUR7Vee3eyo\n" + "nQD4HSamaevMSv79WTUBCozEGITqWnjYA152KUbA/IsCAwEAAaOBkDCBjTAOBgNV\n" + "HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud\n" + "EwEB/wQCMAAwGQYDVR0OBBIEECnFWP/UkDrV+SoXra58k64wGwYDVR0jBBQwEoAQ\n" + "p7JSbajiTZaIRUDSV1C81jAWBgNVHREEDzANggt0ZXN0X2NlcnRfMTANBgkqhkiG\n" + "9w0BAQsFAAOBgQCpJJssfN62T3G5z+5SBB+9KCzXnGxcTHtaTJkb04KLe+19EwhV\n" + "yRY4lZadKHjcNS6GCBogd069wNFUVYOU9VI7uUiEPdcTO+VRV5MYW0wjSi1zlkBZ\n" + "e8OAfYVeGUMfvThFpJ41f8vZ6GHgg95Lwv+Zh89SL8g1J3RWll9YVG8HWw==\n" + "-----END CERTIFICATE-----\n"; + std::string key_pem = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXQIBAAKBgQDbSg7Ij580IJ4cncM2Maig88/jdDp6aFW+5YPymm1rnzZGDGr/\n" + "iPVuX1w8d2gJ3LIexYVVRnOxyPaqEKWaUEAwQDn3k5TY1fZ3zWdKKHl3BlYsxFCQ\n" + "QjPLFRHtV57d7KidAPgdJqZp68xK/v1ZNQEKjMQYhOpaeNgDXnYpRsD8iwIDAQAB\n" + "AoGAbq4kZApJeo/z/dGK0/GggQxOIylo0puSm7VQMcTL8YP8asKdxrgj2D99WG+U\n" + "LVYc+PcM4wuaHWOnTBL24roaitCNhrpIsJfWDkexzHXMj622SYlUcCuwsfjYOEyw\n" + "ntoNAnh0o4S+beYAfzT5VHCh4is9G9u+mwKYiGpJXROrYUECQQD4eq4nuGq3mfYJ\n" + "B0+md30paDVVCyBsuZTAtnu3MbRjMXy5LLE+vhno5nocvVSTOv3QC7Wk6yAa8/bG\n" + "iPT/MWixAkEA4e0zqPGo8tSimVv/1ei8Chyb+YqdSx+Oj5eZpa6X/KB/C1uS1tm6\n" + "DTgHW2GUhV4ypqdGH+t8quprJUtFuzqH+wJBAMRiicSg789eouMt4RjrdYPFdela\n" + "Gu1zm4rYb10xrqV7Vl0wYoH5U5cMmdSfGvomdLX6mzzWDJDg4ti1JBWRonECQQCD\n" + "Umtq0j1QGQUCe5Vz8zoJ7qNDI61WU1t8X7Rxt9CkiW4PXgU2WYxpzp2IImpAM4bh\n" + "k+2Q9EKc3nG1VdGMiPMtAkARkQF+pL8SBrUoh8G8glCam0brh3tW/cdW8L4UGTNF\n" + "2ZKC/LFH6DQBjYs3UXjvMGJxz4k9LysyY6o2Nf1JG6/L\n" + "-----END RSA PRIVATE KEY-----\n"; + + // Create the context objects. + SSL_CTX* client_ctx = nullptr; + SSL_CTX* server_ctx = nullptr; +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + client_ctx = SSL_CTX_new(TLS_method()); + server_ctx = SSL_CTX_new(TLS_method()); +#else + client_ctx = SSL_CTX_new(TLSv1_2_method()); + server_ctx = SSL_CTX_new(TLSv1_2_method()); +#endif + + BIO* client_cert_bio(BIO_new_mem_buf(cert_pem.c_str(), cert_pem.size())); + X509* client_cert = PEM_read_bio_X509(client_cert_bio, /*x=*/nullptr, + /*cb=*/nullptr, /*u=*/nullptr); + BIO* client_key_bio(BIO_new_mem_buf(key_pem.c_str(), key_pem.size())); + EVP_PKEY* client_key = + PEM_read_bio_PrivateKey(client_key_bio, /*x=*/nullptr, + /*cb=*/nullptr, /*u=*/nullptr); + + BIO* server_cert_bio(BIO_new_mem_buf(cert_pem.c_str(), cert_pem.size())); + X509* server_cert = PEM_read_bio_X509(server_cert_bio, /*x=*/nullptr, + /*cb=*/nullptr, /*u=*/nullptr); + BIO* server_key_bio(BIO_new_mem_buf(key_pem.c_str(), key_pem.size())); + EVP_PKEY* server_key = + PEM_read_bio_PrivateKey(server_key_bio, /*x=*/nullptr, + /*cb=*/nullptr, /*u=*/nullptr); + + // Set both client and server certificate and private key. + SSL_CTX_use_certificate(client_ctx, client_cert); + SSL_CTX_use_PrivateKey(client_ctx, client_key); + SSL_CTX_use_certificate(server_ctx, server_cert); + SSL_CTX_use_PrivateKey(server_ctx, server_key); + + EVP_PKEY_free(client_key); + BIO_free(client_key_bio); + X509_free(client_cert); + BIO_free(client_cert_bio); + + EVP_PKEY_free(server_key); + BIO_free(server_key_bio); + X509_free(server_cert); + BIO_free(server_cert_bio); + + // Configure both client and server to request (and accept any) + // certificate but fail if none is sent. + SSL_CTX_set_verify(client_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + /*callback=*/nullptr); + SSL_CTX_set_cert_verify_callback(client_ctx, VerifySucceed, + /*arg=*/nullptr); + SSL_CTX_set_verify(server_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + /*callback=*/nullptr); + SSL_CTX_set_cert_verify_callback(server_ctx, VerifySucceed, + /*arg=*/nullptr); + + // Turns off the session caching. + SSL_CTX_set_session_cache_mode(client_ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 +#if defined(TLS1_3_VERSION) + // Set both the min and max TLS version to 1.3 + SSL_CTX_set_min_proto_version(client_ctx, TLS1_3_VERSION); + SSL_CTX_set_min_proto_version(server_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(client_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(server_ctx, TLS1_3_VERSION); +#else + SSL_CTX_set_min_proto_version(client_ctx, TLS1_2_VERSION); + SSL_CTX_set_min_proto_version(server_ctx, TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(client_ctx, TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(server_ctx, TLS1_2_VERSION); +#endif +#endif + + // Create client and server connection objects and configure their BIOs. + SSL* client(SSL_new(client_ctx)); + SSL* server(SSL_new(server_ctx)); + + SSL_CTX_free(client_ctx); + SSL_CTX_free(server_ctx); + + // Turns off issuance of session tickets by servers. + SSL_set_options(client, SSL_OP_NO_TICKET); + SSL_set_options(server, SSL_OP_NO_TICKET); + + SSL_set_connect_state(client); + SSL_set_accept_state(server); + BIO* bio1; + BIO* bio2; + BIO_new_bio_pair(&bio1, /*writebuf1=*/0, &bio2, /*writebuf2=*/0); + SSL_set_bio(client, bio1, bio1); + SSL_set_bio(server, bio2, bio2); + + // Drive both the client and server handshake operations to completion. + while (true) { + int client_ret = SSL_do_handshake(client); + int client_err = SSL_get_error(client, client_ret); + if (client_err != SSL_ERROR_NONE && client_err != SSL_ERROR_WANT_READ && + client_err != SSL_ERROR_WANT_WRITE) { + return absl::InternalError(absl::StrCat("Client error:", client_err)); + } + + int server_ret = SSL_do_handshake(server); + int server_err = SSL_get_error(server, server_ret); + if (server_err != SSL_ERROR_NONE && server_err != SSL_ERROR_WANT_READ && + server_err != SSL_ERROR_WANT_WRITE) { + return absl::InternalError(absl::StrCat("Server error:", server_err)); + } + if (client_ret == 1 && server_ret == 1) { + break; + } + } + + *out_client = client; + *out_server = server; + + return absl::OkStatus(); + } + + static std::size_t CalculateRecordSizeFromHeader(uint8_t fourth_header_byte, + uint8_t fifth_header_byte) { + return (static_cast(fourth_header_byte & 0xff) << 8) + + static_cast(fifth_header_byte & 0xff); + } + + void SetUp() override { + ASSERT_EQ(DoHandshake(&client_ssl, &server_ssl), absl::OkStatus()); + + ASSERT_THAT(client_ssl, NotNull()); + ASSERT_THAT(server_ssl, NotNull()); + + BIO* client_ssl_bio = nullptr; + ASSERT_EQ(BIO_new_bio_pair(&client_bio, /*writebuf1=*/0, &client_ssl_bio, + /*writebuf2=*/0), + 1); + SSL_set_bio(client_ssl, client_ssl_bio, client_ssl_bio); + + BIO* server_ssl_bio = nullptr; + ASSERT_EQ(BIO_new_bio_pair(&server_bio, /*writebuf1=*/0, &server_ssl_bio, + /*writebuf2=*/0), + 1); + SSL_set_bio(server_ssl, server_ssl_bio, server_ssl_bio); + + client_buffer_offset = 0; + client_buffer.resize(kMaxPlaintextBytesPerTlsRecord); + server_buffer_offset = 0; + server_buffer.resize(kMaxPlaintextBytesPerTlsRecord); + } + + void TearDown() override { + BIO_free(client_bio); + SSL_free(client_ssl); + BIO_free(server_bio); + SSL_free(server_ssl); + } + + SSL* client_ssl; + BIO* client_bio; + std::vector client_buffer; + std::size_t client_buffer_offset; + + SSL* server_ssl; + BIO* server_bio; + std::vector server_buffer; + std::size_t server_buffer_offset; +}; + +// This test consists of two tests, namely for each combination of parameters, +// we create a message on one side, protect it (encrypt it), and send it to +// the other side for unprotecting (decrypting). +TEST_P(FlowTest, + ClientMessageToServerCanBeProtectedAndUnprotectedSuccessfully) { + std::vector unprotected_bytes(GetParam().plaintext_size, 'a'); + std::size_t unprotected_bytes_size = unprotected_bytes.size(); + + std::vector protected_output_frames( + GetParam().expected_encrypted_bytes_size); + std::size_t protected_output_frames_size = protected_output_frames.size(); + + EXPECT_EQ(SslProtectorProtect(unprotected_bytes.data(), client_buffer.size(), + client_buffer_offset, client_buffer.data(), + client_ssl, client_bio, &unprotected_bytes_size, + protected_output_frames.data(), + &protected_output_frames_size), + tsi_result::TSI_OK); + + // If |GetParam().plaintext_size| is larger than the inner client_buffer size + // (kMaxPlaintextBytesPerTlsRecord), then |Protect| will copy up to + // |kMaxPlaintextBytesPerTlsRecord| bytes and output the protected + // frame. Otherwise we need to manually flush the copied data in order + // to get the protected frame. + if (GetParam().plaintext_size >= kMaxPlaintextBytesPerTlsRecord) { + EXPECT_EQ(unprotected_bytes_size, kMaxPlaintextBytesPerTlsRecord); + } else { + EXPECT_EQ(unprotected_bytes_size, GetParam().plaintext_size); + EXPECT_EQ(protected_output_frames_size, 0); + protected_output_frames_size = protected_output_frames.size(); + + std::size_t still_pending_size = 0; + EXPECT_EQ(SslProtectorProtectFlush( + client_buffer_offset, client_buffer.data(), client_ssl, + client_bio, protected_output_frames.data(), + &protected_output_frames_size, &still_pending_size), + tsi_result::TSI_OK); + EXPECT_EQ(still_pending_size, 0); + } + + // The first three bytes are always 0x17, 0x03, 0x03. + EXPECT_EQ(protected_output_frames[0], '\x17'); + EXPECT_EQ(protected_output_frames[1], '\x03'); + EXPECT_EQ(protected_output_frames[2], '\x03'); + // The next two bytes are the size of the record, which is 5 bytes less + // than the size of the whole frame. + EXPECT_EQ(CalculateRecordSizeFromHeader(protected_output_frames[3], + protected_output_frames[4]), + protected_output_frames_size - 5); + + std::vector unprotected_output_bytes(GetParam().plaintext_size); + std::size_t unprotected_output_bytes_size = unprotected_output_bytes.size(); + + // This frame should be decrypted by peer correctly. + EXPECT_EQ(SslProtectorUnprotect(protected_output_frames.data(), server_ssl, + server_bio, &protected_output_frames_size, + unprotected_output_bytes.data(), + &unprotected_output_bytes_size), + tsi_result::TSI_OK); + EXPECT_EQ(unprotected_output_bytes_size, unprotected_bytes_size); + unprotected_output_bytes.resize(unprotected_output_bytes_size); + unprotected_bytes.resize(unprotected_bytes_size); + EXPECT_THAT(unprotected_output_bytes, ContainerEq(unprotected_bytes)); +} + +TEST_P(FlowTest, + ServerMessageToClientCanBeProtectedAndUnprotectedSuccessfully) { + std::vector unprotected_bytes(GetParam().plaintext_size, 'a'); + std::size_t unprotected_bytes_size = unprotected_bytes.size(); + + std::vector protected_output_frames( + GetParam().expected_encrypted_bytes_size); + std::size_t protected_output_frames_size = protected_output_frames.size(); + + EXPECT_EQ(SslProtectorProtect(unprotected_bytes.data(), server_buffer.size(), + server_buffer_offset, server_buffer.data(), + server_ssl, server_bio, &unprotected_bytes_size, + protected_output_frames.data(), + &protected_output_frames_size), + tsi_result::TSI_OK); + + // If |GetParam().plaintext_size| is larger than the inner server_buffer size + // (kMaxPlaintextBytesPerTlsRecord), then |Protect| will copy up to + // |kMaxPlaintextBytesPerTlsRecord| bytes and output the protected + // frame. Otherwise we need to manually flush the copied data in order + // to get the protected frame. + if (GetParam().plaintext_size >= kMaxPlaintextBytesPerTlsRecord) { + EXPECT_EQ(unprotected_bytes_size, kMaxPlaintextBytesPerTlsRecord); + } else { + EXPECT_EQ(unprotected_bytes_size, GetParam().plaintext_size); + EXPECT_EQ(protected_output_frames_size, 0); + protected_output_frames_size = protected_output_frames.size(); + + std::size_t still_pending_size = 0; + EXPECT_EQ(SslProtectorProtectFlush( + server_buffer_offset, server_buffer.data(), server_ssl, + server_bio, protected_output_frames.data(), + &protected_output_frames_size, &still_pending_size), + tsi_result::TSI_OK); + EXPECT_EQ(still_pending_size, 0); + } + + // The first three bytes are always 0x17, 0x03, 0x03. + EXPECT_EQ(protected_output_frames[0], '\x17'); + EXPECT_EQ(protected_output_frames[1], '\x03'); + EXPECT_EQ(protected_output_frames[2], '\x03'); + // The next two bytes are the size of the record, which is 5 bytes less + // than the size of the whole frame. + EXPECT_EQ(CalculateRecordSizeFromHeader(protected_output_frames[3], + protected_output_frames[4]), + protected_output_frames_size - 5); + + std::vector unprotected_output_bytes(GetParam().plaintext_size); + std::size_t unprotected_output_bytes_size = unprotected_output_bytes.size(); + + // This frame should be decrypted by peer correctly. + EXPECT_EQ(SslProtectorUnprotect(protected_output_frames.data(), client_ssl, + client_bio, &protected_output_frames_size, + unprotected_output_bytes.data(), + &unprotected_output_bytes_size), + tsi_result::TSI_OK); + EXPECT_EQ(unprotected_output_bytes_size, unprotected_bytes_size); + unprotected_output_bytes.resize(unprotected_output_bytes_size); + unprotected_bytes.resize(unprotected_bytes_size); + EXPECT_THAT(unprotected_output_bytes, ContainerEq(unprotected_bytes)); +} + +INSTANTIATE_TEST_SUITE_P(FrameProtectorUtil, FlowTest, + ValuesIn(GenerateTestData())); + +} // namespace testing +} // namespace grpc_core + +int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(&argc, argv); + ::testing::InitGoogleTest(&argc, argv); + grpc_init(); + int ret = RUN_ALL_TESTS(); + grpc_shutdown(); + return ret; +} diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index 5908488b828..6df73a4666c 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -2596,6 +2596,8 @@ src/core/tsi/ssl/session_cache/ssl_session_cache.h \ src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \ src/core/tsi/ssl_transport_security.cc \ src/core/tsi/ssl_transport_security.h \ +src/core/tsi/ssl_transport_security_utils.cc \ +src/core/tsi/ssl_transport_security_utils.h \ src/core/tsi/ssl_types.h \ src/core/tsi/transport_security.cc \ src/core/tsi/transport_security.h \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 0bc5ea0732e..bba1ac95db2 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -2377,6 +2377,8 @@ src/core/tsi/ssl/session_cache/ssl_session_cache.h \ src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \ src/core/tsi/ssl_transport_security.cc \ src/core/tsi/ssl_transport_security.h \ +src/core/tsi/ssl_transport_security_utils.cc \ +src/core/tsi/ssl_transport_security_utils.h \ src/core/tsi/ssl_types.h \ src/core/tsi/transport_security.cc \ src/core/tsi/transport_security.h \ diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index dcebed73079..54fb90311b9 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -6659,6 +6659,28 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "ssl_transport_security_utils_test", + "platforms": [ + "linux", + "mac", + "posix" + ], + "uses_polling": true + }, { "args": [], "benchmark": false,