blob: 0053ac177558f5f695bdaec2ed2ebc40559e8535 [file] [log] [blame]
// Copyright 2021 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 <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <memory>
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include "src/core/lib/gprpp/crash.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/security/security_connector/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 "test/core/tsi/transport_security_test_lib.h"
#include "test/core/util/test_config.h"
#include "test/core/util/tls_utils.h"
extern "C" {
#include <openssl/crypto.h>
#include <openssl/pem.h>
}
namespace {
const char* kSslTsiTestCrlSupportedCredentialsDir =
"test/core/tsi/test_creds/crl_data/";
const char* kSslTsiTestCrlSupportedCrlDir =
"test/core/tsi/test_creds/crl_data/crls/";
const char* kSslTsiTestCrlSupportedCrlDirMissingIntermediate =
"test/core/tsi/test_creds/crl_data/"
"crls_missing_intermediate/";
const char* kSslTsiTestCrlSupportedCrlDirMissingRoot =
"test/core/tsi/test_creds/crl_data/crls_missing_root/";
const char* kSslTsiTestFaultyCrlsDir = "bad_path/";
const char* kRevokedKeyPath =
"test/core/tsi/test_creds/crl_data/revoked.key";
const char* kRevokedCertPath =
"test/core/tsi/test_creds/crl_data/revoked.pem";
const char* kValidKeyPath =
"test/core/tsi/test_creds/crl_data/valid.key";
const char* kValidCertPath =
"test/core/tsi/test_creds/crl_data/valid.pem";
const char* kRevokedIntermediateKeyPath =
"test/core/tsi/test_creds/crl_data/"
"leaf_signed_by_intermediate.key";
const char* kRevokedIntermediateCertPath =
"test/core/tsi/test_creds/crl_data/"
"leaf_and_intermediate_chain.pem";
const char* kRootCrlPath =
"test/core/tsi/test_creds/crl_data/crls/current.crl";
const char* kIntermediateCrlPath =
"test/core/tsi/test_creds/crl_data/crls/intermediate.crl";
class CrlSslTransportSecurityTest
: 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,
const char* crl_directory,
std::shared_ptr<grpc_core::experimental::CrlProvider> crl_provider,
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());
root_cert_ = grpc_core::testing::GetFileContents(
absl::StrCat(kSslTsiTestCrlSupportedCredentialsDir, "ca.pem").data());
root_store_ = tsi_ssl_root_certs_store_create(root_cert_.c_str());
crl_directory_ = crl_directory;
crl_provider_ = std::move(crl_provider);
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();
GPR_ASSERT(root_store_ != nullptr);
}
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_root_certs_store_destroy(root_store_);
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) {
GPR_ASSERT(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_root_certs = root_cert_.c_str();
client_options.pem_key_cert_pair = client_pem_key_cert_pairs_;
client_options.crl_directory = crl_directory_;
client_options.crl_provider = crl_provider_;
client_options.root_store = root_store_;
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;
server_options.pem_client_root_certs = root_cert_.c_str();
server_options.crl_directory = crl_directory_;
server_options.crl_provider = crl_provider_;
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,
&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) {
GPR_ASSERT(fixture != nullptr);
auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
self->CheckHandshakerPeers();
}
void CheckHandshakerPeers() {
// In TLS 1.3, the client-side handshake succeeds even if the client
// sends a revoked certificate. In such a case, the server would fail
// the TLS handshake and send an alert to the client as the first
// application data message. In TLS 1.2, the client-side handshake will
// fail if the client sends a revoked certificate.
//
// For OpenSSL versions < 1.1, TLS 1.3 is not supported, so the
// client-side handshake should succeed precisely when the server-side
// handshake succeeds.
//
// For the intermediate cases, we have a CA -> Intermediate CA -> Leaf
// Cert chain in which the Intermediate CA cert is revoked by the CA. We
// test 3 cases. Note: A CRL not existing should not make the handshake
// fail
// 1. CRL Directory with CA's CRL and Intermediate CA's CRL -> Handshake
// fails due to revoked cert
// 2. CRL Directory with CA's CRL but missing Intermediate CA's CRL ->
// Handshake fails due to revoked cert
// 3. CRL Directory without CA's CRL with but Intermediate CA's CRL ->
// Handshake succeeds because the CRL that revokes the cert is not
// present.
bool expect_server_success = expect_server_success_;
#if OPENSSL_VERSION_NUMBER >= 0x10100000
bool 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 root_cert_;
tsi_ssl_root_certs_store* root_store_;
tsi_ssl_server_handshaker_factory* server_handshaker_factory_;
tsi_ssl_client_handshaker_factory* client_handshaker_factory_;
std::string server_key_;
std::string server_cert_;
std::string client_key_;
std::string client_cert_;
const char* crl_directory_;
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_;
std::shared_ptr<grpc_core::experimental::CrlProvider> crl_provider_;
};
};
struct tsi_test_fixture_vtable
CrlSslTransportSecurityTest::SslTsiTestFixture::kVtable = {
&CrlSslTransportSecurityTest::SslTsiTestFixture::SetupHandshakers,
&CrlSslTransportSecurityTest::SslTsiTestFixture::CheckHandshakerPeers,
&CrlSslTransportSecurityTest::SslTsiTestFixture::Destruct};
TEST_P(CrlSslTransportSecurityTest, RevokedServerCert) {
auto* fixture = new SslTsiTestFixture(
kRevokedKeyPath, kRevokedCertPath, kValidKeyPath, kValidCertPath,
kSslTsiTestCrlSupportedCrlDir, nullptr, false, false, false);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, RevokedClientCert) {
auto* fixture = new SslTsiTestFixture(
kValidKeyPath, kValidCertPath, kRevokedKeyPath, kRevokedCertPath,
kSslTsiTestCrlSupportedCrlDir, nullptr, false, false, true);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, ValidCerts) {
auto* fixture = new SslTsiTestFixture(
kValidKeyPath, kValidCertPath, kValidKeyPath, kValidCertPath,
kSslTsiTestCrlSupportedCrlDir, nullptr, true, true, true);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, UseFaultyCrlDirectory) {
auto* fixture = new SslTsiTestFixture(
kRevokedKeyPath, kRevokedCertPath, kValidKeyPath, kValidCertPath,
kSslTsiTestFaultyCrlsDir, nullptr, true, true, true);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, UseRevokedIntermediateValidCrl) {
auto* fixture = new SslTsiTestFixture(
kRevokedIntermediateKeyPath, kRevokedIntermediateCertPath, kValidKeyPath,
kValidCertPath, kSslTsiTestCrlSupportedCrlDir, nullptr, false, false,
false);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest,
UseRevokedIntermediateWithMissingIntermediateCrl) {
auto* fixture = new SslTsiTestFixture(
kRevokedIntermediateKeyPath, kRevokedIntermediateCertPath, kValidKeyPath,
kValidCertPath, kSslTsiTestCrlSupportedCrlDirMissingIntermediate, nullptr,
false, false, false);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, UseRevokedIntermediateWithMissingRootCrl) {
auto* fixture = new SslTsiTestFixture(
kRevokedIntermediateKeyPath, kRevokedIntermediateCertPath, kValidKeyPath,
kValidCertPath, kSslTsiTestCrlSupportedCrlDirMissingRoot, nullptr, true,
true, true);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, CrlProviderValidCerts) {
std::string root_crl = grpc_core::testing::GetFileContents(kRootCrlPath);
std::string intermediate_crl =
grpc_core::testing::GetFileContents(kIntermediateCrlPath);
absl::StatusOr<std::shared_ptr<grpc_core::experimental::CrlProvider>>
provider = grpc_core::experimental::CreateStaticCrlProvider(
{root_crl, intermediate_crl});
ASSERT_TRUE(provider.ok());
auto* fixture = new SslTsiTestFixture(kValidKeyPath, kValidCertPath,
kValidKeyPath, kValidCertPath, nullptr,
*provider, true, true, true);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, CrlProviderRevokedServer) {
std::string root_crl = grpc_core::testing::GetFileContents(kRootCrlPath);
std::string intermediate_crl =
grpc_core::testing::GetFileContents(kIntermediateCrlPath);
absl::StatusOr<std::shared_ptr<grpc_core::experimental::CrlProvider>>
provider = grpc_core::experimental::CreateStaticCrlProvider(
{root_crl, intermediate_crl});
ASSERT_TRUE(provider.ok());
auto* fixture = new SslTsiTestFixture(kRevokedKeyPath, kRevokedCertPath,
kValidKeyPath, kValidCertPath, nullptr,
*provider, false, false, false);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, CrlProviderRevokedClient) {
std::string root_crl = grpc_core::testing::GetFileContents(kRootCrlPath);
std::string intermediate_crl =
grpc_core::testing::GetFileContents(kIntermediateCrlPath);
absl::StatusOr<std::shared_ptr<grpc_core::experimental::CrlProvider>>
provider = grpc_core::experimental::CreateStaticCrlProvider(
{root_crl, intermediate_crl});
ASSERT_TRUE(provider.ok());
auto* fixture = new SslTsiTestFixture(kValidKeyPath, kValidCertPath,
kRevokedKeyPath, kRevokedCertPath,
nullptr, *provider, false, false, true);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest, CrlProviderRevokedIntermediateValidCrl) {
std::string root_crl = grpc_core::testing::GetFileContents(kRootCrlPath);
std::string intermediate_crl =
grpc_core::testing::GetFileContents(kIntermediateCrlPath);
absl::StatusOr<std::shared_ptr<grpc_core::experimental::CrlProvider>>
provider = grpc_core::experimental::CreateStaticCrlProvider(
{root_crl, intermediate_crl});
ASSERT_TRUE(provider.ok());
auto* fixture = new SslTsiTestFixture(
kRevokedIntermediateKeyPath, kRevokedIntermediateCertPath, kValidKeyPath,
kValidCertPath, nullptr, *provider, false, false, false);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest,
CrlProviderRevokedIntermediateMissingIntermediateCrl) {
std::string root_crl = grpc_core::testing::GetFileContents(kRootCrlPath);
absl::StatusOr<std::shared_ptr<grpc_core::experimental::CrlProvider>>
provider = grpc_core::experimental::CreateStaticCrlProvider({root_crl});
ASSERT_TRUE(provider.ok());
auto* fixture = new SslTsiTestFixture(
kRevokedIntermediateKeyPath, kRevokedIntermediateCertPath, kValidKeyPath,
kValidCertPath, nullptr, *provider, false, false, false);
fixture->Run();
}
TEST_P(CrlSslTransportSecurityTest,
CrlProviderRevokedIntermediateMissingRootCrl) {
std::string intermediate_crl =
grpc_core::testing::GetFileContents(kIntermediateCrlPath);
absl::StatusOr<std::shared_ptr<grpc_core::experimental::CrlProvider>>
provider =
grpc_core::experimental::CreateStaticCrlProvider({intermediate_crl});
ASSERT_TRUE(provider.ok());
auto* fixture = new SslTsiTestFixture(
kRevokedIntermediateKeyPath, kRevokedIntermediateCertPath, kValidKeyPath,
kValidCertPath, nullptr, *provider, true, true, true);
fixture->Run();
}
std::string TestNameSuffix(
const ::testing::TestParamInfo<tsi_tls_version>& version) {
if (version.param == tsi_tls_version::TSI_TLS1_2) return "TLS_1_2";
GPR_ASSERT(version.param == tsi_tls_version::TSI_TLS1_3);
return "TLS_1_3";
}
// TODO(gtcooke94) Add nullptr issuer test cases - this is not simple to test
// the way the code is currently designed - we plan to refactor ways the OpenSSL
// callback functions are written to have more easily testable chunks in
// ssl_transport_security_utils and will add nullptr issuer tests at that time.
INSTANTIATE_TEST_SUITE_P(TLSVersionsTest, CrlSslTransportSecurityTest,
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();
}