diff options
30 files changed, 526 insertions, 38 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d92d7c0558..6371521f6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,6 +309,9 @@ endif() if(_gRPC_PLATFORM_LINUX) add_dependencies(buildtests_c handshake_server_with_readahead_handshaker) endif() +if(_gRPC_PLATFORM_LINUX) +add_dependencies(buildtests_c handshake_verify_peer_options) +endif() add_dependencies(buildtests_c histogram_test) add_dependencies(buildtests_c hpack_parser_test) add_dependencies(buildtests_c hpack_table_test) @@ -7491,6 +7494,37 @@ target_link_libraries(handshake_server_with_readahead_handshaker endif() endif (gRPC_BUILD_TESTS) if (gRPC_BUILD_TESTS) +if(_gRPC_PLATFORM_LINUX) + +add_executable(handshake_verify_peer_options + test/core/handshake/verify_peer_options.cc +) + + +target_include_directories(handshake_verify_peer_options + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + PRIVATE ${_gRPC_SSL_INCLUDE_DIR} + PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR} + PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR} + PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR} + PRIVATE ${_gRPC_CARES_INCLUDE_DIR} + PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR} + PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} +) + +target_link_libraries(handshake_verify_peer_options + ${_gRPC_SSL_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util + grpc + gpr_test_util + gpr +) + +endif() +endif (gRPC_BUILD_TESTS) +if (gRPC_BUILD_TESTS) add_executable(histogram_test test/core/util/histogram_test.cc @@ -1012,6 +1012,7 @@ grpc_verify_jwt: $(BINDIR)/$(CONFIG)/grpc_verify_jwt handshake_client: $(BINDIR)/$(CONFIG)/handshake_client handshake_server: $(BINDIR)/$(CONFIG)/handshake_server handshake_server_with_readahead_handshaker: $(BINDIR)/$(CONFIG)/handshake_server_with_readahead_handshaker +handshake_verify_peer_options: $(BINDIR)/$(CONFIG)/handshake_verify_peer_options histogram_test: $(BINDIR)/$(CONFIG)/histogram_test hpack_parser_fuzzer_test: $(BINDIR)/$(CONFIG)/hpack_parser_fuzzer_test hpack_parser_test: $(BINDIR)/$(CONFIG)/hpack_parser_test @@ -1454,6 +1455,7 @@ buildtests_c: privatelibs_c \ $(BINDIR)/$(CONFIG)/handshake_client \ $(BINDIR)/$(CONFIG)/handshake_server \ $(BINDIR)/$(CONFIG)/handshake_server_with_readahead_handshaker \ + $(BINDIR)/$(CONFIG)/handshake_verify_peer_options \ $(BINDIR)/$(CONFIG)/histogram_test \ $(BINDIR)/$(CONFIG)/hpack_parser_test \ $(BINDIR)/$(CONFIG)/hpack_table_test \ @@ -2009,6 +2011,8 @@ test_c: buildtests_c $(Q) $(BINDIR)/$(CONFIG)/handshake_server || ( echo test handshake_server failed ; exit 1 ) $(E) "[RUN] Testing handshake_server_with_readahead_handshaker" $(Q) $(BINDIR)/$(CONFIG)/handshake_server_with_readahead_handshaker || ( echo test handshake_server_with_readahead_handshaker failed ; exit 1 ) + $(E) "[RUN] Testing handshake_verify_peer_options" + $(Q) $(BINDIR)/$(CONFIG)/handshake_verify_peer_options || ( echo test handshake_verify_peer_options failed ; exit 1 ) $(E) "[RUN] Testing histogram_test" $(Q) $(BINDIR)/$(CONFIG)/histogram_test || ( echo test histogram_test failed ; exit 1 ) $(E) "[RUN] Testing hpack_parser_test" @@ -12469,6 +12473,38 @@ endif endif +HANDSHAKE_VERIFY_PEER_OPTIONS_SRC = \ + test/core/handshake/verify_peer_options.cc \ + +HANDSHAKE_VERIFY_PEER_OPTIONS_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(HANDSHAKE_VERIFY_PEER_OPTIONS_SRC)))) +ifeq ($(NO_SECURE),true) + +# You can't build secure targets if you don't have OpenSSL. + +$(BINDIR)/$(CONFIG)/handshake_verify_peer_options: openssl_dep_error + +else + + + +$(BINDIR)/$(CONFIG)/handshake_verify_peer_options: $(HANDSHAKE_VERIFY_PEER_OPTIONS_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a + $(E) "[LD] Linking $@" + $(Q) mkdir -p `dirname $@` + $(Q) $(LD) $(LDFLAGS) $(HANDSHAKE_VERIFY_PEER_OPTIONS_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/handshake_verify_peer_options + +endif + +$(OBJDIR)/$(CONFIG)/test/core/handshake/verify_peer_options.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a + +deps_handshake_verify_peer_options: $(HANDSHAKE_VERIFY_PEER_OPTIONS_OBJS:.o=.dep) + +ifneq ($(NO_SECURE),true) +ifneq ($(NO_DEPS),true) +-include $(HANDSHAKE_VERIFY_PEER_OPTIONS_OBJS:.o=.dep) +endif +endif + + HISTOGRAM_TEST_SRC = \ test/core/util/histogram_test.cc \ diff --git a/build.yaml b/build.yaml index 593c845e3c..f963f0c913 100644 --- a/build.yaml +++ b/build.yaml @@ -2792,6 +2792,21 @@ targets: platforms: - linux secure: true +- name: handshake_verify_peer_options + build: test + language: c + src: + - test/core/handshake/verify_peer_options.cc + deps: + - grpc_test_util + - grpc + - gpr_test_util + - gpr + exclude_iomgrs: + - uv + platforms: + - linux + secure: true - name: histogram_test build: test language: c diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index e1975a8e09..b34fb70898 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -163,6 +163,26 @@ typedef struct { const char* cert_chain; } grpc_ssl_pem_key_cert_pair; +/** Object that holds additional peer-verification options on a secure + channel. */ +typedef struct { + /** If non-NULL this callback will be invoked with the expected + target_name, the peer's certificate (in PEM format), and whatever + userdata pointer is set below. If a non-zero value is returned by this + callback then it is treated as a verification failure. Invocation of + the callback is blocking, so any implementation should be light-weight. + */ + int (*verify_peer_callback)(const char* target_name, const char* peer_pem, + void* userdata); + /** Arbitrary userdata that will be passed as the last argument to + verify_peer_callback. */ + void* verify_peer_callback_userdata; + /** A destruct callback that will be invoked when the channel is being + cleaned up. The userdata argument will be passed to it. The intent is + to perform any cleanup associated with that userdata. */ + void (*verify_peer_destruct)(void* userdata); +} verify_peer_options; + /** Creates an SSL credentials object. - pem_root_certs is the NULL-terminated string containing the PEM encoding of the server root certificates. If this parameter is NULL, the @@ -173,10 +193,17 @@ typedef struct { disk (in the grpc install directory). - pem_key_cert_pair is a pointer on the object containing client's private key and certificate chain. This parameter can be NULL if the client does - not have such a key/cert pair. */ + not have such a key/cert pair. + - verify_options is an optional verify_peer_options object which holds + additional options controlling how peer certificates are verified. For + example, you can supply a callback which receives the peer's certificate + with which you can do additional verification. Can be NULL, in which + case verification will retain default behavior. Any settings in + verify_options are copied during this call, so the verify_options + object can be released afterwards. */ GRPCAPI grpc_channel_credentials* grpc_ssl_credentials_create( const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, - void* reserved); + const verify_peer_options* verify_options, void* reserved); /** --- grpc_call_credentials object. diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.cc b/src/core/lib/security/credentials/google_default/google_default_credentials.cc index 38c9175717..c456ffaf5d 100644 --- a/src/core/lib/security/credentials/google_default/google_default_credentials.cc +++ b/src/core/lib/security/credentials/google_default/google_default_credentials.cc @@ -231,7 +231,8 @@ end: creds->base.vtable = &google_default_credentials_vtable; creds->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_GOOGLE_DEFAULT; gpr_ref_init(&creds->base.refcount, 1); - creds->ssl_creds = grpc_ssl_credentials_create(nullptr, nullptr, nullptr); + creds->ssl_creds = + grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); GPR_ASSERT(creds->ssl_creds != nullptr); grpc_alts_credentials_options* options = grpc_alts_credentials_client_options_create(); diff --git a/src/core/lib/security/credentials/ssl/ssl_credentials.cc b/src/core/lib/security/credentials/ssl/ssl_credentials.cc index 2b6377d3ec..3d6f2f200a 100644 --- a/src/core/lib/security/credentials/ssl/ssl_credentials.cc +++ b/src/core/lib/security/credentials/ssl/ssl_credentials.cc @@ -48,6 +48,10 @@ static void ssl_destruct(grpc_channel_credentials* creds) { grpc_ssl_credentials* c = reinterpret_cast<grpc_ssl_credentials*>(creds); gpr_free(c->config.pem_root_certs); grpc_tsi_ssl_pem_key_cert_pairs_destroy(c->config.pem_key_cert_pair, 1); + if (c->config.verify_options.verify_peer_destruct != nullptr) { + c->config.verify_options.verify_peer_destruct( + c->config.verify_options.verify_peer_callback_userdata); + } } static grpc_security_status ssl_create_security_connector( @@ -87,6 +91,7 @@ static grpc_channel_credentials_vtable ssl_vtable = { static void ssl_build_config(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, + const verify_peer_options* verify_options, grpc_ssl_config* config) { if (pem_root_certs != nullptr) { config->pem_root_certs = gpr_strdup(pem_root_certs); @@ -101,23 +106,32 @@ static void ssl_build_config(const char* pem_root_certs, config->pem_key_cert_pair->private_key = gpr_strdup(pem_key_cert_pair->private_key); } + if (verify_options != nullptr) { + memcpy(&config->verify_options, verify_options, + sizeof(verify_peer_options)); + } else { + // Otherwise set all options to default values + memset(&config->verify_options, 0, sizeof(verify_peer_options)); + } } grpc_channel_credentials* grpc_ssl_credentials_create( const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, - void* reserved) { + const verify_peer_options* verify_options, void* reserved) { grpc_ssl_credentials* c = static_cast<grpc_ssl_credentials*>( gpr_zalloc(sizeof(grpc_ssl_credentials))); GRPC_API_TRACE( "grpc_ssl_credentials_create(pem_root_certs=%s, " "pem_key_cert_pair=%p, " + "verify_options=%p, " "reserved=%p)", - 3, (pem_root_certs, pem_key_cert_pair, reserved)); + 4, (pem_root_certs, pem_key_cert_pair, verify_options, reserved)); GPR_ASSERT(reserved == nullptr); c->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_SSL; c->base.vtable = &ssl_vtable; gpr_ref_init(&c->base.refcount, 1); - ssl_build_config(pem_root_certs, pem_key_cert_pair, &c->config); + ssl_build_config(pem_root_certs, pem_key_cert_pair, verify_options, + &c->config); return &c->base; } diff --git a/src/core/lib/security/security_connector/security_connector.cc b/src/core/lib/security/security_connector/security_connector.cc index b54a7643e4..cc72bb6164 100644 --- a/src/core/lib/security/security_connector/security_connector.cc +++ b/src/core/lib/security/security_connector/security_connector.cc @@ -620,6 +620,7 @@ typedef struct { tsi_ssl_client_handshaker_factory* client_handshaker_factory; char* target_name; char* overridden_target_name; + const verify_peer_options* verify_options; } grpc_ssl_channel_security_connector; typedef struct { @@ -878,11 +879,34 @@ static void ssl_channel_check_peer(grpc_security_connector* sc, tsi_peer peer, grpc_closure* on_peer_checked) { grpc_ssl_channel_security_connector* c = reinterpret_cast<grpc_ssl_channel_security_connector*>(sc); - grpc_error* error = ssl_check_peer(sc, - c->overridden_target_name != nullptr - ? c->overridden_target_name - : c->target_name, - &peer, auth_context); + const char* target_name = c->overridden_target_name != nullptr + ? c->overridden_target_name + : c->target_name; + grpc_error* error = ssl_check_peer(sc, target_name, &peer, auth_context); + if (error == GRPC_ERROR_NONE && + c->verify_options->verify_peer_callback != nullptr) { + const tsi_peer_property* p = + tsi_peer_get_property_by_name(&peer, TSI_X509_PEM_CERT_PROPERTY); + if (p == nullptr) { + error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Cannot check peer: missing pem cert property."); + } else { + char* peer_pem = static_cast<char*>(gpr_malloc(p->value.length + 1)); + memcpy(peer_pem, p->value.data, p->value.length); + peer_pem[p->value.length] = '\0'; + int callback_status = c->verify_options->verify_peer_callback( + target_name, peer_pem, + c->verify_options->verify_peer_callback_userdata); + gpr_free(peer_pem); + if (callback_status) { + char* msg; + gpr_asprintf(&msg, "Verify peer callback returned a failure (%d)", + callback_status); + error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg); + gpr_free(msg); + } + } + } GRPC_CLOSURE_SCHED(on_peer_checked, error); tsi_peer_destruct(&peer); } @@ -1047,6 +1071,7 @@ grpc_security_status grpc_ssl_channel_security_connector_create( if (overridden_target_name != nullptr) { c->overridden_target_name = gpr_strdup(overridden_target_name); } + c->verify_options = &config->verify_options; has_key_cert_pair = config->pem_key_cert_pair != nullptr && config->pem_key_cert_pair->private_key != nullptr && diff --git a/src/core/lib/security/security_connector/security_connector.h b/src/core/lib/security/security_connector/security_connector.h index f9723166d0..67a506b576 100644 --- a/src/core/lib/security/security_connector/security_connector.h +++ b/src/core/lib/security/security_connector/security_connector.h @@ -193,6 +193,7 @@ grpc_server_security_connector* grpc_fake_server_security_connector_create( typedef struct { tsi_ssl_pem_key_cert_pair* pem_key_cert_pair; char* pem_root_certs; + verify_peer_options verify_options; } grpc_ssl_config; /* Creates an SSL channel_security_connector. diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc index 00245b397d..bdb6359632 100644 --- a/src/cpp/client/secure_credentials.cc +++ b/src/cpp/client/secure_credentials.cc @@ -83,7 +83,8 @@ std::shared_ptr<ChannelCredentials> SslCredentials( grpc_channel_credentials* c_creds = grpc_ssl_credentials_create( options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(), - options.pem_private_key.empty() ? nullptr : &pem_key_cert_pair, nullptr); + options.pem_private_key.empty() ? nullptr : &pem_key_cert_pair, nullptr, + nullptr); return WrapChannelCredentials(c_creds); } diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 3e6ec474b7..87a2516f8d 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -935,11 +935,12 @@ grpcsharp_ssl_credentials_create(const char* pem_root_certs, if (key_cert_pair_cert_chain || key_cert_pair_private_key) { key_cert_pair.cert_chain = key_cert_pair_cert_chain; key_cert_pair.private_key = key_cert_pair_private_key; - return grpc_ssl_credentials_create(pem_root_certs, &key_cert_pair, NULL); + return grpc_ssl_credentials_create(pem_root_certs, &key_cert_pair, NULL, + NULL); } else { GPR_ASSERT(!key_cert_pair_cert_chain); GPR_ASSERT(!key_cert_pair_private_key); - return grpc_ssl_credentials_create(pem_root_certs, NULL, NULL); + return grpc_ssl_credentials_create(pem_root_certs, NULL, NULL, NULL); } } diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m index f4b933751f..348989904a 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.m +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -183,14 +183,14 @@ static NSMutableDictionary *kHostCache; grpc_channel_credentials *creds; if (pemPrivateKey == nil && pemCertChain == nil) { - creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL); + creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL); } else { grpc_ssl_pem_key_cert_pair key_cert_pair; NSData *privateKeyASCII = [self nullTerminatedDataWithString:pemPrivateKey]; NSData *certChainASCII = [self nullTerminatedDataWithString:pemCertChain]; key_cert_pair.private_key = privateKeyASCII.bytes; key_cert_pair.cert_chain = certChainASCII.bytes; - creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL); + creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL); } @synchronized(self) { diff --git a/src/php/ext/grpc/channel_credentials.c b/src/php/ext/grpc/channel_credentials.c index 10d7380ca1..af1372878d 100644 --- a/src/php/ext/grpc/channel_credentials.c +++ b/src/php/ext/grpc/channel_credentials.c @@ -158,7 +158,7 @@ PHP_METHOD(ChannelCredentials, createSsl) { grpc_channel_credentials *creds = grpc_ssl_credentials_create( pem_root_certs, - pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, NULL); + pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, NULL, NULL); zval *creds_object = grpc_php_wrap_channel_credentials(creds, hashstr, false TSRMLS_CC); efree(hashkey); diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi index f4ccfbc016..d2c0389ca6 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi @@ -142,12 +142,12 @@ cdef class SSLChannelCredentials(ChannelCredentials): c_pem_root_certificates = self._pem_root_certificates if self._private_key is None and self._certificate_chain is None: return grpc_ssl_credentials_create( - c_pem_root_certificates, NULL, NULL) + c_pem_root_certificates, NULL, NULL, NULL) else: c_pem_key_certificate_pair.private_key = self._private_key c_pem_key_certificate_pair.certificate_chain = self._certificate_chain return grpc_ssl_credentials_create( - c_pem_root_certificates, &c_pem_key_certificate_pair, NULL) + c_pem_root_certificates, &c_pem_key_certificate_pair, NULL, NULL) cdef class CompositeChannelCredentials(ChannelCredentials): diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi index cfefeaf938..bcbfec0c9f 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi @@ -453,11 +453,14 @@ cdef extern from "grpc/grpc_security.h": # We don't care about the internals (and in fact don't know them) pass - ctypedef struct grpc_ssl_session_cache: # We don't care about the internals (and in fact don't know them) pass + ctypedef struct verify_peer_options: + # We don't care about the internals (and in fact don't know them) + pass + ctypedef void (*grpc_ssl_roots_override_callback)(char **pem_root_certs) grpc_ssl_session_cache *grpc_ssl_session_cache_create_lru(size_t capacity) @@ -469,7 +472,7 @@ cdef extern from "grpc/grpc_security.h": grpc_channel_credentials *grpc_google_default_credentials_create() nogil grpc_channel_credentials *grpc_ssl_credentials_create( const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, - void *reserved) nogil + verify_peer_options *verify_options, void *reserved) nogil grpc_channel_credentials *grpc_composite_channel_credentials_create( grpc_channel_credentials *creds1, grpc_call_credentials *creds2, void *reserved) nogil diff --git a/src/ruby/ext/grpc/rb_channel_credentials.c b/src/ruby/ext/grpc/rb_channel_credentials.c index b23a32caf1..178224c6e0 100644 --- a/src/ruby/ext/grpc/rb_channel_credentials.c +++ b/src/ruby/ext/grpc/rb_channel_credentials.c @@ -159,12 +159,12 @@ static VALUE grpc_rb_channel_credentials_init(int argc, VALUE* argv, pem_root_certs_cstr = RSTRING_PTR(pem_root_certs); } if (pem_private_key == Qnil && pem_cert_chain == Qnil) { - creds = grpc_ssl_credentials_create(pem_root_certs_cstr, NULL, NULL); + creds = grpc_ssl_credentials_create(pem_root_certs_cstr, NULL, NULL, NULL); } else { key_cert_pair.private_key = RSTRING_PTR(pem_private_key); key_cert_pair.cert_chain = RSTRING_PTR(pem_cert_chain); - creds = - grpc_ssl_credentials_create(pem_root_certs_cstr, &key_cert_pair, NULL); + creds = grpc_ssl_credentials_create(pem_root_certs_cstr, &key_cert_pair, + NULL, NULL); } if (creds == NULL) { rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why"); diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h index b2186a69aa..46d3bf5a33 100644 --- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h +++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h @@ -317,7 +317,7 @@ extern grpc_google_default_credentials_create_type grpc_google_default_credentia typedef void(*grpc_set_ssl_roots_override_callback_type)(grpc_ssl_roots_override_callback cb); extern grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import; #define grpc_set_ssl_roots_override_callback grpc_set_ssl_roots_override_callback_import -typedef grpc_channel_credentials*(*grpc_ssl_credentials_create_type)(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, void* reserved); +typedef grpc_channel_credentials*(*grpc_ssl_credentials_create_type)(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, const verify_peer_options* verify_options, void* reserved); extern grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import; #define grpc_ssl_credentials_create grpc_ssl_credentials_create_import typedef void(*grpc_call_credentials_release_type)(grpc_call_credentials* creds); diff --git a/test/core/bad_ssl/bad_ssl_test.cc b/test/core/bad_ssl/bad_ssl_test.cc index e2ea7540ef..73d251eff4 100644 --- a/test/core/bad_ssl/bad_ssl_test.cc +++ b/test/core/bad_ssl/bad_ssl_test.cc @@ -37,7 +37,7 @@ static void* tag(intptr_t t) { return (void*)t; } static void run_test(const char* target, size_t nops) { grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(nullptr, nullptr, nullptr); + grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); grpc_channel* channel; grpc_call* c; diff --git a/test/core/end2end/fixtures/h2_oauth2.cc b/test/core/end2end/fixtures/h2_oauth2.cc index d44aafd50a..37397d6450 100644 --- a/test/core/end2end/fixtures/h2_oauth2.cc +++ b/test/core/end2end/fixtures/h2_oauth2.cc @@ -146,7 +146,7 @@ static void chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack( grpc_end2end_test_fixture* f, grpc_channel_args* client_args) { grpc_core::ExecCtx exec_ctx; grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr); + grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr); grpc_call_credentials* oauth2_creds = grpc_md_only_test_credentials_create( "authorization", oauth2_md, true /* is_async */); grpc_channel_credentials* ssl_oauth2_creds = diff --git a/test/core/end2end/fixtures/h2_ssl.cc b/test/core/end2end/fixtures/h2_ssl.cc index 999cd4cdfb..4d6c815716 100644 --- a/test/core/end2end/fixtures/h2_ssl.cc +++ b/test/core/end2end/fixtures/h2_ssl.cc @@ -101,7 +101,7 @@ void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) { static void chttp2_init_client_simple_ssl_secure_fullstack( grpc_end2end_test_fixture* f, grpc_channel_args* client_args) { grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(nullptr, nullptr, nullptr); + grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), diff --git a/test/core/end2end/fixtures/h2_ssl_proxy.cc b/test/core/end2end/fixtures/h2_ssl_proxy.cc index 9ab50c6217..09cbf974b6 100644 --- a/test/core/end2end/fixtures/h2_ssl_proxy.cc +++ b/test/core/end2end/fixtures/h2_ssl_proxy.cc @@ -55,7 +55,7 @@ static grpc_channel* create_proxy_client(const char* target, grpc_channel_args* client_args) { grpc_channel* channel; grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(nullptr, nullptr, nullptr); + grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), @@ -138,7 +138,7 @@ void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) { static void chttp2_init_client_simple_ssl_secure_fullstack( grpc_end2end_test_fixture* f, grpc_channel_args* client_args) { grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(nullptr, nullptr, nullptr); + grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr); grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), diff --git a/test/core/end2end/fuzzers/api_fuzzer.cc b/test/core/end2end/fuzzers/api_fuzzer.cc index ccb194cdf5..6c226818b9 100644 --- a/test/core/end2end/fuzzers/api_fuzzer.cc +++ b/test/core/end2end/fuzzers/api_fuzzer.cc @@ -222,7 +222,7 @@ static grpc_channel_credentials* read_ssl_channel_creds(input_stream* inp) { grpc_channel_credentials* creds = grpc_ssl_credentials_create( root_certs, private_key != nullptr && certs != nullptr ? &key_cert_pair : nullptr, - nullptr); + nullptr, nullptr); cred_artifact_ctx_finish(&ctx); return creds; } diff --git a/test/core/end2end/h2_ssl_cert_test.cc b/test/core/end2end/h2_ssl_cert_test.cc index 9ed6f23798..2c5ee3b156 100644 --- a/test/core/end2end/h2_ssl_cert_test.cc +++ b/test/core/end2end/h2_ssl_cert_test.cc @@ -169,8 +169,8 @@ typedef enum { NONE, SELF_SIGNED, SIGNED, BAD_CERT_PAIR } certtype; default: \ break; \ } \ - ssl_creds = \ - grpc_ssl_credentials_create(test_root_cert, key_cert_pair, NULL); \ + ssl_creds = grpc_ssl_credentials_create(test_root_cert, key_cert_pair, \ + NULL, NULL); \ grpc_arg ssl_name_override = { \ GRPC_ARG_STRING, \ const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), \ diff --git a/test/core/end2end/h2_ssl_session_reuse_test.cc b/test/core/end2end/h2_ssl_session_reuse_test.cc index d5984be93f..b2f398625a 100644 --- a/test/core/end2end/h2_ssl_session_reuse_test.cc +++ b/test/core/end2end/h2_ssl_session_reuse_test.cc @@ -66,7 +66,7 @@ grpc_channel* client_create(char* server_addr, grpc_ssl_session_cache* cache) { grpc_ssl_pem_key_cert_pair signed_client_key_cert_pair = { test_signed_client_key, test_signed_client_cert}; grpc_channel_credentials* client_creds = grpc_ssl_credentials_create( - test_root_cert, &signed_client_key_cert_pair, nullptr); + test_root_cert, &signed_client_key_cert_pair, nullptr, nullptr); grpc_arg args[] = { grpc_channel_arg_string_create( diff --git a/test/core/handshake/BUILD b/test/core/handshake/BUILD index a3276b9343..712cd59197 100644 --- a/test/core/handshake/BUILD +++ b/test/core/handshake/BUILD @@ -82,3 +82,21 @@ grpc_cc_test( "//test/core/util:grpc_test_util", ], ) + +grpc_cc_test( + name = "handshake_verify_peer_options", + srcs = ["verify_peer_options.cc"], + language = "C++", + data = [ + "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:server1.key", + "//src/core/tsi/test_creds:server1.pem", + ], + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + ], +) + diff --git a/test/core/handshake/client_ssl.cc b/test/core/handshake/client_ssl.cc index 8ac763ac4b..467df6e229 100644 --- a/test/core/handshake/client_ssl.cc +++ b/test/core/handshake/client_ssl.cc @@ -251,8 +251,8 @@ static bool client_ssl_test(char* server_alpn_preferred) { reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice); pem_key_cert_pair.cert_chain = reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice); - grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(ca_cert, &pem_key_cert_pair, nullptr); + grpc_channel_credentials* ssl_creds = grpc_ssl_credentials_create( + ca_cert, &pem_key_cert_pair, nullptr, nullptr); // Establish a channel pointing at the TLS server. Since the gRPC runtime is // lazy, this won't necessarily establish a connection yet. diff --git a/test/core/handshake/verify_peer_options.cc b/test/core/handshake/verify_peer_options.cc new file mode 100644 index 0000000000..86c524b4a5 --- /dev/null +++ b/test/core/handshake/verify_peer_options.cc @@ -0,0 +1,275 @@ +/* + * + * Copyright 2018 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/lib/iomgr/port.h" + +// This test won't work except with posix sockets enabled +#ifdef GRPC_POSIX_SOCKET + +#include <arpa/inet.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <grpc/grpc.h> +#include <grpc/grpc_security.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/gprpp/thd.h" +#include "src/core/lib/iomgr/load_file.h" +#include "test/core/util/port.h" +#include "test/core/util/test_config.h" + +#define SSL_CERT_PATH "src/core/tsi/test_creds/server1.pem" +#define SSL_KEY_PATH "src/core/tsi/test_creds/server1.key" +#define SSL_CA_PATH "src/core/tsi/test_creds/ca.pem" + +// Simple gRPC server. This listens until client_handshake_complete occurs. +static gpr_event client_handshake_complete; + +static void server_thread(void* arg) { + const int port = *static_cast<int*>(arg); + + // Load key pair and establish server SSL credentials. + grpc_ssl_pem_key_cert_pair pem_key_cert_pair; + grpc_slice ca_slice, cert_slice, key_slice; + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_CA_PATH, 1, &ca_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_CERT_PATH, 1, &cert_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_KEY_PATH, 1, &key_slice))); + const char* ca_cert = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice); + pem_key_cert_pair.private_key = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice); + pem_key_cert_pair.cert_chain = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice); + grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create( + ca_cert, &pem_key_cert_pair, 1, 0, nullptr); + + // Start server listening on local port. + char* addr; + gpr_asprintf(&addr, "127.0.0.1:%d", port); + grpc_server* server = grpc_server_create(nullptr, nullptr); + GPR_ASSERT(grpc_server_add_secure_http2_port(server, addr, ssl_creds)); + free(addr); + + grpc_completion_queue* cq = grpc_completion_queue_create_for_next(nullptr); + + grpc_server_register_completion_queue(server, cq, nullptr); + grpc_server_start(server); + + // Wait a bounded number of time until client_handshake_complete is set, + // sleeping between polls. The total time spent (deadline * retries) + // should be strictly greater than the client retry limit so that the + // client will always timeout first. + int retries = 60; + while (!gpr_event_get(&client_handshake_complete) && retries-- > 0) { + const gpr_timespec cq_deadline = grpc_timeout_seconds_to_deadline(1); + grpc_event ev = grpc_completion_queue_next(cq, cq_deadline, nullptr); + GPR_ASSERT(ev.type == GRPC_QUEUE_TIMEOUT); + } + + gpr_log(GPR_INFO, "Shutting down server"); + grpc_server_shutdown_and_notify(server, cq, nullptr); + grpc_server_cancel_all_calls(server); + grpc_completion_queue_shutdown(cq); + + const gpr_timespec cq_deadline = grpc_timeout_seconds_to_deadline(60); + grpc_event ev = grpc_completion_queue_next(cq, cq_deadline, nullptr); + GPR_ASSERT(ev.type == GRPC_OP_COMPLETE); + + grpc_server_destroy(server); + grpc_completion_queue_destroy(cq); + grpc_server_credentials_release(ssl_creds); + grpc_slice_unref(cert_slice); + grpc_slice_unref(key_slice); + grpc_slice_unref(ca_slice); +} + +// This test launches a minimal TLS grpc server on a separate thread and then +// establishes a TLS handshake via the core library to the server. The client +// uses the supplied verify options. +static bool verify_peer_options_test(verify_peer_options* verify_options) { + bool success = true; + + grpc_init(); + int port = grpc_pick_unused_port_or_die(); + gpr_event_init(&client_handshake_complete); + + // Launch the gRPC server thread. + bool ok; + grpc_core::Thread thd("grpc_client_ssl_test", server_thread, &port, &ok); + GPR_ASSERT(ok); + thd.Start(); + + // Load key pair and establish client SSL credentials. + grpc_ssl_pem_key_cert_pair pem_key_cert_pair; + grpc_slice ca_slice, cert_slice, key_slice; + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_CA_PATH, 1, &ca_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_CERT_PATH, 1, &cert_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_KEY_PATH, 1, &key_slice))); + const char* ca_cert = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice); + pem_key_cert_pair.private_key = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice); + pem_key_cert_pair.cert_chain = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice); + grpc_channel_credentials* ssl_creds = grpc_ssl_credentials_create( + ca_cert, &pem_key_cert_pair, verify_options, nullptr); + + // Establish a channel pointing at the TLS server. Since the gRPC runtime is + // lazy, this won't necessarily establish a connection yet. + char* target; + gpr_asprintf(&target, "127.0.0.1:%d", port); + grpc_arg ssl_name_override = { + GRPC_ARG_STRING, + const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), + {const_cast<char*>("foo.test.google.fr")}}; + grpc_channel_args grpc_args; + grpc_args.num_args = 1; + grpc_args.args = &ssl_name_override; + grpc_channel* channel = + grpc_secure_channel_create(ssl_creds, target, &grpc_args, nullptr); + GPR_ASSERT(channel); + gpr_free(target); + + // Initially the channel will be idle, the + // grpc_channel_check_connectivity_state triggers an attempt to connect. + GPR_ASSERT(grpc_channel_check_connectivity_state( + channel, 1 /* try_to_connect */) == GRPC_CHANNEL_IDLE); + + // Wait a bounded number of times for the channel to be ready. When the + // channel is ready, the initial TLS handshake will have successfully + // completed. The total time spent on the client side (retries * deadline) + // should be greater than the server side time limit. + int retries = 10; + grpc_connectivity_state state = GRPC_CHANNEL_IDLE; + grpc_completion_queue* cq = grpc_completion_queue_create_for_next(nullptr); + + while (state != GRPC_CHANNEL_READY && retries-- > 0) { + grpc_channel_watch_connectivity_state( + channel, state, grpc_timeout_seconds_to_deadline(3), cq, nullptr); + gpr_timespec cq_deadline = grpc_timeout_seconds_to_deadline(5); + grpc_event ev = grpc_completion_queue_next(cq, cq_deadline, nullptr); + GPR_ASSERT(ev.type == GRPC_OP_COMPLETE); + state = + grpc_channel_check_connectivity_state(channel, 0 /* try_to_connect */); + } + grpc_completion_queue_destroy(cq); + if (retries < 0) { + success = false; + } + + grpc_channel_destroy(channel); + grpc_channel_credentials_release(ssl_creds); + grpc_slice_unref(cert_slice); + grpc_slice_unref(key_slice); + grpc_slice_unref(ca_slice); + + // Now that the client is completely cleaned up, trigger the server to + // shutdown + gpr_event_set(&client_handshake_complete, &client_handshake_complete); + // Wait for the server to completely shutdown + thd.Join(); + + grpc_shutdown(); + + return success; +} + +static int callback_return_value = 0; +static char callback_target_host[4096]; +static char callback_target_pem[4096]; +static void* callback_userdata = nullptr; +static void* destruct_userdata = nullptr; + +static int verify_callback(const char* target_host, const char* target_pem, + void* userdata) { + if (target_host != nullptr) { + snprintf(callback_target_host, sizeof(callback_target_host), "%s", + target_host); + } else { + callback_target_host[0] = '\0'; + } + if (target_pem != nullptr) { + snprintf(callback_target_pem, sizeof(callback_target_pem), "%s", + target_pem); + } else { + callback_target_pem[0] = '\0'; + } + callback_userdata = userdata; + return callback_return_value; +} + +static void verify_destruct(void* userdata) { destruct_userdata = userdata; } + +int main(int argc, char* argv[]) { + int userdata = 42; + verify_peer_options verify_options; + + // Load the server's cert so that we can assert it gets passed to the callback + grpc_slice cert_slice; + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SSL_CERT_PATH, 1, &cert_slice))); + const char* server_cert = + reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice); + + // Running with all-null values should have no effect + verify_options.verify_peer_callback = nullptr; + verify_options.verify_peer_callback_userdata = nullptr; + verify_options.verify_peer_destruct = nullptr; + GPR_ASSERT(verify_peer_options_test(&verify_options)); + GPR_ASSERT(strlen(callback_target_host) == 0); + GPR_ASSERT(strlen(callback_target_pem) == 0); + GPR_ASSERT(callback_userdata == nullptr); + GPR_ASSERT(destruct_userdata == nullptr); + + // Running with the callbacks and verify we get the expected values + verify_options.verify_peer_callback = verify_callback; + verify_options.verify_peer_callback_userdata = static_cast<void*>(&userdata); + verify_options.verify_peer_destruct = verify_destruct; + GPR_ASSERT(verify_peer_options_test(&verify_options)); + GPR_ASSERT(strcmp(callback_target_host, "foo.test.google.fr") == 0); + GPR_ASSERT(strcmp(callback_target_pem, server_cert) == 0); + GPR_ASSERT(callback_userdata == static_cast<void*>(&userdata)); + GPR_ASSERT(destruct_userdata == static_cast<void*>(&userdata)); + + // If the callback returns non-zero, initializing the channel should fail. + callback_return_value = 1; + GPR_ASSERT(!verify_peer_options_test(&verify_options)); + + grpc_slice_unref(cert_slice); + + return 0; +} + +#else /* GRPC_POSIX_SOCKET */ + +int main(int argc, char** argv) { return 1; } + +#endif /* GRPC_POSIX_SOCKET */ diff --git a/test/core/surface/num_external_connectivity_watchers_test.cc b/test/core/surface/num_external_connectivity_watchers_test.cc index 467deeeaec..7b7a0b6dfc 100644 --- a/test/core/surface/num_external_connectivity_watchers_test.cc +++ b/test/core/surface/num_external_connectivity_watchers_test.cc @@ -168,7 +168,7 @@ static const test_fixture insecure_test = { static grpc_channel* secure_test_create_channel(const char* addr) { grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr); + grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr); grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), diff --git a/test/core/surface/sequential_connectivity_test.cc b/test/core/surface/sequential_connectivity_test.cc index 9aba4c499e..10562b3be9 100644 --- a/test/core/surface/sequential_connectivity_test.cc +++ b/test/core/surface/sequential_connectivity_test.cc @@ -144,7 +144,7 @@ static void secure_test_add_port(grpc_server* server, const char* addr) { static grpc_channel* secure_test_create_channel(const char* addr) { grpc_channel_credentials* ssl_creds = - grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr); + grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr); grpc_arg ssl_name_override = { GRPC_ARG_STRING, const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json index f17c1c75b6..66191656ff 100644 --- a/tools/run_tests/generated/sources_and_headers.json +++ b/tools/run_tests/generated/sources_and_headers.json @@ -1177,6 +1177,23 @@ { "deps": [ "gpr", + "gpr_test_util", + "grpc", + "grpc_test_util" + ], + "headers": [], + "is_filegroup": false, + "language": "c", + "name": "handshake_verify_peer_options", + "src": [ + "test/core/handshake/verify_peer_options.cc" + ], + "third_party": false, + "type": "target" + }, + { + "deps": [ + "gpr", "grpc_test_util" ], "headers": [], diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index ba63352036..498edbb35d 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -1451,6 +1451,26 @@ "args": [], "benchmark": false, "ci_platforms": [ + "linux" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [ + "uv" + ], + "flaky": false, + "gtest": false, + "language": "c", + "name": "handshake_verify_peer_options", + "platforms": [ + "linux" + ], + "uses_polling": true + }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ "linux", "mac", "posix", |