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