grpc 1.59.2 → 1.59.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);