blob: 93e75bb091c5e06eaed8c82a9937662f3138bab9 [file]
// Copyright 2025 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 <grpc/grpc.h>
#include <grpc/support/alloc.h>
#include <grpc/support/string_util.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <memory>
#include <utility>
#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/core/credentials/transport/security_connector.h"
#include "src/core/tsi/ssl_transport_security.h"
#include "src/core/tsi/transport_security.h"
#include "src/core/tsi/transport_security_interface.h"
#include "src/core/util/crash.h"
#include "test/core/test_util/test_config.h"
#include "test/core/test_util/tls_utils.h"
#include "test/core/tsi/transport_security_test_lib.h"
extern "C" {
#include <openssl/crypto.h>
#include <openssl/pem.h>
}
namespace {
constexpr absl::string_view kCaPemPath =
"test/core/tsi/test_creds/spiffe_end2end/ca.pem";
constexpr absl::string_view kClientKeyPath =
"test/core/tsi/test_creds/spiffe_end2end/client.key";
constexpr absl::string_view kClientCertPath =
"test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem";
constexpr absl::string_view kServerKeyPath =
"test/core/tsi/test_creds/spiffe_end2end/server.key";
constexpr absl::string_view kServerCertPath =
"test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem";
constexpr absl::string_view kServerChainKeyPath =
"test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.key";
constexpr absl::string_view kServerChainCertPath =
"test/core/tsi/test_creds/spiffe_end2end/leaf_and_intermediate_chain.pem";
constexpr absl::string_view kClientSpiffeBundleMapPath =
"test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json";
constexpr absl::string_view kServerSpiffeBundleMapPath =
"test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json";
constexpr absl::string_view kNonSpiffeKeyPath =
"test/core/tsi/test_creds/crl_data/valid.key";
constexpr absl::string_view kNonSpiffeCertPath =
"test/core/tsi/test_creds/crl_data/valid.pem";
constexpr absl::string_view kMultiSanKeyPath =
"test/core/tsi/test_creds/spiffe_end2end/multi_san.key";
constexpr absl::string_view kMultiSanCertPath =
"test/core/tsi/test_creds/spiffe_end2end/multi_san_spiffe.pem";
constexpr absl::string_view kInvalidUtf8SanKeyPath =
"test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san.key";
constexpr absl::string_view kInvalidUtf8SanCertPath =
"test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san_spiffe.pem";
class SpiffeSslTransportSecurityTest
: public testing::TestWithParam<tsi_tls_version> {
protected:
// A tsi_test_fixture implementation.
class SslTsiTestFixture {
public:
SslTsiTestFixture(
absl::string_view server_key_path, absl::string_view server_cert_path,
absl::string_view client_key_path, absl::string_view client_cert_path,
absl::string_view server_spiffe_bundle_map_path,
absl::string_view client_spiffe_bundle_map_path,
std::optional<absl::string_view> ca_path, bool expect_server_success,
bool expect_client_success_1_2, bool expect_client_success_1_3) {
tsi_test_fixture_init(&base_);
base_.test_unused_bytes = true;
base_.vtable = &kVtable;
server_key_ = grpc_core::testing::GetFileContents(server_key_path.data());
server_cert_ =
grpc_core::testing::GetFileContents(server_cert_path.data());
client_key_ = grpc_core::testing::GetFileContents(client_key_path.data());
client_cert_ =
grpc_core::testing::GetFileContents(client_cert_path.data());
// We set this and it shouldn't matter if we set spiffe bundles
if (ca_path.has_value()) {
ca_certificates_ = grpc_core::testing::GetFileContents(ca_path->data());
}
if (!server_spiffe_bundle_map_path.empty()) {
auto server_map =
grpc_core::SpiffeBundleMap::FromFile(server_spiffe_bundle_map_path);
CHECK(server_map.ok());
server_spiffe_bundle_map_ = std::make_shared<RootCertInfo>(*server_map);
}
if (!client_spiffe_bundle_map_path.empty()) {
auto client_map =
grpc_core::SpiffeBundleMap::FromFile(client_spiffe_bundle_map_path);
CHECK(client_map.ok());
client_spiffe_bundle_map_ = std::make_shared<RootCertInfo>(*client_map);
}
expect_server_success_ = expect_server_success;
expect_client_success_1_2_ = expect_client_success_1_2;
expect_client_success_1_3_ = expect_client_success_1_3;
server_pem_key_cert_pairs_ = static_cast<tsi_ssl_pem_key_cert_pair*>(
gpr_malloc(sizeof(tsi_ssl_pem_key_cert_pair)));
server_pem_key_cert_pairs_[0].private_key = server_key_.c_str();
server_pem_key_cert_pairs_[0].cert_chain = server_cert_.c_str();
client_pem_key_cert_pairs_ = static_cast<tsi_ssl_pem_key_cert_pair*>(
gpr_malloc(sizeof(tsi_ssl_pem_key_cert_pair)));
client_pem_key_cert_pairs_[0].private_key = client_key_.c_str();
client_pem_key_cert_pairs_[0].cert_chain = client_cert_.c_str();
}
void Run() {
tsi_test_do_handshake(&base_);
tsi_test_fixture_destroy(&base_);
}
~SslTsiTestFixture() {
gpr_free(server_pem_key_cert_pairs_);
gpr_free(client_pem_key_cert_pairs_);
tsi_ssl_server_handshaker_factory_unref(server_handshaker_factory_);
tsi_ssl_client_handshaker_factory_unref(client_handshaker_factory_);
}
private:
static void SetupHandshakers(tsi_test_fixture* fixture) {
CHECK_NE(fixture, nullptr);
auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
self->SetupHandshakers();
}
void SetupHandshakers() {
// Create client handshaker factory.
tsi_ssl_client_handshaker_options client_options;
client_options.pem_key_cert_pair = client_pem_key_cert_pairs_;
if (client_spiffe_bundle_map_ != nullptr) {
client_options.root_cert_info = client_spiffe_bundle_map_;
} else {
client_options.root_cert_info =
std::make_shared<RootCertInfo>(ca_certificates_);
}
client_options.min_tls_version = GetParam();
client_options.max_tls_version = GetParam();
EXPECT_EQ(tsi_create_ssl_client_handshaker_factory_with_options(
&client_options, &client_handshaker_factory_),
TSI_OK);
// Create server handshaker factory.
tsi_ssl_server_handshaker_options server_options;
server_options.pem_key_cert_pairs = server_pem_key_cert_pairs_;
server_options.num_key_cert_pairs = 1;
if (server_spiffe_bundle_map_ != nullptr) {
server_options.root_cert_info = server_spiffe_bundle_map_;
} else {
server_options.root_cert_info =
std::make_shared<RootCertInfo>(ca_certificates_);
}
server_options.client_certificate_request =
TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY;
server_options.session_ticket_key = nullptr;
server_options.session_ticket_key_size = 0;
server_options.min_tls_version = GetParam();
server_options.max_tls_version = GetParam();
EXPECT_EQ(tsi_create_ssl_server_handshaker_factory_with_options(
&server_options, &server_handshaker_factory_),
TSI_OK);
// Create server and client handshakers.
EXPECT_EQ(tsi_ssl_client_handshaker_factory_create_handshaker(
client_handshaker_factory_, nullptr, 0, 0,
/*alpn_preferred_protocol_list=*/std::nullopt,
&base_.client_handshaker),
TSI_OK);
EXPECT_EQ(tsi_ssl_server_handshaker_factory_create_handshaker(
server_handshaker_factory_, 0, 0, &base_.server_handshaker),
TSI_OK);
}
static void CheckHandshakerPeers(tsi_test_fixture* fixture) {
CHECK_NE(fixture, nullptr);
auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
self->CheckHandshakerPeers();
}
void CheckHandshakerPeers() {
bool expect_server_success = expect_server_success_;
bool expect_client_success = false;
#if OPENSSL_VERSION_NUMBER >= 0x10100000
expect_client_success = GetParam() == tsi_tls_version::TSI_TLS1_2
? expect_client_success_1_2_
: expect_client_success_1_3_;
#else
// If using OpenSSL version < 1.1, the CRL revocation won't
// be enabled anyways, so we always expect the connection to
// be successful.
expect_server_success = true;
expect_client_success = expect_server_success;
#endif
tsi_peer peer;
if (expect_client_success) {
EXPECT_EQ(
tsi_handshaker_result_extract_peer(base_.client_result, &peer),
TSI_OK);
tsi_peer_destruct(&peer);
} else {
EXPECT_EQ(base_.client_result, nullptr);
}
if (expect_server_success) {
EXPECT_EQ(
tsi_handshaker_result_extract_peer(base_.server_result, &peer),
TSI_OK);
tsi_peer_destruct(&peer);
} else {
EXPECT_EQ(base_.server_result, nullptr);
}
}
static void Destruct(tsi_test_fixture* fixture) {
auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
delete self;
}
static struct tsi_test_fixture_vtable kVtable;
tsi_test_fixture base_;
std::string ca_certificates_;
tsi_ssl_server_handshaker_factory* server_handshaker_factory_;
tsi_ssl_client_handshaker_factory* client_handshaker_factory_;
std::shared_ptr<RootCertInfo> server_spiffe_bundle_map_;
std::shared_ptr<RootCertInfo> client_spiffe_bundle_map_;
std::string server_key_;
std::string server_cert_;
std::string client_key_;
std::string client_cert_;
bool expect_server_success_;
bool expect_client_success_1_2_;
bool expect_client_success_1_3_;
tsi_ssl_pem_key_cert_pair* client_pem_key_cert_pairs_;
tsi_ssl_pem_key_cert_pair* server_pem_key_cert_pairs_;
};
};
struct tsi_test_fixture_vtable
SpiffeSslTransportSecurityTest::SslTsiTestFixture::kVtable = {
&SpiffeSslTransportSecurityTest::SslTsiTestFixture::SetupHandshakers,
&SpiffeSslTransportSecurityTest::SslTsiTestFixture::
CheckHandshakerPeers,
&SpiffeSslTransportSecurityTest::SslTsiTestFixture::Destruct};
// Valid SPIFFE Bundles on both sides with the root configured for the
// appropriate trust domain
TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffe) {
auto* fixture = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
/*expect_server_success=*/true,
/*expect_client_success_1_2=*/true, /*expect_client_success_1_3=*/true);
fixture->Run();
}
// Valid SPIFFE Bundles on both sides with the root configured for the
// appropriate trust domain, and a certificate chain with an intermediate CA on
// the server side
TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffeChain) {
auto* fixture = new SslTsiTestFixture(
kServerChainKeyPath, kServerChainCertPath, kClientKeyPath,
kClientCertPath, kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath,
std::nullopt,
/*expect_server_success=*/true,
/*expect_client_success_1_2=*/true,
/*expect_client_success_1_3=*/true);
fixture->Run();
}
// Valid SPIFFE bundle on the client side, but the server side has a flat list
// of CA certificates.
TEST_P(SpiffeSslTransportSecurityTest, ClientSideSpiffeBundle) {
auto* fixture = new SslTsiTestFixture(kServerKeyPath, kServerCertPath,
kClientKeyPath, kClientCertPath, "",
kClientSpiffeBundleMapPath, kCaPemPath,
/*expect_server_success=*/true,
/*expect_client_success_1_2=*/true,
/*expect_client_success_1_3=*/true);
fixture->Run();
}
// Valid SPIFFE bundle on the server side, but the client side has a flat list
// of CA certificates.
TEST_P(SpiffeSslTransportSecurityTest, ServerSideSpiffeBundle) {
auto* fixture = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
kServerSpiffeBundleMapPath, "", kCaPemPath,
/*expect_server_success=*/true,
/*expect_client_success_1_2=*/true,
/*expect_client_success_1_3=*/true);
fixture->Run();
}
// Valid SPIFFE bundle on the client side, but the server side has a SPIFFE
// bundle that does not have a trust domain that will match the client leaf
// certificate. When negotiating TLS 1.3, the client-side handshake succeeds
// because server verification of the client certificate occurs after the
// client-side handshake is complete.
TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffeServerMismatchFail) {
auto* fixture = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
kClientSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
/*expect_server_success=*/false,
/*expect_client_success_1_2=*/false,
/*expect_client_success_1_3=*/true);
fixture->Run();
}
// Valid SPIFFE bundle on the server side, but the client side has a SPIFFE
// bundle that does not have a trust domain that will match the server leaf
// certificate.
TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffeClientMismatchFail) {
auto* fixture = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
kServerSpiffeBundleMapPath, kServerSpiffeBundleMapPath, std::nullopt,
/*expect_server_success=*/false,
/*expect_client_success_1_2=*/false,
/*expect_client_success_1_3=*/false);
fixture->Run();
}
// The client side is configured with only a SPIFFE bundle, but the server leaf
// certificate does not have a SPIFFE ID.
TEST_P(SpiffeSslTransportSecurityTest, NonSpiffeServerCertFail) {
auto* fixture = new SslTsiTestFixture(
kNonSpiffeKeyPath, kNonSpiffeCertPath, kClientKeyPath, kClientCertPath,
kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
/*expect_server_success=*/false,
/*expect_client_success_1_2=*/false,
/*expect_client_success_1_3=*/false);
fixture->Run();
}
// The server side is configured with only a SPIFFE bundle, but the client leaf
// certificate does not have a SPIFFE ID. When negotiating TLS 1.3, the
// client-side handshake succeeds because server verification of the client
// certificate occurs after the client-side handshake is complete.
TEST_P(SpiffeSslTransportSecurityTest, NonSpiffeClientCertFail) {
// TLS1.3 client will pass because it validates the server
auto* fixture = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kNonSpiffeKeyPath, kNonSpiffeCertPath,
kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
/*expect_server_success=*/false,
/*expect_client_success_1_2=*/false,
/*expect_client_success_1_3=*/true);
fixture->Run();
}
// The server side is configued with a SPIFFE bundle, but the client side has a
// certificate with multiple URI SANs which should fail SPIFFE verification. The
// client's certificate is otherwise valid. This specific failure should show up
// in logs. If SPIFFE verification is NOT done, we would expect this to pass -
// it's a function of the SPIFFE spec to fail on multiple URI SANs. We verify
// that the certificates used here would otherwise succeed when the root CA is
// used directly rather than the SPIFFE Bundle Map, then that same setup fails
// when a SPIFFE Bundle Map is used.
TEST_P(SpiffeSslTransportSecurityTest, MultiSanSpiffeCertFails) {
// Passes because SPIFFE verification is not done, and this would be valid in
// that case.
auto* fixture_pass =
new SslTsiTestFixture(kServerKeyPath, kServerCertPath, kMultiSanKeyPath,
kMultiSanCertPath, "", "", kCaPemPath,
/*expect_server_success=*/true,
/*expect_client_success_1_2=*/true,
/*expect_client_success_1_3=*/true);
fixture_pass->Run();
// Should fail SPIFFE verification because of multiple URI SANs.
auto* fixture_fail = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kMultiSanKeyPath, kMultiSanCertPath,
kServerSpiffeBundleMapPath, "", kCaPemPath,
/*expect_server_success=*/false,
/*expect_client_success_1_2=*/false,
/*expect_client_success_1_3=*/true);
fixture_fail->Run();
}
// The server side is configued with a SPIFFE bundle, but the client side has a
// certificate with multiple URI SANs which should fail SPIFFE verification. The
// client's certificate is otherwise valid. This specific failure should show up
// in logs. If SPIFFE verification is NOT done, we would expect this to pass -
// it's a function of the SPIFFE spec to fail on multiple URI SANs. We verify
// that the certificates used here would otherwise succeed when the root CA is
// used directly rather than the SPIFFE Bundle Map, then that same setup fails
// when a SPIFFE Bundle Map is used.
TEST_P(SpiffeSslTransportSecurityTest, InvalidUTF8Fails) {
// Passes because SPIFFE verification is not done, and this would be valid in
// that case.
auto* fixture_pass = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kInvalidUtf8SanKeyPath,
kInvalidUtf8SanCertPath, "", "", kCaPemPath,
/*expect_server_success=*/true,
/*expect_client_success_1_2=*/true,
/*expect_client_success_1_3=*/true);
fixture_pass->Run();
// Should fail SPIFFE verification because of multiple URI SANs.
auto* fixture_fail = new SslTsiTestFixture(
kServerKeyPath, kServerCertPath, kInvalidUtf8SanKeyPath,
kInvalidUtf8SanCertPath, kServerSpiffeBundleMapPath, "", kCaPemPath,
/*expect_server_success=*/false,
/*expect_client_success_1_2=*/false,
/*expect_client_success_1_3=*/true);
fixture_fail->Run();
}
std::string TestNameSuffix(
const ::testing::TestParamInfo<tsi_tls_version>& version) {
if (version.param == tsi_tls_version::TSI_TLS1_2) return "TLS_1_2";
CHECK(version.param == tsi_tls_version::TSI_TLS1_3);
return "TLS_1_3";
}
INSTANTIATE_TEST_SUITE_P(TLSVersionsTest, SpiffeSslTransportSecurityTest,
testing::Values(tsi_tls_version::TSI_TLS1_2,
tsi_tls_version::TSI_TLS1_3),
&TestNameSuffix);
} // namespace
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(&argc, argv);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}