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