From cced243a06d984bb04c71757564c69035dc16748 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Sat, 2 Nov 2019 12:11:50 -0700 Subject: [PATCH] OpenSslEngineSupport-200114 --- CMakeLists.txt | 39 ++++++++++ Makefile | 40 ++++++++++ build.yaml | 6 ++ grpc.gyp | 9 +++ src/core/tsi/ssl_transport_security.cc | 99 ++++++++++++++++++++++++- test/core/end2end/engine_passthrough.cc | 73 ++++++++++++++++++ test/core/end2end/h2_ssl_cert_test.cc | 28 ++++++- 7 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 test/core/end2end/engine_passthrough.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 04d0a4a1010..b7b7bdca2c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1312,6 +1312,45 @@ foreach(_hdr ) endforeach() +endif() +if(gRPC_BUILD_TESTS) + +add_library(engine_passthrough SHARED + test/core/end2end/engine_passthrough.cc +) + +set_target_properties(engine_passthrough PROPERTIES + VERSION ${gRPC_CORE_VERSION} + SOVERSION ${gRPC_CORE_SOVERSION} +) + +if(WIN32 AND MSVC) + set_target_properties(engine_passthrough PROPERTIES COMPILE_PDB_NAME "engine_passthrough" + COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + ) + if(gRPC_INSTALL) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/engine_passthrough.pdb + DESTINATION ${gRPC_INSTALL_LIBDIR} OPTIONAL + ) + endif() +endif() + +target_include_directories(engine_passthrough + PUBLIC $ $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} +) +target_link_libraries(engine_passthrough + ${_gRPC_ALLTARGETS_LIBRARIES} +) + + endif() add_library(gpr diff --git a/Makefile b/Makefile index d0f4bcc6ed9..1e498c1e1d1 100644 --- a/Makefile +++ b/Makefile @@ -3787,6 +3787,45 @@ endif endif +LIBENGINE_PASSTHROUGH_SRC = \ + test/core/end2end/engine_passthrough.cc \ + +PUBLIC_HEADERS_C += \ + +LIBENGINE_PASSTHROUGH_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBENGINE_PASSTHROUGH_SRC)))) + + +ifeq ($(NO_SECURE),true) + +# You can't build secure libraries if you don't have OpenSSL. + +$(LIBDIR)/$(CONFIG)/libengine_passthrough.a: openssl_dep_error + + +else + + +$(LIBDIR)/$(CONFIG)/libengine_passthrough.a: $(ZLIB_DEP) $(OPENSSL_DEP) $(CARES_DEP) $(ADDRESS_SORTING_DEP) $(UPB_DEP) $(GRPC_ABSEIL_DEP) $(LIBENGINE_PASSTHROUGH_OBJS) + $(E) "[AR] Creating $@" + $(Q) mkdir -p `dirname $@` + $(Q) rm -f $(LIBDIR)/$(CONFIG)/libengine_passthrough.a + $(Q) $(AR) $(AROPTS) $(LIBDIR)/$(CONFIG)/libengine_passthrough.a $(LIBENGINE_PASSTHROUGH_OBJS) +ifeq ($(SYSTEM),Darwin) + $(Q) ranlib -no_warning_for_no_symbols $(LIBDIR)/$(CONFIG)/libengine_passthrough.a +endif + + + + +endif + +ifneq ($(NO_SECURE),true) +ifneq ($(NO_DEPS),true) +-include $(LIBENGINE_PASSTHROUGH_OBJS:.o=.dep) +endif +endif + + LIBGPR_SRC = \ src/core/lib/gpr/alloc.cc \ src/core/lib/gpr/atm.cc \ @@ -23734,6 +23773,7 @@ test/core/end2end/data/server1_cert.cc: $(OPENSSL_DEP) test/core/end2end/data/server1_key.cc: $(OPENSSL_DEP) test/core/end2end/data/test_root_cert.cc: $(OPENSSL_DEP) test/core/end2end/end2end_tests.cc: $(OPENSSL_DEP) +test/core/end2end/engine_passthrough.cc: $(OPENSSL_DEP) test/core/end2end/tests/call_creds.cc: $(OPENSSL_DEP) test/core/security/oauth2_utils.cc: $(OPENSSL_DEP) test/core/tsi/alts/crypt/gsec_test_util.cc: $(OPENSSL_DEP) diff --git a/build.yaml b/build.yaml index a80fff96da8..b23596084c4 100644 --- a/build.yaml +++ b/build.yaml @@ -1699,6 +1699,12 @@ libs: filegroups: - grpc_test_util_base secure: true +- name: engine_passthrough + build: test + language: c + src: + - test/core/end2end/engine_passthrough.cc + dll: only - name: gpr build: all language: c diff --git a/grpc.gyp b/grpc.gyp index f9dc635632d..888512abe3f 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -429,6 +429,15 @@ 'src/core/ext/filters/http/server/http_server_filter.cc', ], }, + { + 'target_name': 'engine_passthrough', + 'type': 'static_library', + 'dependencies': [ + ], + 'sources': [ + 'test/core/end2end/engine_passthrough.cc', + ], + }, { 'target_name': 'gpr', 'type': 'static_library', diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc index 4da7035c24d..69ba9e89ba2 100644 --- a/src/core/tsi/ssl_transport_security.cc +++ b/src/core/tsi/ssl_transport_security.cc @@ -45,6 +45,7 @@ extern "C" { #include #include /* For OPENSSL_free */ +#include #include #include #include @@ -136,6 +137,9 @@ typedef struct { static gpr_once g_init_openssl_once = GPR_ONCE_INIT; static int g_ssl_ctx_ex_factory_index = -1; static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'}; +#ifndef OPENSSL_IS_BORINGSSL +static const char kSslEnginePrefix[] = "engine:"; +#endif #if OPENSSL_VERSION_NUMBER < 0x10100000 static gpr_mu* g_openssl_mutexes = nullptr; @@ -562,9 +566,84 @@ static tsi_result ssl_ctx_use_certificate_chain(SSL_CTX* context, return result; } -/* Loads an in-memory PEM private key into the SSL context. */ -static tsi_result ssl_ctx_use_private_key(SSL_CTX* context, const char* pem_key, - size_t pem_key_size) { +#ifndef OPENSSL_IS_BORINGSSL +static tsi_result ssl_ctx_use_engine_private_key(SSL_CTX* context, + const char* pem_key, + size_t pem_key_size) { + tsi_result result = TSI_OK; + EVP_PKEY* private_key = nullptr; + ENGINE* engine = nullptr; + char* engine_name = nullptr; + // Parse key which is in following format engine:: + do { + char* engine_start = (char*)pem_key + strlen(kSslEnginePrefix); + char* engine_end = (char*)strchr(engine_start, ':'); + if (engine_end == nullptr) { + result = TSI_INVALID_ARGUMENT; + break; + } + char* key_id = engine_end + 1; + int engine_name_length = engine_end - engine_start; + if (engine_name_length == 0) { + result = TSI_INVALID_ARGUMENT; + break; + } + engine_name = static_cast(gpr_zalloc(engine_name_length + 1)); + memcpy(engine_name, engine_start, engine_name_length); + gpr_log(GPR_DEBUG, "ENGINE key: %s", engine_name); + ENGINE_load_dynamic(); + engine = ENGINE_by_id(engine_name); + if (engine == nullptr) { + // If not available at ENGINE_DIR, use dynamic to load from + // current working directory. + engine = ENGINE_by_id("dynamic"); + if (engine == nullptr) { + gpr_log(GPR_ERROR, "Cannot load dynamic engine"); + result = TSI_INVALID_ARGUMENT; + break; + } + if (!ENGINE_ctrl_cmd_string(engine, "ID", engine_name, 0) || + !ENGINE_ctrl_cmd_string(engine, "DIR_LOAD", "2", 0) || + !ENGINE_ctrl_cmd_string(engine, "DIR_ADD", ".", 0) || + !ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0) || + !ENGINE_ctrl_cmd_string(engine, "LOAD", NULL, 0)) { + gpr_log(GPR_ERROR, "Cannot find engine"); + result = TSI_INVALID_ARGUMENT; + break; + } + } + if (!ENGINE_set_default(engine, ENGINE_METHOD_ALL)) { + gpr_log(GPR_ERROR, "ENGINE_set_default with ENGINE_METHOD_ALL failed"); + result = TSI_INVALID_ARGUMENT; + break; + } + if (!ENGINE_init(engine)) { + gpr_log(GPR_ERROR, "ENGINE_init failed"); + result = TSI_INVALID_ARGUMENT; + break; + } + private_key = ENGINE_load_private_key(engine, key_id, 0, 0); + if (private_key == nullptr) { + gpr_log(GPR_ERROR, "ENGINE_load_private_key failed"); + result = TSI_INVALID_ARGUMENT; + break; + } + if (!SSL_CTX_use_PrivateKey(context, private_key)) { + gpr_log(GPR_ERROR, "SSL_CTX_use_PrivateKey failed"); + result = TSI_INVALID_ARGUMENT; + break; + } + } while (0); + if (engine != nullptr) ENGINE_free(engine); + if (private_key != nullptr) EVP_PKEY_free(private_key); + if (engine_name != nullptr) gpr_free(engine_name); + return result; +} +#endif /* OPENSSL_IS_BORINGSSL */ + +static tsi_result ssl_ctx_use_pem_private_key(SSL_CTX* context, + const char* pem_key, + size_t pem_key_size) { tsi_result result = TSI_OK; EVP_PKEY* private_key = nullptr; BIO* pem; @@ -587,6 +666,20 @@ static tsi_result ssl_ctx_use_private_key(SSL_CTX* context, const char* pem_key, return result; } +/* Loads an in-memory PEM private key into the SSL context. */ +static tsi_result ssl_ctx_use_private_key(SSL_CTX* context, const char* pem_key, + size_t pem_key_size) { +// BoringSSL does not have ENGINE support +#ifndef OPENSSL_IS_BORINGSSL + if (strncmp(pem_key, kSslEnginePrefix, strlen(kSslEnginePrefix)) == 0) { + return ssl_ctx_use_engine_private_key(context, pem_key, pem_key_size); + } else +#endif /* OPENSSL_IS_BORINGSSL */ + { + return ssl_ctx_use_pem_private_key(context, pem_key, pem_key_size); + } +} + /* Loads in-memory PEM verification certs into the SSL context and optionally returns the verification cert names (root_names can be NULL). */ static tsi_result x509_store_load_certs(X509_STORE* cert_store, diff --git a/test/core/end2end/engine_passthrough.cc b/test/core/end2end/engine_passthrough.cc new file mode 100644 index 00000000000..79c3088389f --- /dev/null +++ b/test/core/end2end/engine_passthrough.cc @@ -0,0 +1,73 @@ +/* + * + * Copyright 2020 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. + * + */ + +// This is a sample openSSL engine which tests the openSSL +// engine plugability with gRPC. +// This sample engine expects KeyId to be actual PEM encoded +// key itself and just calls standard openSSL functions. + +#include +#include +#include + +#ifndef OPENSSL_IS_BORINGSSL + +#include +#include + +extern "C" { +static const char engine_id[] = "libengine_passthrough"; +static const char engine_name[] = "A passthrough engine for private keys"; +static int e_passthrough_idx = -1; + +static int e_passthrough_init(ENGINE* e) { + if (e_passthrough_idx < 0) { + e_passthrough_idx = ENGINE_get_ex_new_index(0, NULL, NULL, NULL, 0); + if (e_passthrough_idx < 0) return 0; + } + return 1; +} + +EVP_PKEY* e_passthrough_load_privkey(ENGINE* eng, const char* key_id, + UI_METHOD* ui_method, + void* callback_data) { + EVP_PKEY* pkey = NULL; + BIO* pem = BIO_new_mem_buf((void*)key_id, (int)(strlen(key_id))); + if (pem == NULL) return NULL; + pkey = PEM_read_bio_PrivateKey(pem, NULL, NULL, (void*)""); + BIO_free(pem); + return pkey; +} + +int passthrough_bind_helper(ENGINE* e, const char* id) { + if (id && strcmp(id, engine_id)) { + return 0; + } + if (!ENGINE_set_id(e, engine_id) || !ENGINE_set_name(e, engine_name) || + !ENGINE_set_flags(e, ENGINE_FLAGS_NO_REGISTER_ALL) || + !ENGINE_set_init_function(e, e_passthrough_init) || + !ENGINE_set_load_privkey_function(e, e_passthrough_load_privkey)) { + return 0; + } + return 1; +} + +IMPLEMENT_DYNAMIC_BIND_FN(passthrough_bind_helper) +IMPLEMENT_DYNAMIC_CHECK_FN() +} +#endif // OPENSSL_IS_BORINGSSL diff --git a/test/core/end2end/h2_ssl_cert_test.cc b/test/core/end2end/h2_ssl_cert_test.cc index 61f685045e7..a555c5c4349 100644 --- a/test/core/end2end/h2_ssl_cert_test.cc +++ b/test/core/end2end/h2_ssl_cert_test.cc @@ -23,6 +23,7 @@ #include #include +#include #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/gpr/string.h" @@ -37,6 +38,12 @@ #include +extern "C" { +#include +} + +static grpc::string test_server1_key_id; + namespace grpc { namespace testing { @@ -118,8 +125,14 @@ static int fail_server_auth_check(grpc_channel_args* server_args) { #define SERVER_INIT(REQUEST_TYPE) \ static void SERVER_INIT_NAME(REQUEST_TYPE)( \ grpc_end2end_test_fixture * f, grpc_channel_args * server_args) { \ - grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key, \ - test_server1_cert}; \ + grpc_ssl_pem_key_cert_pair pem_cert_key_pair; \ + if (!test_server1_key_id.empty()) { \ + pem_cert_key_pair.private_key = test_server1_key_id.c_str(); \ + pem_cert_key_pair.cert_chain = test_server1_cert; \ + } else { \ + pem_cert_key_pair.private_key = test_server1_key; \ + pem_cert_key_pair.cert_chain = test_server1_cert; \ + } \ grpc_server_credentials* ssl_creds = \ grpc_ssl_server_credentials_create_ex( \ test_root_cert, &pem_cert_key_pair, 1, REQUEST_TYPE, NULL); \ @@ -346,6 +359,17 @@ TEST_P(H2SslCertTest, SimpleRequestBody) { simple_request_body(fixture_, GetParam().result); } +#ifndef OPENSSL_IS_BORINGSSL +#if GPR_LINUX +TEST_P(H2SslCertTest, SimpleRequestBodyUseEngine) { + test_server1_key_id.clear(); + test_server1_key_id.append("engine:libengine_passthrough:"); + test_server1_key_id.append(test_server1_key); + simple_request_body(fixture_, GetParam().result); +} +#endif +#endif + INSTANTIATE_TEST_SUITE_P(H2SslCert, H2SslCertTest, ::testing::ValuesIn(configs));