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