Added verified_root_cert_subject pass up through cpp api (#32335)

PR #32215 added the verified root cert subject to the lower level
`tsi_peer`. This PR is a companion to that and completes the feature by
bubbling the information up to the `TsiCustomVerificationCheckRequest`
which is part of the user facing API for implementing custom
verification callbacks.
pull/32570/head
Gregory Cooke 2 years ago committed by GitHub
parent 86bd0721ea
commit ca9e365002
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      CMakeLists.txt
  2. 11
      build_autogenerated.yaml
  3. 4
      include/grpc/grpc_security.h
  4. 7
      include/grpcpp/security/tls_certificate_verifier.h
  5. 12
      src/core/lib/security/security_connector/tls/tls_security_connector.cc
  6. 7
      src/cpp/common/tls_certificate_verifier.cc
  7. 26
      test/core/end2end/BUILD
  8. 334
      test/core/end2end/h2_tls_peer_property_external_verifier_test.cc
  9. 75
      test/core/security/tls_security_connector_test.cc
  10. 22
      test/core/util/tls_utils.cc
  11. 34
      test/core/util/tls_utils.h
  12. 49
      test/cpp/security/tls_certificate_verifier_test.cc
  13. 12
      test/cpp/util/tls_test_utils.cc
  14. 19
      test/cpp/util/tls_test_utils.h
  15. 24
      tools/run_tests/generated/tests.json

39
CMakeLists.txt generated

@ -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)

@ -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

@ -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;

@ -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<grpc::string_ref> uri_names() const;
std::vector<grpc::string_ref> dns_names() const;
std::vector<grpc::string_ref> email_names() const;

@ -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<char*> uri_names;
std::vector<char*> dns_names;
std::vector<char*> 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<char*>(request->peer_info.peer_cert_full_chain));
}
if (request->peer_info.verified_root_cert_subject != nullptr) {
gpr_free(const_cast<char*>(request->peer_info.verified_root_cert_subject));
}
}
tsi_ssl_pem_key_cert_pair* ConvertToTsiPemKeyCertPair(

@ -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<grpc::string_ref> TlsCustomVerificationCheckRequest::uri_names()
const {
std::vector<grpc::string_ref> uri_names;

@ -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",
],
)

@ -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 <stdint.h>
#include <string.h>
#include <string>
#include <gtest/gtest.h>
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
#include <grpc/grpc_security_constants.h>
#include <grpc/impl/propagation_bits.h>
#include <grpc/slice.h>
#include <grpc/status.h>
#include <grpc/support/log.h>
#include <grpc/support/time.h>
#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<void*>(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<const char*> GRPC_SLICE_START_PTR(ca_slice);
const char* server_cert =
reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
const char* server_key =
reinterpret_cast<const char*> 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<const char*> GRPC_SLICE_START_PTR(ca_slice);
const char* client_cert =
reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
const char* client_key =
reinterpret_cast<const char*> 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<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
const_cast<char*>("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<size_t>(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<size_t>(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;
}

@ -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<grpc_tls_credentials_options> options =
MakeRefCounted<grpc_tls_credentials_options>();
options->set_verify_server_cert(true);
options->set_certificate_verifier(core_external_verifier.Ref());
options->set_check_call_host(false);
RefCountedPtr<TlsCredentials> credential =
MakeRefCounted<TlsCredentials>(options);
ChannelArgs new_args;
RefCountedPtr<grpc_channel_security_connector> connector =
credential->create_security_connector(nullptr, kTargetName, &new_args);
EXPECT_NE(connector, nullptr);
TlsChannelSecurityConnector* tls_connector =
static_cast<TlsChannelSecurityConnector*>(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<grpc_auth_context> 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<grpc_tls_credentials_options> options =
MakeRefCounted<grpc_tls_credentials_options>();
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<StaticDataCertificateProvider>("", PemKeyCertPairList());
options->set_certificate_provider(std::move(provider));
options->set_watch_identity_pair(true);
auto credentials = MakeRefCounted<TlsServerCredentials>(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<grpc_auth_context> 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

@ -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<PeerPropertyExternalVerifier*>(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<PeerPropertyExternalVerifier*>(user_data);
delete self;
}
} // namespace testing
} // namespace grpc_core

@ -19,6 +19,7 @@
#include <deque>
#include <string>
#include <utility>
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
@ -142,6 +143,39 @@ class AsyncExternalVerifier {
std::deque<Request> 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

@ -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<VerifiedRootCertSubjectVerifier>(
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<VerifiedRootCertSubjectVerifier>(
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<VerifiedRootCertSubjectVerifier>(
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

@ -94,5 +94,17 @@ void AsyncCertificateVerifier::WorkerThread(void* arg) {
}
}
bool VerifiedRootCertSubjectVerifier::Verify(
TlsCustomVerificationCheckRequest* request,
std::function<void(grpc::Status)>, 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

@ -76,6 +76,25 @@ class AsyncCertificateVerifier
std::deque<Request> 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<void(grpc::Status)> callback,
grpc::Status* sync_status) override;
void Cancel(grpc::experimental::TlsCustomVerificationCheckRequest*) override {
}
private:
std::string expected_subject_;
};
} // namespace testing
} // namespace grpc

@ -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,

Loading…
Cancel
Save