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