xref: /aosp_15_r20/external/grpc-grpc/src/core/lib/security/credentials/tls/grpc_tls_crl_provider.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 <grpc/support/port_platform.h>
20 
21 #include "src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h"
22 
23 #include <limits.h>
24 
25 // IWYU pragma: no_include <ratio>
26 #include <memory>
27 #include <type_traits>
28 #include <utility>
29 #include <vector>
30 
31 // IWYU pragma: no_include <openssl/mem.h>
32 #include <openssl/bio.h>
33 #include <openssl/crypto.h>  // IWYU pragma: keep
34 #include <openssl/pem.h>
35 #include <openssl/x509.h>
36 
37 #include "absl/container/flat_hash_map.h"
38 #include "absl/status/status.h"
39 #include "absl/status/statusor.h"
40 #include "absl/strings/str_cat.h"
41 #include "absl/strings/str_join.h"
42 #include "absl/types/span.h"
43 
44 #include <grpc/support/log.h>
45 
46 #include "src/core/lib/event_engine/default_event_engine.h"
47 #include "src/core/lib/gprpp/directory_reader.h"
48 #include "src/core/lib/gprpp/load_file.h"
49 #include "src/core/lib/iomgr/exec_ctx.h"
50 #include "src/core/lib/slice/slice.h"
51 
52 namespace grpc_core {
53 namespace experimental {
54 
55 namespace {
56 // TODO(gtcooke94) Move ssl_transport_security_utils to it's own BUILD target
57 // and add this to it.
IssuerFromCrl(X509_CRL * crl)58 absl::StatusOr<std::string> IssuerFromCrl(X509_CRL* crl) {
59   if (crl == nullptr) {
60     return absl::InvalidArgumentError("crl cannot be null");
61   }
62   X509_NAME* issuer = X509_CRL_get_issuer(crl);
63   if (issuer == nullptr) {
64     return absl::InvalidArgumentError("crl cannot have null issuer");
65   }
66   unsigned char* buf = nullptr;
67   int len = i2d_X509_NAME(issuer, &buf);
68   if (len < 0 || buf == nullptr) {
69     return absl::InvalidArgumentError("crl cannot have null issuer");
70   }
71   std::string ret(reinterpret_cast<char const*>(buf), len);
72   OPENSSL_free(buf);
73   return ret;
74 }
75 
ReadCrlFromFile(const std::string & crl_path)76 absl::StatusOr<std::shared_ptr<Crl>> ReadCrlFromFile(
77     const std::string& crl_path) {
78   absl::StatusOr<Slice> crl_slice = LoadFile(crl_path, false);
79   if (!crl_slice.ok()) {
80     return crl_slice.status();
81   }
82   absl::StatusOr<std::unique_ptr<Crl>> crl =
83       Crl::Parse(crl_slice->as_string_view());
84   if (!crl.ok()) {
85     return crl.status();
86   }
87   return crl;
88 }
89 
90 }  // namespace
91 
Parse(absl::string_view crl_string)92 absl::StatusOr<std::unique_ptr<Crl>> Crl::Parse(absl::string_view crl_string) {
93   if (crl_string.size() >= INT_MAX) {
94     return absl::InvalidArgumentError("crl_string cannot be of size INT_MAX");
95   }
96   BIO* crl_bio =
97       BIO_new_mem_buf(crl_string.data(), static_cast<int>(crl_string.size()));
98   // Errors on BIO
99   if (crl_bio == nullptr) {
100     return absl::InvalidArgumentError(
101         "Conversion from crl string to BIO failed.");
102   }
103   X509_CRL* crl = PEM_read_bio_X509_CRL(crl_bio, nullptr, nullptr, nullptr);
104   BIO_free(crl_bio);
105   if (crl == nullptr) {
106     return absl::InvalidArgumentError(
107         "Conversion from PEM string to X509 CRL failed.");
108   }
109   return CrlImpl::Create(crl);
110 }
111 
Create(X509_CRL * crl)112 absl::StatusOr<std::unique_ptr<CrlImpl>> CrlImpl::Create(X509_CRL* crl) {
113   absl::StatusOr<std::string> issuer = IssuerFromCrl(crl);
114   if (!issuer.ok()) {
115     return issuer.status();
116   }
117   return std::make_unique<CrlImpl>(crl, *issuer);
118 }
119 
~CrlImpl()120 CrlImpl::~CrlImpl() { X509_CRL_free(crl_); }
121 
CreateStaticCrlProvider(absl::Span<const std::string> crls)122 absl::StatusOr<std::shared_ptr<CrlProvider>> CreateStaticCrlProvider(
123     absl::Span<const std::string> crls) {
124   absl::flat_hash_map<std::string, std::shared_ptr<Crl>> crl_map;
125   for (const auto& raw_crl : crls) {
126     absl::StatusOr<std::unique_ptr<Crl>> crl = Crl::Parse(raw_crl);
127     if (!crl.ok()) {
128       return absl::InvalidArgumentError(absl::StrCat(
129           "Parsing crl string failed with result ", crl.status().ToString()));
130     }
131     bool inserted = crl_map.emplace((*crl)->Issuer(), std::move(*crl)).second;
132     if (!inserted) {
133       gpr_log(GPR_ERROR,
134               "StaticCrlProvider received multiple CRLs with the same issuer. "
135               "The first one in the span will be used.");
136     }
137   }
138   StaticCrlProvider provider = StaticCrlProvider(std::move(crl_map));
139   return std::make_shared<StaticCrlProvider>(std::move(provider));
140 }
141 
GetCrl(const CertificateInfo & certificate_info)142 std::shared_ptr<Crl> StaticCrlProvider::GetCrl(
143     const CertificateInfo& certificate_info) {
144   auto it = crls_.find(certificate_info.Issuer());
145   if (it == crls_.end()) {
146     return nullptr;
147   }
148   return it->second;
149 }
150 
CreateDirectoryReloaderCrlProvider(absl::string_view directory,std::chrono::seconds refresh_duration,std::function<void (absl::Status)> reload_error_callback)151 absl::StatusOr<std::shared_ptr<CrlProvider>> CreateDirectoryReloaderCrlProvider(
152     absl::string_view directory, std::chrono::seconds refresh_duration,
153     std::function<void(absl::Status)> reload_error_callback) {
154   if (refresh_duration < std::chrono::seconds(60)) {
155     return absl::InvalidArgumentError("Refresh duration minimum is 60 seconds");
156   }
157   auto provider = std::make_shared<DirectoryReloaderCrlProvider>(
158       refresh_duration, reload_error_callback, /*event_engine=*/nullptr,
159       MakeDirectoryReader(directory));
160   // This could be slow to do at startup, but we want to
161   // make sure it's done before the provider is used.
162   provider->UpdateAndStartTimer();
163   return provider;
164 }
165 
DirectoryReloaderCrlProvider(std::chrono::seconds duration,std::function<void (absl::Status)> callback,std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,std::shared_ptr<DirectoryReader> directory_impl)166 DirectoryReloaderCrlProvider::DirectoryReloaderCrlProvider(
167     std::chrono::seconds duration, std::function<void(absl::Status)> callback,
168     std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,
169     std::shared_ptr<DirectoryReader> directory_impl)
170     : refresh_duration_(Duration::FromSecondsAsDouble(duration.count())),
171       reload_error_callback_(std::move(callback)),
172       crl_directory_(std::move(directory_impl)) {
173   // Must be called before `GetDefaultEventEngine`
174   grpc_init();
175   if (event_engine == nullptr) {
176     event_engine_ = grpc_event_engine::experimental::GetDefaultEventEngine();
177   } else {
178     event_engine_ = std::move(event_engine);
179   }
180 }
181 
~DirectoryReloaderCrlProvider()182 DirectoryReloaderCrlProvider::~DirectoryReloaderCrlProvider() {
183   if (refresh_handle_.has_value()) {
184     event_engine_->Cancel(refresh_handle_.value());
185   }
186   // Call here because we call grpc_init in the constructor
187   grpc_shutdown();
188 }
189 
UpdateAndStartTimer()190 void DirectoryReloaderCrlProvider::UpdateAndStartTimer() {
191   absl::Status status = Update();
192   if (!status.ok() && reload_error_callback_ != nullptr) {
193     reload_error_callback_(status);
194   }
195   std::weak_ptr<DirectoryReloaderCrlProvider> self = shared_from_this();
196   refresh_handle_ =
197       event_engine_->RunAfter(refresh_duration_, [self = std::move(self)]() {
198         ApplicationCallbackExecCtx callback_exec_ctx;
199         ExecCtx exec_ctx;
200         if (std::shared_ptr<DirectoryReloaderCrlProvider> valid_ptr =
201                 self.lock()) {
202           valid_ptr->UpdateAndStartTimer();
203         }
204       });
205 }
206 
Update()207 absl::Status DirectoryReloaderCrlProvider::Update() {
208   absl::flat_hash_map<std::string, std::shared_ptr<Crl>> new_crls;
209   std::vector<std::string> files_with_errors;
210   absl::Status status = crl_directory_->ForEach([&](absl::string_view file) {
211     std::string file_path = absl::StrCat(crl_directory_->Name(), "/", file);
212     // Build a map of new_crls to update to. If all files successful, do a
213     // full swap of the map. Otherwise update in place.
214     absl::StatusOr<std::shared_ptr<Crl>> crl = ReadCrlFromFile(file_path);
215     if (!crl.ok()) {
216       files_with_errors.push_back(
217           absl::StrCat(file_path, ": ", crl.status().ToString()));
218       return;
219     }
220     // Now we have a good CRL to update in our map.
221     // It's not safe to say crl->Issuer() on the LHS and std::move(crl) on the
222     // RHS, because C++ does not guarantee which of those will be executed
223     // first.
224     std::string issuer((*crl)->Issuer());
225     new_crls[std::move(issuer)] = std::move(*crl);
226   });
227   if (!status.ok()) {
228     return status;
229   }
230   MutexLock lock(&mu_);
231   if (!files_with_errors.empty()) {
232     // Need to make sure CRLs we read successfully into new_crls are still
233     // in-place updated in crls_.
234     for (auto& kv : new_crls) {
235       std::shared_ptr<Crl>& crl = kv.second;
236       // It's not safe to say crl->Issuer() on the LHS and std::move(crl) on
237       // the RHS, because C++ does not guarantee which of those will be
238       // executed first.
239       std::string issuer(crl->Issuer());
240       crls_[std::move(issuer)] = std::move(crl);
241     }
242     return absl::UnknownError(absl::StrCat(
243         "Errors reading the following files in the CRL directory: [",
244         absl::StrJoin(files_with_errors, "; "), "]"));
245   } else {
246     crls_ = std::move(new_crls);
247   }
248   return absl::OkStatus();
249 }
250 
GetCrl(const CertificateInfo & certificate_info)251 std::shared_ptr<Crl> DirectoryReloaderCrlProvider::GetCrl(
252     const CertificateInfo& certificate_info) {
253   MutexLock lock(&mu_);
254   auto it = crls_.find(certificate_info.Issuer());
255   if (it == crls_.end()) {
256     return nullptr;
257   }
258   return it->second;
259 }
260 
261 }  // namespace experimental
262 }  // namespace grpc_core
263