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