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,