xref: /aosp_15_r20/external/grpc-grpc/test/core/security/grpc_tls_crl_provider_test.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1 //
2 //
3 // Copyright 2023 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18 
19 #include "src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h"
20 
21 #include <chrono>
22 #include <cstdlib>
23 #include <memory>
24 #include <string>
25 #include <vector>
26 
27 #include <gtest/gtest.h>
28 
29 #include "absl/status/status.h"
30 #include "absl/status/statusor.h"
31 #include "absl/strings/str_split.h"
32 #include "absl/strings/string_view.h"
33 
34 #include <grpc/grpc.h>
35 #include <grpc/grpc_audit_logging.h>
36 #include <grpc/grpc_crl_provider.h>
37 
38 #include "src/core/lib/event_engine/default_event_engine.h"
39 #include "src/core/lib/iomgr/timer_manager.h"
40 #include "test/core/event_engine/event_engine_test_utils.h"
41 #include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h"
42 #include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h"
43 #include "test/core/tsi/transport_security_test_lib.h"
44 #include "test/core/util/test_config.h"
45 #include "test/core/util/tls_utils.h"
46 
47 static constexpr absl::string_view kCrlPath =
48     "test/core/tsi/test_creds/crl_data/crls/current.crl";
49 static constexpr absl::string_view kCrlName = "current.crl";
50 static constexpr absl::string_view kCrlIntermediateIssuerPath =
51     "test/core/tsi/test_creds/crl_data/intermediate_ca.pem";
52 static constexpr absl::string_view kCrlDirectory =
53     "test/core/tsi/test_creds/crl_data/crls";
54 static constexpr absl::string_view kRootCert =
55     "test/core/tsi/test_creds/crl_data/ca.pem";
56 
57 using ::grpc_core::experimental::CertificateInfoImpl;
58 using ::grpc_core::experimental::Crl;
59 using ::grpc_core::experimental::CrlProvider;
60 
61 namespace grpc_core {
62 namespace testing {
63 
64 class FakeDirectoryReader : public DirectoryReader {
65  public:
66   ~FakeDirectoryReader() override = default;
ForEach(absl::FunctionRef<void (absl::string_view)> callback)67   absl::Status ForEach(
68       absl::FunctionRef<void(absl::string_view)> callback) override {
69     if (!files_in_directory_.ok()) {
70       return files_in_directory_.status();
71     }
72     for (const auto& file : *files_in_directory_) {
73       callback(file);
74     }
75     return absl::OkStatus();
76   }
Name() const77   absl::string_view Name() const override { return kCrlDirectory; }
78 
SetFilesInDirectory(std::vector<std::string> files)79   void SetFilesInDirectory(std::vector<std::string> files) {
80     files_in_directory_ = std::move(files);
81   }
82 
SetStatus(absl::Status status)83   void SetStatus(absl::Status status) { files_in_directory_ = status; }
84 
85  private:
86   absl::StatusOr<std::vector<std::string>> files_in_directory_ =
87       std::vector<std::string>();
88 };
89 
90 class CrlProviderTest : public ::testing::Test {
91  public:
SetUp()92   void SetUp() override {
93     std::string pem_cert = GetFileContents(kRootCert.data());
94     X509* issuer = ReadPemCert(pem_cert);
95     auto base_crl_issuer = IssuerFromCert(issuer);
96     ASSERT_EQ(base_crl_issuer.status(), absl::OkStatus());
97     base_crl_issuer_ = *base_crl_issuer;
98     std::string intermediate_string =
99         GetFileContents(kCrlIntermediateIssuerPath.data());
100     X509* intermediate_issuer = ReadPemCert(intermediate_string);
101     auto intermediate_crl_issuer = IssuerFromCert(intermediate_issuer);
102     ASSERT_EQ(intermediate_crl_issuer.status(), absl::OkStatus());
103     intermediate_crl_issuer_ = *intermediate_crl_issuer;
104     X509_free(issuer);
105     X509_free(intermediate_issuer);
106   }
107 
TearDown()108   void TearDown() override {}
109 
110  protected:
111   std::string base_crl_issuer_;
112   std::string intermediate_crl_issuer_;
113 };
114 
115 class DirectoryReloaderCrlProviderTest : public CrlProviderTest {
116  public:
SetUp()117   void SetUp() override {
118     CrlProviderTest::SetUp();
119     event_engine_ =
120         std::make_shared<grpc_event_engine::experimental::FuzzingEventEngine>(
121             grpc_event_engine::experimental::FuzzingEventEngine::Options(),
122             fuzzing_event_engine::Actions());
123     // Without this the test had a failure dealing with grpc timers on TSAN
124     grpc_timer_manager_set_start_threaded(false);
125     grpc_init();
126   }
TearDown()127   void TearDown() override {
128     ExecCtx exec_ctx;
129     event_engine_->FuzzingDone();
130     exec_ctx.Flush();
131     event_engine_->TickUntilIdle();
132     grpc_event_engine::experimental::WaitForSingleOwner(
133         std::move(event_engine_));
134     grpc_shutdown_blocking();
135     event_engine_.reset();
136   }
137 
138  protected:
139   // Tests that want a fake directory reader can call this without setting the
140   // last parameter.
CreateCrlProvider(std::chrono::seconds refresh_duration,std::function<void (absl::Status)> reload_error_callback,std::shared_ptr<DirectoryReader> directory_reader=nullptr)141   absl::StatusOr<std::shared_ptr<CrlProvider>> CreateCrlProvider(
142       std::chrono::seconds refresh_duration,
143       std::function<void(absl::Status)> reload_error_callback,
144       std::shared_ptr<DirectoryReader> directory_reader = nullptr) {
145     if (directory_reader == nullptr) directory_reader = directory_reader_;
146     auto provider =
147         std::make_shared<experimental::DirectoryReloaderCrlProvider>(
148             refresh_duration, std::move(reload_error_callback), event_engine_,
149             std::move(directory_reader));
150     provider->UpdateAndStartTimer();
151     return provider;
152   }
153 
154   // Tests that want a real directory can call this instead of the above.
CreateCrlProvider(absl::string_view directory,std::chrono::seconds refresh_duration,std::function<void (absl::Status)> reload_error_callback)155   absl::StatusOr<std::shared_ptr<CrlProvider>> CreateCrlProvider(
156       absl::string_view directory, std::chrono::seconds refresh_duration,
157       std::function<void(absl::Status)> reload_error_callback) {
158     return CreateCrlProvider(refresh_duration, std::move(reload_error_callback),
159                              MakeDirectoryReader(directory));
160   }
161 
162   std::shared_ptr<FakeDirectoryReader> directory_reader_ =
163       std::make_shared<FakeDirectoryReader>();
164   std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>
165       event_engine_;
166 };
167 
TEST_F(CrlProviderTest,CanParseCrl)168 TEST_F(CrlProviderTest, CanParseCrl) {
169   std::string crl_string = GetFileContents(kCrlPath.data());
170   absl::StatusOr<std::shared_ptr<Crl>> crl = Crl::Parse(crl_string);
171   ASSERT_TRUE(crl.ok()) << crl.status();
172   ASSERT_NE(*crl, nullptr);
173   EXPECT_EQ((*crl)->Issuer(), base_crl_issuer_);
174 }
175 
TEST_F(CrlProviderTest,InvalidFile)176 TEST_F(CrlProviderTest, InvalidFile) {
177   std::string crl_string = "INVALID CRL FILE";
178   absl::StatusOr<std::shared_ptr<Crl>> crl = Crl::Parse(crl_string);
179   EXPECT_EQ(crl.status(),
180             absl::InvalidArgumentError(
181                 "Conversion from PEM string to X509 CRL failed."));
182 }
183 
TEST_F(CrlProviderTest,StaticCrlProviderLookup)184 TEST_F(CrlProviderTest, StaticCrlProviderLookup) {
185   std::vector<std::string> crl_strings = {GetFileContents(kCrlPath.data())};
186   absl::StatusOr<std::shared_ptr<CrlProvider>> provider =
187       experimental::CreateStaticCrlProvider(crl_strings);
188   ASSERT_TRUE(provider.ok()) << provider.status();
189   CertificateInfoImpl cert(base_crl_issuer_);
190   auto crl = (*provider)->GetCrl(cert);
191   ASSERT_NE(crl, nullptr);
192   EXPECT_EQ(crl->Issuer(), base_crl_issuer_);
193 }
194 
TEST_F(CrlProviderTest,StaticCrlProviderLookupIssuerNotFound)195 TEST_F(CrlProviderTest, StaticCrlProviderLookupIssuerNotFound) {
196   std::vector<std::string> crl_strings = {GetFileContents(kCrlPath.data())};
197   absl::StatusOr<std::shared_ptr<CrlProvider>> provider =
198       experimental::CreateStaticCrlProvider(crl_strings);
199   ASSERT_TRUE(provider.ok()) << provider.status();
200   CertificateInfoImpl bad_cert("BAD CERT");
201   auto crl = (*provider)->GetCrl(bad_cert);
202   EXPECT_EQ(crl, nullptr);
203 }
204 
TEST_F(DirectoryReloaderCrlProviderTest,CrlLookupGood)205 TEST_F(DirectoryReloaderCrlProviderTest, CrlLookupGood) {
206   auto provider =
207       CreateCrlProvider(kCrlDirectory, std::chrono::seconds(60), nullptr);
208   ASSERT_TRUE(provider.ok()) << provider.status();
209   CertificateInfoImpl cert(base_crl_issuer_);
210   auto crl = (*provider)->GetCrl(cert);
211   ASSERT_NE(crl, nullptr);
212   EXPECT_EQ(crl->Issuer(), base_crl_issuer_);
213   CertificateInfoImpl intermediate(intermediate_crl_issuer_);
214   auto intermediate_crl = (*provider)->GetCrl(intermediate);
215   ASSERT_NE(intermediate_crl, nullptr);
216   EXPECT_EQ(intermediate_crl->Issuer(), intermediate_crl_issuer_);
217 }
218 
TEST_F(DirectoryReloaderCrlProviderTest,CrlLookupMissingIssuer)219 TEST_F(DirectoryReloaderCrlProviderTest, CrlLookupMissingIssuer) {
220   auto provider =
221       CreateCrlProvider(kCrlDirectory, std::chrono::seconds(60), nullptr);
222   ASSERT_TRUE(provider.ok()) << provider.status();
223   CertificateInfoImpl bad_cert("BAD CERT");
224   auto crl = (*provider)->GetCrl(bad_cert);
225   ASSERT_EQ(crl, nullptr);
226 }
227 
TEST_F(DirectoryReloaderCrlProviderTest,ReloadsAndDeletes)228 TEST_F(DirectoryReloaderCrlProviderTest, ReloadsAndDeletes) {
229   const std::chrono::seconds kRefreshDuration(60);
230   auto provider = CreateCrlProvider(kRefreshDuration, nullptr);
231   ASSERT_TRUE(provider.ok()) << provider.status();
232   CertificateInfoImpl cert(base_crl_issuer_);
233   auto should_be_no_crl = (*provider)->GetCrl(cert);
234   ASSERT_EQ(should_be_no_crl, nullptr);
235   // Give the provider files to find in the directory
236   directory_reader_->SetFilesInDirectory({std::string(kCrlName)});
237   event_engine_->TickForDuration(kRefreshDuration);
238   auto crl = (*provider)->GetCrl(cert);
239   ASSERT_NE(crl, nullptr);
240   EXPECT_EQ(crl->Issuer(), base_crl_issuer_);
241   // Now we won't see any files in our directory
242   directory_reader_->SetFilesInDirectory({});
243   event_engine_->TickForDuration(kRefreshDuration);
244   auto crl_should_be_deleted = (*provider)->GetCrl(cert);
245   ASSERT_EQ(crl_should_be_deleted, nullptr);
246 }
247 
TEST_F(DirectoryReloaderCrlProviderTest,WithCorruption)248 TEST_F(DirectoryReloaderCrlProviderTest, WithCorruption) {
249   directory_reader_->SetFilesInDirectory({std::string(kCrlName)});
250   const std::chrono::seconds kRefreshDuration(60);
251   std::vector<absl::Status> reload_errors;
252   std::function<void(absl::Status)> reload_error_callback =
253       [&](const absl::Status& status) { reload_errors.push_back(status); };
254   auto provider =
255       CreateCrlProvider(kRefreshDuration, std::move(reload_error_callback));
256   ASSERT_TRUE(provider.ok()) << provider.status();
257   CertificateInfoImpl cert(base_crl_issuer_);
258   auto crl = (*provider)->GetCrl(cert);
259   ASSERT_NE(crl, nullptr);
260   EXPECT_EQ(crl->Issuer(), base_crl_issuer_);
261   EXPECT_EQ(reload_errors.size(), 0);
262   // Point the provider at a non-crl file so loading fails
263   // Should result in the CRL Reloader keeping the old CRL data
264   directory_reader_->SetFilesInDirectory({std::string(kRootCert)});
265   event_engine_->TickForDuration(kRefreshDuration);
266   auto crl_post_update = (*provider)->GetCrl(cert);
267   ASSERT_NE(crl_post_update, nullptr);
268   EXPECT_EQ(crl_post_update->Issuer(), base_crl_issuer_);
269   EXPECT_EQ(reload_errors.size(), 1);
270 }
271 
TEST_F(DirectoryReloaderCrlProviderTest,WithBadInitialDirectoryStatus)272 TEST_F(DirectoryReloaderCrlProviderTest, WithBadInitialDirectoryStatus) {
273   absl::Status status = absl::UnknownError("");
274   directory_reader_->SetStatus(status);
275   std::vector<absl::Status> reload_errors;
276   std::function<void(absl::Status)> reload_error_callback =
277       [&](const absl::Status& status) { reload_errors.push_back(status); };
278   const std::chrono::seconds kRefreshDuration(60);
279   auto provider =
280       CreateCrlProvider(kRefreshDuration, reload_error_callback, nullptr);
281   // We expect the provider to be created successfully, but the reload error
282   // callback will have been called
283   ASSERT_TRUE(provider.ok()) << provider.status();
284   EXPECT_EQ(reload_errors.size(), 1);
285 }
286 
TEST(CertificateInfoImplTest,CanFetchValues)287 TEST(CertificateInfoImplTest, CanFetchValues) {
288   experimental::CertificateInfoImpl cert =
289       CertificateInfoImpl("issuer", "akid");
290   EXPECT_EQ(cert.Issuer(), "issuer");
291   EXPECT_EQ(cert.AuthorityKeyIdentifier(), "akid");
292 }
293 
TEST(CertificateInfoImplTest,NoAkid)294 TEST(CertificateInfoImplTest, NoAkid) {
295   experimental::CertificateInfoImpl cert = CertificateInfoImpl("issuer");
296   EXPECT_EQ(cert.Issuer(), "issuer");
297   EXPECT_EQ(cert.AuthorityKeyIdentifier(), "");
298 }
299 
300 }  // namespace testing
301 }  // namespace grpc_core
302 
main(int argc,char ** argv)303 int main(int argc, char** argv) {
304   grpc::testing::TestEnvironment env(&argc, argv);
305   ::testing::InitGoogleTest(&argc, argv);
306   int ret = RUN_ALL_TESTS();
307   return ret;
308 }
309