Skip to content

Commit

Permalink
Merge pull request grpc#20316 from akshayku/support-engine
Browse files Browse the repository at this point in the history
Add support for OpenSSL ENGINE
  • Loading branch information
Jiangtao Li authored Jan 16, 2020
2 parents 35ca266 + cced243 commit c3aaf06
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 5 deletions.
39 changes: 39 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,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 $<INSTALL_INTERFACE:${gRPC_INSTALL_INCLUDEDIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
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
Expand Down
40 changes: 40 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3803,6 +3803,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 \
Expand Down Expand Up @@ -23361,6 +23400,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)
Expand Down
6 changes: 6 additions & 0 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1698,6 +1698,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
Expand Down
9 changes: 9 additions & 0 deletions grpc.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
99 changes: 96 additions & 3 deletions src/core/tsi/ssl_transport_security.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
extern "C" {
#include <openssl/bio.h>
#include <openssl/crypto.h> /* For OPENSSL_free */
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:<engine_id>:<key_id>
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<char*>(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;
Expand All @@ -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,
Expand Down
73 changes: 73 additions & 0 deletions test/core/end2end/engine_passthrough.cc
Original file line number Diff line number Diff line change
@@ -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 <openssl/bio.h>
#include <openssl/engine.h>
#include <openssl/pem.h>

#ifndef OPENSSL_IS_BORINGSSL

#include <stdio.h>
#include <string.h>

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
28 changes: 26 additions & 2 deletions test/core/end2end/h2_ssl_cert_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpcpp/support/string_ref.h>

#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/string.h"
Expand All @@ -37,6 +38,12 @@

#include <gtest/gtest.h>

extern "C" {
#include <openssl/crypto.h>
}

static grpc::string test_server1_key_id;

namespace grpc {
namespace testing {

Expand Down Expand Up @@ -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); \
Expand Down Expand Up @@ -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));

Expand Down

0 comments on commit c3aaf06

Please sign in to comment.