grpc 1.59.2 → 1.59.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,238 @@
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
+ // IWYU pragma: no_include <openssl/mem.h>
25
+ #include <openssl/bio.h>
26
+ #include <openssl/crypto.h> // IWYU pragma: keep
27
+ #include <openssl/pem.h>
28
+ #include <openssl/x509.h>
29
+
30
+ #include <grpc/support/log.h>
31
+ // IWYU pragma: no_include <ratio>
32
+ #include <algorithm>
33
+ #include <memory>
34
+ #include <type_traits>
35
+ #include <utility>
36
+ #include <vector>
37
+
38
+ #include "absl/container/flat_hash_map.h"
39
+ #include "absl/meta/type_traits.h"
40
+ #include "absl/status/status.h"
41
+ #include "absl/status/statusor.h"
42
+ #include "absl/strings/str_cat.h"
43
+ #include "absl/strings/str_join.h"
44
+ #include "absl/types/span.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
+ std::string IssuerFromCrl(X509_CRL* crl) {
57
+ if (crl == nullptr) {
58
+ return "";
59
+ }
60
+ char* buf = X509_NAME_oneline(X509_CRL_get_issuer(crl), nullptr, 0);
61
+ std::string ret;
62
+ if (buf != nullptr) {
63
+ ret = buf;
64
+ }
65
+ OPENSSL_free(buf);
66
+ return ret;
67
+ }
68
+
69
+ absl::StatusOr<std::shared_ptr<Crl>> ReadCrlFromFile(
70
+ const std::string& crl_path) {
71
+ absl::StatusOr<Slice> crl_slice = LoadFile(crl_path, false);
72
+ if (!crl_slice.ok()) {
73
+ return crl_slice.status();
74
+ }
75
+ absl::StatusOr<std::unique_ptr<Crl>> crl =
76
+ Crl::Parse(crl_slice->as_string_view());
77
+ if (!crl.ok()) {
78
+ return crl.status();
79
+ }
80
+ return crl;
81
+ }
82
+
83
+ } // namespace
84
+
85
+ absl::StatusOr<std::unique_ptr<Crl>> Crl::Parse(absl::string_view crl_string) {
86
+ if (crl_string.size() >= INT_MAX) {
87
+ return absl::InvalidArgumentError("crl_string cannot be of size INT_MAX");
88
+ }
89
+ BIO* crl_bio =
90
+ BIO_new_mem_buf(crl_string.data(), static_cast<int>(crl_string.size()));
91
+ // Errors on BIO
92
+ if (crl_bio == nullptr) {
93
+ return absl::InvalidArgumentError(
94
+ "Conversion from crl string to BIO failed.");
95
+ }
96
+ X509_CRL* crl = PEM_read_bio_X509_CRL(crl_bio, nullptr, nullptr, nullptr);
97
+ BIO_free(crl_bio);
98
+ if (crl == nullptr) {
99
+ return absl::InvalidArgumentError(
100
+ "Conversion from PEM string to X509 CRL failed.");
101
+ }
102
+ return CrlImpl::Create(crl);
103
+ }
104
+
105
+ absl::StatusOr<std::unique_ptr<CrlImpl>> CrlImpl::Create(X509_CRL* crl) {
106
+ std::string issuer = IssuerFromCrl(crl);
107
+ if (issuer.empty()) {
108
+ return absl::InvalidArgumentError("Issuer of crl cannot be empty");
109
+ }
110
+ return std::make_unique<CrlImpl>(crl, issuer);
111
+ }
112
+
113
+ CrlImpl::~CrlImpl() { X509_CRL_free(crl_); }
114
+
115
+ absl::StatusOr<std::shared_ptr<CrlProvider>> CreateStaticCrlProvider(
116
+ absl::Span<const std::string> crls) {
117
+ absl::flat_hash_map<std::string, std::shared_ptr<Crl>> crl_map;
118
+ for (const auto& raw_crl : crls) {
119
+ absl::StatusOr<std::unique_ptr<Crl>> crl = Crl::Parse(raw_crl);
120
+ if (!crl.ok()) {
121
+ return absl::InvalidArgumentError(absl::StrCat(
122
+ "Parsing crl string failed with result ", crl.status().ToString()));
123
+ }
124
+ bool inserted = crl_map.emplace((*crl)->Issuer(), std::move(*crl)).second;
125
+ if (!inserted) {
126
+ gpr_log(GPR_ERROR,
127
+ "StaticCrlProvider received multiple CRLs with the same issuer. "
128
+ "The first one in the span will be used.");
129
+ }
130
+ }
131
+ StaticCrlProvider provider = StaticCrlProvider(std::move(crl_map));
132
+ return std::make_shared<StaticCrlProvider>(std::move(provider));
133
+ }
134
+
135
+ std::shared_ptr<Crl> StaticCrlProvider::GetCrl(
136
+ const CertificateInfo& certificate_info) {
137
+ auto it = crls_.find(certificate_info.Issuer());
138
+ if (it == crls_.end()) {
139
+ return nullptr;
140
+ }
141
+ return it->second;
142
+ }
143
+
144
+ absl::StatusOr<std::shared_ptr<CrlProvider>> CreateDirectoryReloaderCrlProvider(
145
+ absl::string_view directory, std::chrono::seconds refresh_duration,
146
+ std::function<void(absl::Status)> reload_error_callback) {
147
+ if (refresh_duration < std::chrono::seconds(60)) {
148
+ return absl::InvalidArgumentError("Refresh duration minimum is 60 seconds");
149
+ }
150
+ auto provider = std::make_shared<DirectoryReloaderCrlProvider>(
151
+ refresh_duration, reload_error_callback,
152
+ grpc_event_engine::experimental::GetDefaultEventEngine(),
153
+ MakeDirectoryReader(directory));
154
+ // This could be slow to do at startup, but we want to
155
+ // make sure it's done before the provider is used.
156
+ provider->UpdateAndStartTimer();
157
+ return provider;
158
+ }
159
+
160
+ DirectoryReloaderCrlProvider::~DirectoryReloaderCrlProvider() {
161
+ if (refresh_handle_.has_value()) {
162
+ event_engine_->Cancel(refresh_handle_.value());
163
+ }
164
+ }
165
+
166
+ void DirectoryReloaderCrlProvider::UpdateAndStartTimer() {
167
+ absl::Status status = Update();
168
+ if (!status.ok() && reload_error_callback_ != nullptr) {
169
+ reload_error_callback_(status);
170
+ }
171
+ std::weak_ptr<DirectoryReloaderCrlProvider> self = shared_from_this();
172
+ refresh_handle_ =
173
+ event_engine_->RunAfter(refresh_duration_, [self = std::move(self)]() {
174
+ ApplicationCallbackExecCtx callback_exec_ctx;
175
+ ExecCtx exec_ctx;
176
+ if (std::shared_ptr<DirectoryReloaderCrlProvider> valid_ptr =
177
+ self.lock()) {
178
+ valid_ptr->UpdateAndStartTimer();
179
+ }
180
+ });
181
+ }
182
+
183
+ absl::Status DirectoryReloaderCrlProvider::Update() {
184
+ absl::flat_hash_map<std::string, std::shared_ptr<Crl>> new_crls;
185
+ std::vector<std::string> files_with_errors;
186
+ absl::Status status = crl_directory_->ForEach([&](absl::string_view file) {
187
+ std::string file_path = absl::StrCat(crl_directory_->Name(), "/", file);
188
+ // Build a map of new_crls to update to. If all files successful, do a
189
+ // full swap of the map. Otherwise update in place.
190
+ absl::StatusOr<std::shared_ptr<Crl>> crl = ReadCrlFromFile(file_path);
191
+ if (!crl.ok()) {
192
+ files_with_errors.push_back(
193
+ absl::StrCat(file_path, ": ", crl.status().ToString()));
194
+ return;
195
+ }
196
+ // Now we have a good CRL to update in our map.
197
+ // It's not safe to say crl->Issuer() on the LHS and std::move(crl) on the
198
+ // RHS, because C++ does not guarantee which of those will be executed
199
+ // first.
200
+ std::string issuer((*crl)->Issuer());
201
+ new_crls[std::move(issuer)] = std::move(*crl);
202
+ });
203
+ if (!status.ok()) {
204
+ return status;
205
+ }
206
+ MutexLock lock(&mu_);
207
+ if (!files_with_errors.empty()) {
208
+ // Need to make sure CRLs we read successfully into new_crls are still
209
+ // in-place updated in crls_.
210
+ for (auto& kv : new_crls) {
211
+ std::shared_ptr<Crl>& crl = kv.second;
212
+ // It's not safe to say crl->Issuer() on the LHS and std::move(crl) on the
213
+ // RHS, because C++ does not guarantee which of those will be executed
214
+ // first.
215
+ std::string issuer(crl->Issuer());
216
+ crls_[std::move(issuer)] = std::move(crl);
217
+ }
218
+ return absl::UnknownError(absl::StrCat(
219
+ "Errors reading the following files in the CRL directory: [",
220
+ absl::StrJoin(files_with_errors, "; "), "]"));
221
+ } else {
222
+ crls_ = std::move(new_crls);
223
+ }
224
+ return absl::OkStatus();
225
+ }
226
+
227
+ std::shared_ptr<Crl> DirectoryReloaderCrlProvider::GetCrl(
228
+ const CertificateInfo& certificate_info) {
229
+ MutexLock lock(&mu_);
230
+ auto it = crls_.find(certificate_info.Issuer());
231
+ if (it == crls_.end()) {
232
+ return nullptr;
233
+ }
234
+ return it->second;
235
+ }
236
+
237
+ } // namespace experimental
238
+ } // namespace grpc_core
@@ -0,0 +1,132 @@
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
+ #ifndef GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CRL_PROVIDER_H
20
+ #define GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CRL_PROVIDER_H
21
+
22
+ #include <grpc/support/port_platform.h>
23
+
24
+ #include <chrono>
25
+ #include <functional>
26
+ #include <memory>
27
+ #include <string>
28
+ #include <utility>
29
+
30
+ #include <openssl/crypto.h>
31
+
32
+ #include "absl/base/thread_annotations.h"
33
+ #include "absl/container/flat_hash_map.h"
34
+ #include "absl/status/status.h"
35
+ #include "absl/status/statusor.h"
36
+ #include "absl/strings/string_view.h"
37
+ #include "absl/types/optional.h"
38
+
39
+ #include <grpc/event_engine/event_engine.h>
40
+ #include <grpc/grpc_crl_provider.h>
41
+
42
+ #include "src/core/lib/gprpp/directory_reader.h"
43
+ #include "src/core/lib/gprpp/sync.h"
44
+ #include "src/core/lib/gprpp/time.h"
45
+
46
+ namespace grpc_core {
47
+ namespace experimental {
48
+
49
+ class StaticCrlProvider : public CrlProvider {
50
+ public:
51
+ // Each element of the input vector is expected to be the raw contents of a
52
+ // CRL file.
53
+ explicit StaticCrlProvider(
54
+ absl::flat_hash_map<std::string, std::shared_ptr<Crl>> crls)
55
+ : crls_(std::move(crls)) {}
56
+ std::shared_ptr<Crl> GetCrl(const CertificateInfo& certificate_info) override;
57
+
58
+ private:
59
+ const absl::flat_hash_map<std::string, std::shared_ptr<Crl>> crls_;
60
+ };
61
+
62
+ class CrlImpl : public Crl {
63
+ public:
64
+ static absl::StatusOr<std::unique_ptr<CrlImpl>> Create(X509_CRL* crl);
65
+ // Takes ownership of the X509_CRL pointer.
66
+ CrlImpl(X509_CRL* crl, absl::string_view issuer)
67
+ : crl_(crl), issuer_(issuer) {}
68
+ ~CrlImpl() override;
69
+ // Returns a string view representation of the issuer pulled from the CRL.
70
+ absl::string_view Issuer() override { return issuer_; }
71
+ // The caller should not take ownership of the returned pointer.
72
+ X509_CRL* crl() const { return crl_; }
73
+
74
+ private:
75
+ X509_CRL* crl_;
76
+ const std::string issuer_;
77
+ };
78
+
79
+ class CertificateInfoImpl : public CertificateInfo {
80
+ public:
81
+ explicit CertificateInfoImpl(absl::string_view issuer) : issuer_(issuer) {}
82
+ // Returns a string representation of the issuer pulled from the
83
+ // certificate.
84
+ absl::string_view Issuer() const override { return issuer_; }
85
+
86
+ private:
87
+ const std::string issuer_;
88
+ };
89
+
90
+ // Defining this here lets us hide implementation details (and includes) from
91
+ // the header in include
92
+ class DirectoryReloaderCrlProvider
93
+ : public CrlProvider,
94
+ public std::enable_shared_from_this<DirectoryReloaderCrlProvider> {
95
+ public:
96
+ DirectoryReloaderCrlProvider(
97
+ std::chrono::seconds duration, std::function<void(absl::Status)> callback,
98
+ std::shared_ptr<grpc_event_engine::experimental::EventEngine>
99
+ event_engine,
100
+ std::shared_ptr<DirectoryReader> directory_impl)
101
+ : refresh_duration_(Duration::FromSecondsAsDouble(duration.count())),
102
+ reload_error_callback_(std::move(callback)),
103
+ event_engine_(std::move(event_engine)),
104
+ crl_directory_(std::move(directory_impl)) {}
105
+
106
+ ~DirectoryReloaderCrlProvider() override;
107
+ std::shared_ptr<Crl> GetCrl(const CertificateInfo& certificate_info) override;
108
+ // Reads the configured directory and updates the internal crls_ map, called
109
+ // asynchronously by event engine then schedules the timer for the next
110
+ // update.
111
+ void UpdateAndStartTimer();
112
+
113
+ private:
114
+ // Reads the configured directory and updates the internal crls_ map, called
115
+ // asynchronously by event engine.
116
+ absl::Status Update();
117
+ Duration refresh_duration_;
118
+ std::function<void(::absl::Status)> reload_error_callback_;
119
+ std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine_;
120
+ std::shared_ptr<DirectoryReader> crl_directory_;
121
+ // guards the crls_ map
122
+ Mutex mu_;
123
+ absl::flat_hash_map<::std::string, ::std::shared_ptr<Crl>> crls_
124
+ ABSL_GUARDED_BY(mu_);
125
+ absl::optional<grpc_event_engine::experimental::EventEngine::TaskHandle>
126
+ refresh_handle_;
127
+ };
128
+
129
+ } // namespace experimental
130
+ } // namespace grpc_core
131
+
132
+ #endif // GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CRL_PROVIDER_H
@@ -20,6 +20,7 @@
20
20
 
21
21
  #include "src/core/lib/security/credentials/tls/tls_credentials.h"
22
22
 
23
+ #include <memory>
23
24
  #include <string>
24
25
  #include <utility>
25
26
 
@@ -46,6 +47,28 @@ bool CredentialOptionSanityCheck(grpc_tls_credentials_options* options,
46
47
  gpr_log(GPR_ERROR, "TLS credentials options is nullptr.");
47
48
  return false;
48
49
  }
50
+ // In this case, there will be non-retriable handshake errors.
51
+ if (options->min_tls_version() == grpc_tls_version::TLS1_3 &&
52
+ options->max_tls_version() == grpc_tls_version::TLS1_2) {
53
+ gpr_log(GPR_ERROR, "TLS min version must not be higher than max version.");
54
+ return false;
55
+ }
56
+ if (options->max_tls_version() > grpc_tls_version::TLS1_3) {
57
+ gpr_log(GPR_ERROR, "TLS max version must not be higher than v1.3.");
58
+ return false;
59
+ }
60
+ if (options->min_tls_version() < grpc_tls_version::TLS1_2) {
61
+ gpr_log(GPR_ERROR, "TLS min version must not be lower than v1.2.");
62
+ return false;
63
+ }
64
+ if (!options->crl_directory().empty() && options->crl_provider() != nullptr) {
65
+ gpr_log(GPR_ERROR,
66
+ "Setting crl_directory and crl_provider not supported. Using the "
67
+ "crl_provider.");
68
+ // TODO(gtcooke94) - Maybe return false here. Right now object lifetime of
69
+ // this options struct is leaky if false is returned and represents a more
70
+ // complex fix to handle in another PR.
71
+ }
49
72
  // In the following conditions, there won't be any issues, but it might
50
73
  // indicate callers are doing something wrong with the API.
51
74
  if (is_client && options->cert_request_type() !=
@@ -23,6 +23,8 @@
23
23
  #include <stdint.h>
24
24
  #include <string.h>
25
25
 
26
+ #include <memory>
27
+ #include <utility>
26
28
  #include <vector>
27
29
 
28
30
  #include "absl/strings/match.h"
@@ -30,6 +32,7 @@
30
32
  #include "absl/strings/str_split.h"
31
33
 
32
34
  #include <grpc/grpc.h>
35
+ #include <grpc/grpc_crl_provider.h>
33
36
  #include <grpc/impl/channel_arg_names.h>
34
37
  #include <grpc/support/alloc.h>
35
38
  #include <grpc/support/log.h>
@@ -410,6 +413,7 @@ grpc_security_status grpc_ssl_tsi_client_handshaker_factory_init(
410
413
  tsi_tls_version max_tls_version, tsi_ssl_session_cache* ssl_session_cache,
411
414
  tsi::TlsSessionKeyLoggerCache::TlsSessionKeyLogger* tls_session_key_logger,
412
415
  const char* crl_directory,
416
+ std::shared_ptr<grpc_core::experimental::CrlProvider> crl_provider,
413
417
  tsi_ssl_client_handshaker_factory** handshaker_factory) {
414
418
  const char* root_certs;
415
419
  const tsi_ssl_root_certs_store* root_store;
@@ -448,6 +452,7 @@ grpc_security_status grpc_ssl_tsi_client_handshaker_factory_init(
448
452
  options.min_tls_version = min_tls_version;
449
453
  options.max_tls_version = max_tls_version;
450
454
  options.crl_directory = crl_directory;
455
+ options.crl_provider = std::move(crl_provider);
451
456
  const tsi_result result =
452
457
  tsi_create_ssl_client_handshaker_factory_with_options(&options,
453
458
  handshaker_factory);
@@ -467,6 +472,7 @@ grpc_security_status grpc_ssl_tsi_server_handshaker_factory_init(
467
472
  tsi_tls_version min_tls_version, tsi_tls_version max_tls_version,
468
473
  tsi::TlsSessionKeyLoggerCache::TlsSessionKeyLogger* tls_session_key_logger,
469
474
  const char* crl_directory, bool send_client_ca_list,
475
+ std::shared_ptr<grpc_core::experimental::CrlProvider> crl_provider,
470
476
  tsi_ssl_server_handshaker_factory** handshaker_factory) {
471
477
  size_t num_alpn_protocols = 0;
472
478
  const char** alpn_protocol_strings =
@@ -484,6 +490,7 @@ grpc_security_status grpc_ssl_tsi_server_handshaker_factory_init(
484
490
  options.max_tls_version = max_tls_version;
485
491
  options.key_logger = tls_session_key_logger;
486
492
  options.crl_directory = crl_directory;
493
+ options.crl_provider = std::move(crl_provider);
487
494
  options.send_client_ca_list = send_client_ca_list;
488
495
  const tsi_result result =
489
496
  tsi_create_ssl_server_handshaker_factory_with_options(&options,
@@ -23,6 +23,7 @@
23
23
 
24
24
  #include <stddef.h>
25
25
 
26
+ #include <memory>
26
27
  #include <string>
27
28
  #include <utility>
28
29
  #include <vector>
@@ -30,6 +31,7 @@
30
31
  #include "absl/status/status.h"
31
32
  #include "absl/strings/string_view.h"
32
33
 
34
+ #include <grpc/grpc_crl_provider.h>
33
35
  #include <grpc/grpc_security.h>
34
36
  #include <grpc/grpc_security_constants.h>
35
37
  #include <grpc/slice.h>
@@ -85,6 +87,7 @@ grpc_security_status grpc_ssl_tsi_client_handshaker_factory_init(
85
87
  tsi_tls_version max_tls_version, tsi_ssl_session_cache* ssl_session_cache,
86
88
  tsi::TlsSessionKeyLoggerCache::TlsSessionKeyLogger* tls_session_key_logger,
87
89
  const char* crl_directory,
90
+ std::shared_ptr<grpc_core::experimental::CrlProvider> crl_provider,
88
91
  tsi_ssl_client_handshaker_factory** handshaker_factory);
89
92
 
90
93
  grpc_security_status grpc_ssl_tsi_server_handshaker_factory_init(
@@ -94,6 +97,7 @@ grpc_security_status grpc_ssl_tsi_server_handshaker_factory_init(
94
97
  tsi_tls_version min_tls_version, tsi_tls_version max_tls_version,
95
98
  tsi::TlsSessionKeyLoggerCache::TlsSessionKeyLogger* tls_session_key_logger,
96
99
  const char* crl_directory, bool send_client_ca_list,
100
+ std::shared_ptr<grpc_core::experimental::CrlProvider> crl_provider,
97
101
  tsi_ssl_server_handshaker_factory** handshaker_factory);
98
102
 
99
103
  // Free the memory occupied by key cert pairs.
@@ -551,7 +551,7 @@ TlsChannelSecurityConnector::UpdateHandshakerFactoryLocked() {
551
551
  grpc_get_tsi_tls_version(options_->min_tls_version()),
552
552
  grpc_get_tsi_tls_version(options_->max_tls_version()), ssl_session_cache_,
553
553
  tls_session_key_logger_.get(), options_->crl_directory().c_str(),
554
- &client_handshaker_factory_);
554
+ options_->crl_provider(), &client_handshaker_factory_);
555
555
  // Free memory.
556
556
  if (pem_key_cert_pair != nullptr) {
557
557
  grpc_tsi_ssl_pem_key_cert_pairs_destroy(pem_key_cert_pair, 1);
@@ -818,7 +818,8 @@ TlsServerSecurityConnector::UpdateHandshakerFactoryLocked() {
818
818
  grpc_get_tsi_tls_version(options_->min_tls_version()),
819
819
  grpc_get_tsi_tls_version(options_->max_tls_version()),
820
820
  tls_session_key_logger_.get(), options_->crl_directory().c_str(),
821
- options_->send_client_ca_list(), &server_handshaker_factory_);
821
+ options_->send_client_ca_list(), options_->crl_provider(),
822
+ &server_handshaker_factory_);
822
823
  // Free memory.
823
824
  grpc_tsi_ssl_pem_key_cert_pairs_destroy(pem_key_cert_pairs,
824
825
  num_key_cert_pairs);