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