diff --git a/CMakeLists.txt b/CMakeLists.txt index 8568ecd0f66..9f5d7c98bd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -997,6 +997,7 @@ if(gRPC_BUILD_TESTS) endif() add_dependencies(buildtests_cxx h2_ssl_cert_test) add_dependencies(buildtests_cxx h2_ssl_session_reuse_test) + add_dependencies(buildtests_cxx h2_tls_peer_property_external_verifier_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx handshake_server_with_readahead_handshaker_test) endif() @@ -13161,6 +13162,44 @@ target_link_libraries(h2_ssl_session_reuse_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(h2_tls_peer_property_external_verifier_test + test/core/end2end/cq_verifier.cc + test/core/end2end/h2_tls_peer_property_external_verifier_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) +target_compile_features(h2_tls_peer_property_external_verifier_test PUBLIC cxx_std_14) +target_include_directories(h2_tls_peer_property_external_verifier_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(h2_tls_peer_property_external_verifier_test + ${_gRPC_BASELIB_LIBRARIES} + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ZLIB_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util +) + + endif() if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 67a66275876..1dbd12463fd 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -8474,6 +8474,17 @@ targets: - test/core/end2end/h2_ssl_session_reuse_test.cc deps: - grpc_test_util +- name: h2_tls_peer_property_external_verifier_test + gtest: true + build: test + language: c++ + headers: + - test/core/end2end/cq_verifier.h + src: + - test/core/end2end/cq_verifier.cc + - test/core/end2end/h2_tls_peer_property_external_verifier_test.cc + deps: + - grpc_test_util - name: handshake_server_with_readahead_handshaker_test gtest: true build: test diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index 63810f6fa91..d7ea4cc669a 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -931,6 +931,10 @@ typedef struct grpc_tls_custom_verification_check_request { * grpc_security_constants.h. * TODO(ZhenLian): Consider fixing this in the future. */ const char* peer_cert_full_chain; + /* The verified root cert subject. + * This value will only be filled if the cryptographic peer certificate + * verification was successful */ + const char* verified_root_cert_subject; } peer_info; } grpc_tls_custom_verification_check_request; diff --git a/include/grpcpp/security/tls_certificate_verifier.h b/include/grpcpp/security/tls_certificate_verifier.h index de2df4194d5..61c0acb0fb3 100644 --- a/include/grpcpp/security/tls_certificate_verifier.h +++ b/include/grpcpp/security/tls_certificate_verifier.h @@ -62,6 +62,13 @@ class TlsCustomVerificationCheckRequest { grpc::string_ref peer_cert() const; grpc::string_ref peer_cert_full_chain() const; grpc::string_ref common_name() const; + // The subject name of the root certificate used to verify the peer chain + // If verification fails or the peer cert is self-signed, this will be an + // empty string. If verification is successful, it is a comma-separated list, + // where the entries are of the form "FIELD_ABBREVIATION=string" + // ex: "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" + // ex: "CN=GTS Root R1,O=Google Trust Services LLC,C=US" + grpc::string_ref verified_root_cert_subject() const; std::vector uri_names() const; std::vector dns_names() const; std::vector email_names() const; diff --git a/src/core/lib/security/security_connector/tls/tls_security_connector.cc b/src/core/lib/security/security_connector/tls/tls_security_connector.cc index 1fea77986f9..fceefac8b6b 100644 --- a/src/core/lib/security/security_connector/tls/tls_security_connector.cc +++ b/src/core/lib/security/security_connector/tls/tls_security_connector.cc @@ -74,6 +74,7 @@ void PendingVerifierRequestInit( bool has_common_name = false; bool has_peer_cert = false; bool has_peer_cert_full_chain = false; + bool has_verified_root_cert_subject = false; std::vector uri_names; std::vector dns_names; std::vector email_names; @@ -105,6 +106,11 @@ void PendingVerifierRequestInit( } else if (strcmp(prop->name, TSI_X509_IP_PEER_PROPERTY) == 0) { char* ip = CopyCoreString(prop->value.data, prop->value.length); ip_names.emplace_back(ip); + } else if (strcmp(prop->name, + TSI_X509_VERIFIED_ROOT_CERT_SUBECT_PEER_PROPERTY) == 0) { + request->peer_info.verified_root_cert_subject = + CopyCoreString(prop->value.data, prop->value.length); + has_verified_root_cert_subject = true; } } if (!has_common_name) { @@ -116,6 +122,9 @@ void PendingVerifierRequestInit( if (!has_peer_cert_full_chain) { request->peer_info.peer_cert_full_chain = nullptr; } + if (!has_verified_root_cert_subject) { + request->peer_info.verified_root_cert_subject = nullptr; + } request->peer_info.san_names.uri_names_size = uri_names.size(); if (!uri_names.empty()) { request->peer_info.san_names.uri_names = @@ -202,6 +211,9 @@ void PendingVerifierRequestDestroy( if (request->peer_info.peer_cert_full_chain != nullptr) { gpr_free(const_cast(request->peer_info.peer_cert_full_chain)); } + if (request->peer_info.verified_root_cert_subject != nullptr) { + gpr_free(const_cast(request->peer_info.verified_root_cert_subject)); + } } tsi_ssl_pem_key_cert_pair* ConvertToTsiPemKeyCertPair( diff --git a/src/cpp/common/tls_certificate_verifier.cc b/src/cpp/common/tls_certificate_verifier.cc index 4cc24968989..9ad3e0f0f15 100644 --- a/src/cpp/common/tls_certificate_verifier.cc +++ b/src/cpp/common/tls_certificate_verifier.cc @@ -65,6 +65,13 @@ grpc::string_ref TlsCustomVerificationCheckRequest::common_name() const { : ""; } +grpc::string_ref TlsCustomVerificationCheckRequest::verified_root_cert_subject() + const { + return c_request_->peer_info.verified_root_cert_subject != nullptr + ? c_request_->peer_info.verified_root_cert_subject + : ""; +} + std::vector TlsCustomVerificationCheckRequest::uri_names() const { std::vector uri_names; diff --git a/test/core/end2end/BUILD b/test/core/end2end/BUILD index e1326557902..8d2084cf107 100644 --- a/test/core/end2end/BUILD +++ b/test/core/end2end/BUILD @@ -308,3 +308,29 @@ grpc_cc_test( "//test/core/util:grpc_test_util", ], ) + +grpc_cc_test( + name = "h2_tls_peer_property_external_verifier_test", + srcs = ["h2_tls_peer_property_external_verifier_test.cc"], + data = [ + "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:client.key", + "//src/core/tsi/test_creds:client.pem", + "//src/core/tsi/test_creds:server1.key", + "//src/core/tsi/test_creds:server1.pem", + ], + external_deps = ["gtest"], + language = "C++", + deps = [ + "cq_verifier", + "//:exec_ctx", + "//:gpr", + "//:grpc", + "//:grpc_public_hdrs", + "//:tsi_ssl_credentials", + "//src/core:channel_args", + "//src/core:error", + "//src/core:useful", + "//test/core/util:grpc_test_util", + ], +) diff --git a/test/core/end2end/h2_tls_peer_property_external_verifier_test.cc b/test/core/end2end/h2_tls_peer_property_external_verifier_test.cc new file mode 100644 index 00000000000..35d1877c263 --- /dev/null +++ b/test/core/end2end/h2_tls_peer_property_external_verifier_test.cc @@ -0,0 +1,334 @@ +// +// +// 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 +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/gpr/useful.h" +#include "src/core/lib/gprpp/global_config_generic.h" +#include "src/core/lib/gprpp/host_port.h" +#include "src/core/lib/iomgr/error.h" +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/load_file.h" +#include "src/core/lib/security/security_connector/ssl_utils_config.h" +#include "test/core/end2end/cq_verifier.h" +#include "test/core/util/port.h" +#include "test/core/util/test_config.h" +#include "test/core/util/tls_utils.h" + +#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem" +#define CLIENT_CERT_PATH "src/core/tsi/test_creds/client.pem" +#define CLIENT_KEY_PATH "src/core/tsi/test_creds/client.key" +#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem" +#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key" + +namespace grpc { +namespace testing { +namespace { + +const std::string kCaCertSubject = + "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"; + +void* tag(intptr_t t) { return reinterpret_cast(t); } + +gpr_timespec five_seconds_time() { return grpc_timeout_seconds_to_deadline(5); } + +grpc_server* server_create(grpc_completion_queue* cq, const char* server_addr, + grpc_tls_certificate_provider** server_provider, + grpc_tls_certificate_verifier** verifier) { + grpc_slice ca_slice, cert_slice, key_slice; + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(CA_CERT_PATH, 1, &ca_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR( + "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(SERVER_KEY_PATH, 1, &key_slice))); + const char* ca_cert = + reinterpret_cast GRPC_SLICE_START_PTR(ca_slice); + const char* server_cert = + reinterpret_cast GRPC_SLICE_START_PTR(cert_slice); + const char* server_key = + reinterpret_cast GRPC_SLICE_START_PTR(key_slice); + + grpc_tls_credentials_options* options = grpc_tls_credentials_options_create(); + // Set credential provider. + grpc_tls_identity_pairs* server_pairs = grpc_tls_identity_pairs_create(); + grpc_tls_identity_pairs_add_pair(server_pairs, server_key, server_cert); + *server_provider = + grpc_tls_certificate_provider_static_data_create(ca_cert, server_pairs); + grpc_tls_credentials_options_set_certificate_provider(options, + *server_provider); + grpc_tls_credentials_options_watch_root_certs(options); + grpc_tls_credentials_options_watch_identity_key_cert_pairs(options); + // Set client certificate request type. + grpc_tls_credentials_options_set_cert_request_type( + options, GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + // Set credential verifier. + auto* server_test_verifier = + new grpc_core::testing::PeerPropertyExternalVerifier(kCaCertSubject); + *verifier = grpc_tls_certificate_verifier_external_create( + server_test_verifier->base()); + grpc_tls_credentials_options_set_certificate_verifier(options, *verifier); + grpc_server_credentials* creds = grpc_tls_server_credentials_create(options); + + grpc_server* server = grpc_server_create(nullptr, nullptr); + grpc_server_register_completion_queue(server, cq, nullptr); + GPR_ASSERT(grpc_server_add_http2_port(server, server_addr, creds)); + grpc_server_credentials_release(creds); + + grpc_server_start(server); + + grpc_slice_unref(cert_slice); + grpc_slice_unref(key_slice); + grpc_slice_unref(ca_slice); + return server; +} + +grpc_channel* client_create(const char* server_addr, + grpc_tls_certificate_provider** client_provider, + grpc_tls_certificate_verifier** verifier) { + grpc_slice ca_slice, cert_slice, key_slice; + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(CA_CERT_PATH, 1, &ca_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR( + "load_file", grpc_load_file(CLIENT_CERT_PATH, 1, &cert_slice))); + GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file", + grpc_load_file(CLIENT_KEY_PATH, 1, &key_slice))); + const char* ca_cert = + reinterpret_cast GRPC_SLICE_START_PTR(ca_slice); + const char* client_cert = + reinterpret_cast GRPC_SLICE_START_PTR(cert_slice); + const char* client_key = + reinterpret_cast GRPC_SLICE_START_PTR(key_slice); + + grpc_tls_credentials_options* options = grpc_tls_credentials_options_create(); + // Set credential provider. + grpc_tls_identity_pairs* client_pairs = grpc_tls_identity_pairs_create(); + grpc_tls_identity_pairs_add_pair(client_pairs, client_key, client_cert); + *client_provider = + grpc_tls_certificate_provider_static_data_create(ca_cert, client_pairs); + + grpc_tls_credentials_options_set_certificate_provider(options, + *client_provider); + grpc_tls_credentials_options_watch_root_certs(options); + grpc_tls_credentials_options_watch_identity_key_cert_pairs(options); + // Set client certificate request type. + grpc_tls_credentials_options_set_cert_request_type( + options, GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + // Set credential verifier. + auto* client_test_verifier = + new grpc_core::testing::PeerPropertyExternalVerifier(kCaCertSubject); + *verifier = grpc_tls_certificate_verifier_external_create( + client_test_verifier->base()); + grpc_tls_credentials_options_set_certificate_verifier(options, *verifier); + grpc_channel_credentials* creds = grpc_tls_credentials_create(options); + + grpc_arg args[] = { + grpc_channel_arg_string_create( + const_cast(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), + const_cast("waterzooi.test.google.be")), + }; + + grpc_channel_args* client_args = + grpc_channel_args_copy_and_add(nullptr, args, GPR_ARRAY_SIZE(args)); + + grpc_channel* client = grpc_channel_create(server_addr, creds, client_args); + GPR_ASSERT(client != nullptr); + grpc_channel_credentials_release(creds); + + { + grpc_core::ExecCtx exec_ctx; + grpc_channel_args_destroy(client_args); + } + + grpc_slice_unref(cert_slice); + grpc_slice_unref(key_slice); + grpc_slice_unref(ca_slice); + return client; +} + +void do_round_trip(grpc_completion_queue* cq, grpc_server* server, + const char* server_addr) { + grpc_tls_certificate_provider* provider = nullptr; + grpc_tls_certificate_verifier* verifier = nullptr; + grpc_channel* client = client_create(server_addr, &provider, &verifier); + + grpc_core::CqVerifier cqv(cq); + grpc_op ops[6]; + grpc_op* op; + grpc_metadata_array initial_metadata_recv; + grpc_metadata_array trailing_metadata_recv; + grpc_metadata_array request_metadata_recv; + grpc_call_details call_details; + grpc_status_code status; + grpc_call_error error; + grpc_slice details; + int was_cancelled = 2; + + gpr_timespec deadline = grpc_timeout_seconds_to_deadline(60); + grpc_call* c = grpc_channel_create_call( + client, nullptr, GRPC_PROPAGATE_DEFAULTS, cq, + grpc_slice_from_static_string("/foo"), nullptr, deadline, nullptr); + GPR_ASSERT(c); + + grpc_metadata_array_init(&initial_metadata_recv); + grpc_metadata_array_init(&trailing_metadata_recv); + grpc_metadata_array_init(&request_metadata_recv); + grpc_call_details_init(&call_details); + + memset(ops, 0, sizeof(ops)); + op = ops; + op->op = GRPC_OP_SEND_INITIAL_METADATA; + op->data.send_initial_metadata.count = 0; + op->flags = 0; + op->reserved = nullptr; + op++; + op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; + op->flags = 0; + op->reserved = nullptr; + op++; + op->op = GRPC_OP_RECV_INITIAL_METADATA; + op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv; + op->flags = 0; + op->reserved = nullptr; + op++; + op->op = GRPC_OP_RECV_STATUS_ON_CLIENT; + op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv; + op->data.recv_status_on_client.status = &status; + op->data.recv_status_on_client.status_details = &details; + op->flags = 0; + op->reserved = nullptr; + op++; + error = grpc_call_start_batch(c, ops, static_cast(op - ops), tag(1), + nullptr); + GPR_ASSERT(GRPC_CALL_OK == error); + + grpc_call* s; + error = grpc_server_request_call(server, &s, &call_details, + &request_metadata_recv, cq, cq, tag(101)); + GPR_ASSERT(GRPC_CALL_OK == error); + cqv.Expect(tag(101), true); + cqv.Verify(); + + memset(ops, 0, sizeof(ops)); + op = ops; + op->op = GRPC_OP_SEND_INITIAL_METADATA; + op->data.send_initial_metadata.count = 0; + op->flags = 0; + op->reserved = nullptr; + op++; + op->op = GRPC_OP_RECV_CLOSE_ON_SERVER; + op->data.recv_close_on_server.cancelled = &was_cancelled; + op->flags = 0; + op->reserved = nullptr; + op++; + op->op = GRPC_OP_SEND_STATUS_FROM_SERVER; + op->data.send_status_from_server.trailing_metadata_count = 0; + op->data.send_status_from_server.status = GRPC_STATUS_OK; + op->flags = 0; + op->reserved = nullptr; + op++; + error = grpc_call_start_batch(s, ops, static_cast(op - ops), tag(103), + nullptr); + GPR_ASSERT(GRPC_CALL_OK == error); + + cqv.Expect(tag(103), true); + cqv.Expect(tag(1), true); + cqv.Verify(); + + grpc_metadata_array_destroy(&initial_metadata_recv); + grpc_metadata_array_destroy(&trailing_metadata_recv); + grpc_metadata_array_destroy(&request_metadata_recv); + grpc_call_details_destroy(&call_details); + + grpc_call_unref(c); + grpc_call_unref(s); + + grpc_channel_destroy(client); + grpc_tls_certificate_provider_release(provider); + grpc_tls_certificate_verifier_release(verifier); +} + +void drain_cq(grpc_completion_queue* cq) { + grpc_event ev; + do { + ev = grpc_completion_queue_next(cq, five_seconds_time(), nullptr); + } while (ev.type != GRPC_QUEUE_SHUTDOWN); +} + +TEST(H2TlsPeerPropertyExternalVerifier, PeerPropertyExternalVerifierTest) { + int port = grpc_pick_unused_port_or_die(); + + std::string server_addr = grpc_core::JoinHostPort("localhost", port); + + grpc_completion_queue* cq = grpc_completion_queue_create_for_next(nullptr); + + grpc_tls_certificate_provider* provider = nullptr; + grpc_tls_certificate_verifier* verifier = nullptr; + grpc_server* server = + server_create(cq, server_addr.c_str(), &provider, &verifier); + + do_round_trip(cq, server, server_addr.c_str()); + + GPR_ASSERT(grpc_completion_queue_next( + cq, grpc_timeout_milliseconds_to_deadline(100), nullptr) + .type == GRPC_QUEUE_TIMEOUT); + + grpc_server_shutdown_and_notify(server, cq, tag(1000)); + grpc_event ev; + do { + ev = grpc_completion_queue_next(cq, grpc_timeout_seconds_to_deadline(5), + nullptr); + } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000)); + grpc_server_destroy(server); + grpc_tls_certificate_provider_release(provider); + grpc_tls_certificate_verifier_release(verifier); + + grpc_completion_queue_shutdown(cq); + drain_cq(cq); + grpc_completion_queue_destroy(cq); +} + +} // namespace +} // namespace testing +} // namespace grpc + +int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(&argc, argv); + GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, CA_CERT_PATH); + + grpc_init(); + ::testing::InitGoogleTest(&argc, argv); + int ret = RUN_ALL_TESTS(); + grpc_shutdown(); + + return ret; +} diff --git a/test/core/security/tls_security_connector_test.cc b/test/core/security/tls_security_connector_test.cc index 8e2f87a841a..f4cce75e25e 100644 --- a/test/core/security/tls_security_connector_test.cc +++ b/test/core/security/tls_security_connector_test.cc @@ -737,6 +737,46 @@ TEST_F(TlsSecurityConnectorTest, on_peer_checked); } +TEST_F(TlsSecurityConnectorTest, + ChannelSecurityConnectorWithVerifiedRootCertSubjectSucceeds) { + auto* sync_verifier = new SyncExternalVerifier(true); + ExternalCertificateVerifier core_external_verifier(sync_verifier->base()); + RefCountedPtr options = + MakeRefCounted(); + options->set_verify_server_cert(true); + options->set_certificate_verifier(core_external_verifier.Ref()); + options->set_check_call_host(false); + RefCountedPtr credential = + MakeRefCounted(options); + ChannelArgs new_args; + RefCountedPtr connector = + credential->create_security_connector(nullptr, kTargetName, &new_args); + EXPECT_NE(connector, nullptr); + TlsChannelSecurityConnector* tls_connector = + static_cast(connector.get()); + EXPECT_NE(tls_connector->ClientHandshakerFactoryForTesting(), nullptr); + // Construct a basic TSI Peer. + std::string expected_subject = + "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"; + tsi_peer peer; + EXPECT_EQ(tsi_construct_peer(2, &peer), TSI_OK); + EXPECT_EQ( + tsi_construct_string_peer_property(TSI_SSL_ALPN_SELECTED_PROTOCOL, "grpc", + strlen("grpc"), &peer.properties[0]), + TSI_OK); + EXPECT_EQ(tsi_construct_string_peer_property_from_cstring( + TSI_X509_VERIFIED_ROOT_CERT_SUBECT_PEER_PROPERTY, + expected_subject.c_str(), &peer.properties[1]), + TSI_OK); + RefCountedPtr auth_context; + ExecCtx exec_ctx; + grpc_closure* on_peer_checked = GRPC_CLOSURE_CREATE( + VerifyExpectedErrorCallback, nullptr, grpc_schedule_on_exec_ctx); + ChannelArgs args; + tls_connector->check_peer(peer, nullptr, args, &auth_context, + on_peer_checked); +} + // // Tests for Certificate Providers in ServerSecurityConnector. // @@ -1089,6 +1129,41 @@ TEST_F(TlsSecurityConnectorTest, core_external_verifier->Unref(); } +TEST_F(TlsSecurityConnectorTest, + ServerSecurityConnectorWithVerifiedRootSubjectCertSucceeds) { + auto* sync_verifier = new SyncExternalVerifier(true); + ExternalCertificateVerifier core_external_verifier(sync_verifier->base()); + RefCountedPtr options = + MakeRefCounted(); + options->set_cert_request_type( + GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY); + options->set_certificate_verifier(core_external_verifier.Ref()); + auto provider = + MakeRefCounted("", PemKeyCertPairList()); + options->set_certificate_provider(std::move(provider)); + options->set_watch_identity_pair(true); + auto credentials = MakeRefCounted(options); + auto connector = credentials->create_security_connector(ChannelArgs()); + // Construct a basic TSI Peer. + std::string expected_subject = + "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"; + tsi_peer peer; + EXPECT_EQ(tsi_construct_peer(2, &peer), TSI_OK); + EXPECT_EQ( + tsi_construct_string_peer_property(TSI_SSL_ALPN_SELECTED_PROTOCOL, "grpc", + strlen("grpc"), &peer.properties[0]), + TSI_OK); + EXPECT_EQ(tsi_construct_string_peer_property_from_cstring( + TSI_X509_VERIFIED_ROOT_CERT_SUBECT_PEER_PROPERTY, + expected_subject.c_str(), &peer.properties[1]), + TSI_OK); + RefCountedPtr auth_context; + ExecCtx exec_ctx; + grpc_closure* on_peer_checked = GRPC_CLOSURE_CREATE( + VerifyExpectedErrorCallback, nullptr, grpc_schedule_on_exec_ctx); + ChannelArgs args; + connector->check_peer(peer, nullptr, args, &auth_context, on_peer_checked); +} } // namespace testing } // namespace grpc_core diff --git a/test/core/util/tls_utils.cc b/test/core/util/tls_utils.cc index e0b55fea2ce..27f90c4d272 100644 --- a/test/core/util/tls_utils.cc +++ b/test/core/util/tls_utils.cc @@ -180,6 +180,28 @@ void AsyncExternalVerifier::WorkerThread(void* arg) { } } +int PeerPropertyExternalVerifier::Verify( + void* user_data, grpc_tls_custom_verification_check_request* request, + grpc_tls_on_custom_verification_check_done_cb, void*, + grpc_status_code* sync_status, char** sync_error_details) { + auto* self = static_cast(user_data); + if (request->peer_info.verified_root_cert_subject != + self->expected_verified_root_cert_subject_) { + *sync_status = GRPC_STATUS_UNAUTHENTICATED; + *sync_error_details = gpr_strdup("PeerPropertyExternalVerifier failed"); + return true; + } else { + *sync_status = GRPC_STATUS_OK; + return true; // Synchronous call + } + return true; // Synchronous call +} + +void PeerPropertyExternalVerifier::Destruct(void* user_data) { + auto* self = static_cast(user_data); + delete self; +} + } // namespace testing } // namespace grpc_core diff --git a/test/core/util/tls_utils.h b/test/core/util/tls_utils.h index 6e0acb60305..24dd6f9f136 100644 --- a/test/core/util/tls_utils.h +++ b/test/core/util/tls_utils.h @@ -19,6 +19,7 @@ #include #include +#include #include "absl/base/thread_annotations.h" #include "absl/strings/string_view.h" @@ -142,6 +143,39 @@ class AsyncExternalVerifier { std::deque queue_ ABSL_GUARDED_BY(mu_); }; +// A synchronous external verifier implementation that verifies configured +// properties exist with the correct values. Note that it will delete itself in +// Destruct(), so create it like +// ``` +// auto* verifier_ = new PeerPropertyExternalVerifier(...); +// ``` +// and no need to delete it later. This is basically to keep consistent with the +// semantics in AsyncExternalVerifier. +class PeerPropertyExternalVerifier { + public: + explicit PeerPropertyExternalVerifier( + std::string expected_verified_root_cert_subject) + : expected_verified_root_cert_subject_( + std::move(expected_verified_root_cert_subject)), + base_{this, Verify, Cancel, Destruct} {} + + grpc_tls_certificate_verifier_external* base() { return &base_; } + + private: + static int Verify(void* user_data, + grpc_tls_custom_verification_check_request* request, + grpc_tls_on_custom_verification_check_done_cb callback, + void* callback_arg, grpc_status_code* sync_status, + char** sync_error_details); + + static void Cancel(void*, grpc_tls_custom_verification_check_request*) {} + + static void Destruct(void* user_data); + + std::string expected_verified_root_cert_subject_; + grpc_tls_certificate_verifier_external base_; +}; + } // namespace testing } // namespace grpc_core diff --git a/test/cpp/security/tls_certificate_verifier_test.cc b/test/cpp/security/tls_certificate_verifier_test.cc index b9c1af87f46..e86df6c7255 100644 --- a/test/cpp/security/tls_certificate_verifier_test.cc +++ b/test/cpp/security/tls_certificate_verifier_test.cc @@ -161,6 +161,55 @@ TEST(TlsCertificateVerifierTest, EXPECT_EQ(sync_status.error_message(), "Hostname Verification Check failed."); } +TEST(TlsCertificateVerifierTest, VerifiedRootCertSubjectVerifierSucceeds) { + grpc_tls_custom_verification_check_request request; + constexpr char kExpectedSubject[] = + "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"; + request.peer_info.verified_root_cert_subject = kExpectedSubject; + auto verifier = + ExternalCertificateVerifier::Create( + kExpectedSubject); + TlsCustomVerificationCheckRequest cpp_request(&request); + grpc::Status sync_status; + bool is_sync = verifier->Verify(&cpp_request, nullptr, &sync_status); + EXPECT_TRUE(is_sync); + EXPECT_TRUE(sync_status.ok()) + << sync_status.error_code() << " " << sync_status.error_message(); +} + +TEST(TlsCertificateVerifierTest, VerifiedRootCertSubjectVerifierFailsNull) { + grpc_tls_custom_verification_check_request request; + constexpr char kExpectedSubject[] = + "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"; + request.peer_info.verified_root_cert_subject = nullptr; + auto verifier = + ExternalCertificateVerifier::Create( + kExpectedSubject); + TlsCustomVerificationCheckRequest cpp_request(&request); + EXPECT_EQ(cpp_request.verified_root_cert_subject(), ""); + grpc::Status sync_status; + verifier->Verify(&cpp_request, nullptr, &sync_status); + EXPECT_EQ(sync_status.error_code(), grpc::StatusCode::UNAUTHENTICATED); + EXPECT_EQ(sync_status.error_message(), + "VerifiedRootCertSubjectVerifier failed"); +} + +TEST(TlsCertificateVerifierTest, VerifiedRootCertSubjectVerifierFailsMismatch) { + grpc_tls_custom_verification_check_request request; + constexpr char kExpectedSubject[] = + "CN=testca,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"; + request.peer_info.verified_root_cert_subject = "BAD_SUBJECT"; + auto verifier = + ExternalCertificateVerifier::Create( + kExpectedSubject); + TlsCustomVerificationCheckRequest cpp_request(&request); + grpc::Status sync_status; + verifier->Verify(&cpp_request, nullptr, &sync_status); + EXPECT_EQ(sync_status.error_code(), grpc::StatusCode::UNAUTHENTICATED); + EXPECT_EQ(sync_status.error_message(), + "VerifiedRootCertSubjectVerifier failed"); +} + } // namespace } // namespace testing } // namespace grpc diff --git a/test/cpp/util/tls_test_utils.cc b/test/cpp/util/tls_test_utils.cc index cfdfcca7988..3a8ab04b5a8 100644 --- a/test/cpp/util/tls_test_utils.cc +++ b/test/cpp/util/tls_test_utils.cc @@ -94,5 +94,17 @@ void AsyncCertificateVerifier::WorkerThread(void* arg) { } } +bool VerifiedRootCertSubjectVerifier::Verify( + TlsCustomVerificationCheckRequest* request, + std::function, grpc::Status* sync_status) { + if (request->verified_root_cert_subject() != expected_subject_) { + *sync_status = grpc::Status(grpc::StatusCode::UNAUTHENTICATED, + "VerifiedRootCertSubjectVerifier failed"); + } else { + *sync_status = grpc::Status::OK; + } + return true; +} + } // namespace testing } // namespace grpc diff --git a/test/cpp/util/tls_test_utils.h b/test/cpp/util/tls_test_utils.h index ff155a0f0af..6f7d84acaa9 100644 --- a/test/cpp/util/tls_test_utils.h +++ b/test/cpp/util/tls_test_utils.h @@ -76,6 +76,25 @@ class AsyncCertificateVerifier std::deque queue_ ABSL_GUARDED_BY(mu_); }; +class VerifiedRootCertSubjectVerifier + : public grpc::experimental::ExternalCertificateVerifier { + public: + explicit VerifiedRootCertSubjectVerifier(absl::string_view expected_subject) + : expected_subject_(expected_subject) {} + + ~VerifiedRootCertSubjectVerifier() override {} + + bool Verify(grpc::experimental::TlsCustomVerificationCheckRequest* request, + std::function callback, + grpc::Status* sync_status) override; + + void Cancel(grpc::experimental::TlsCustomVerificationCheckRequest*) override { + } + + private: + std::string expected_subject_; +}; + } // namespace testing } // namespace grpc diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index fbeeba34bb5..f92f89bac0a 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -3791,6 +3791,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "h2_tls_peer_property_external_verifier_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": true + }, { "args": [], "benchmark": false,