OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license | |
5 * that can be found in the LICENSE file in the root of the source | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 #include "webrtc/base/opensslidentity.h" | |
12 | |
13 #include <memory> | |
14 | |
15 // Must be included first before openssl headers. | |
16 #include "webrtc/base/win32.h" // NOLINT | |
17 | |
18 #include <openssl/bio.h> | |
19 #include <openssl/err.h> | |
20 #include <openssl/pem.h> | |
21 #include <openssl/bn.h> | |
22 #include <openssl/rsa.h> | |
23 #include <openssl/crypto.h> | |
24 | |
25 #include "webrtc/base/checks.h" | |
26 #include "webrtc/base/helpers.h" | |
27 #include "webrtc/base/logging.h" | |
28 #include "webrtc/base/openssl.h" | |
29 #include "webrtc/base/openssldigest.h" | |
30 | |
31 namespace rtc { | |
32 | |
33 // We could have exposed a myriad of parameters for the crypto stuff, | |
34 // but keeping it simple seems best. | |
35 | |
36 // Random bits for certificate serial number | |
37 static const int SERIAL_RAND_BITS = 64; | |
38 | |
39 // Generate a key pair. Caller is responsible for freeing the returned object. | |
40 static EVP_PKEY* MakeKey(const KeyParams& key_params) { | |
41 LOG(LS_INFO) << "Making key pair"; | |
42 EVP_PKEY* pkey = EVP_PKEY_new(); | |
43 if (key_params.type() == KT_RSA) { | |
44 int key_length = key_params.rsa_params().mod_size; | |
45 BIGNUM* exponent = BN_new(); | |
46 RSA* rsa = RSA_new(); | |
47 if (!pkey || !exponent || !rsa || | |
48 !BN_set_word(exponent, key_params.rsa_params().pub_exp) || | |
49 !RSA_generate_key_ex(rsa, key_length, exponent, nullptr) || | |
50 !EVP_PKEY_assign_RSA(pkey, rsa)) { | |
51 EVP_PKEY_free(pkey); | |
52 BN_free(exponent); | |
53 RSA_free(rsa); | |
54 LOG(LS_ERROR) << "Failed to make RSA key pair"; | |
55 return nullptr; | |
56 } | |
57 // ownership of rsa struct was assigned, don't free it. | |
58 BN_free(exponent); | |
59 } else if (key_params.type() == KT_ECDSA) { | |
60 if (key_params.ec_curve() == EC_NIST_P256) { | |
61 EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); | |
62 | |
63 // Ensure curve name is included when EC key is serialized. | |
64 // Without this call, OpenSSL versions before 1.1.0 will create | |
65 // certificates that don't work for TLS. | |
66 // This is a no-op for BoringSSL and OpenSSL 1.1.0+ | |
67 EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE); | |
68 | |
69 if (!pkey || !ec_key || !EC_KEY_generate_key(ec_key) || | |
70 !EVP_PKEY_assign_EC_KEY(pkey, ec_key)) { | |
71 EVP_PKEY_free(pkey); | |
72 EC_KEY_free(ec_key); | |
73 LOG(LS_ERROR) << "Failed to make EC key pair"; | |
74 return nullptr; | |
75 } | |
76 // ownership of ec_key struct was assigned, don't free it. | |
77 } else { | |
78 // Add generation of any other curves here. | |
79 EVP_PKEY_free(pkey); | |
80 LOG(LS_ERROR) << "ECDSA key requested for unknown curve"; | |
81 return nullptr; | |
82 } | |
83 } else { | |
84 EVP_PKEY_free(pkey); | |
85 LOG(LS_ERROR) << "Key type requested not understood"; | |
86 return nullptr; | |
87 } | |
88 | |
89 LOG(LS_INFO) << "Returning key pair"; | |
90 return pkey; | |
91 } | |
92 | |
93 // Generate a self-signed certificate, with the public key from the | |
94 // given key pair. Caller is responsible for freeing the returned object. | |
95 static X509* MakeCertificate(EVP_PKEY* pkey, const SSLIdentityParams& params) { | |
96 LOG(LS_INFO) << "Making certificate for " << params.common_name; | |
97 X509* x509 = nullptr; | |
98 BIGNUM* serial_number = nullptr; | |
99 X509_NAME* name = nullptr; | |
100 time_t epoch_off = 0; // Time offset since epoch. | |
101 | |
102 if ((x509 = X509_new()) == nullptr) | |
103 goto error; | |
104 | |
105 if (!X509_set_pubkey(x509, pkey)) | |
106 goto error; | |
107 | |
108 // serial number | |
109 // temporary reference to serial number inside x509 struct | |
110 ASN1_INTEGER* asn1_serial_number; | |
111 if ((serial_number = BN_new()) == nullptr || | |
112 !BN_pseudo_rand(serial_number, SERIAL_RAND_BITS, 0, 0) || | |
113 (asn1_serial_number = X509_get_serialNumber(x509)) == nullptr || | |
114 !BN_to_ASN1_INTEGER(serial_number, asn1_serial_number)) | |
115 goto error; | |
116 | |
117 if (!X509_set_version(x509, 2L)) // version 3 | |
118 goto error; | |
119 | |
120 // There are a lot of possible components for the name entries. In | |
121 // our P2P SSL mode however, the certificates are pre-exchanged | |
122 // (through the secure XMPP channel), and so the certificate | |
123 // identification is arbitrary. It can't be empty, so we set some | |
124 // arbitrary common_name. Note that this certificate goes out in | |
125 // clear during SSL negotiation, so there may be a privacy issue in | |
126 // putting anything recognizable here. | |
127 if ((name = X509_NAME_new()) == nullptr || | |
128 !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8, | |
129 (unsigned char*)params.common_name.c_str(), | |
130 -1, -1, 0) || | |
131 !X509_set_subject_name(x509, name) || !X509_set_issuer_name(x509, name)) | |
132 goto error; | |
133 | |
134 if (!X509_time_adj(X509_get_notBefore(x509), params.not_before, &epoch_off) || | |
135 !X509_time_adj(X509_get_notAfter(x509), params.not_after, &epoch_off)) | |
136 goto error; | |
137 | |
138 if (!X509_sign(x509, pkey, EVP_sha256())) | |
139 goto error; | |
140 | |
141 BN_free(serial_number); | |
142 X509_NAME_free(name); | |
143 LOG(LS_INFO) << "Returning certificate"; | |
144 return x509; | |
145 | |
146 error: | |
147 BN_free(serial_number); | |
148 X509_NAME_free(name); | |
149 X509_free(x509); | |
150 return nullptr; | |
151 } | |
152 | |
153 // This dumps the SSL error stack to the log. | |
154 static void LogSSLErrors(const std::string& prefix) { | |
155 char error_buf[200]; | |
156 unsigned long err; | |
157 | |
158 while ((err = ERR_get_error()) != 0) { | |
159 ERR_error_string_n(err, error_buf, sizeof(error_buf)); | |
160 LOG(LS_ERROR) << prefix << ": " << error_buf << "\n"; | |
161 } | |
162 } | |
163 | |
164 OpenSSLKeyPair* OpenSSLKeyPair::Generate(const KeyParams& key_params) { | |
165 EVP_PKEY* pkey = MakeKey(key_params); | |
166 if (!pkey) { | |
167 LogSSLErrors("Generating key pair"); | |
168 return nullptr; | |
169 } | |
170 return new OpenSSLKeyPair(pkey); | |
171 } | |
172 | |
173 OpenSSLKeyPair* OpenSSLKeyPair::FromPrivateKeyPEMString( | |
174 const std::string& pem_string) { | |
175 BIO* bio = BIO_new_mem_buf(const_cast<char*>(pem_string.c_str()), -1); | |
176 if (!bio) { | |
177 LOG(LS_ERROR) << "Failed to create a new BIO buffer."; | |
178 return nullptr; | |
179 } | |
180 BIO_set_mem_eof_return(bio, 0); | |
181 EVP_PKEY* pkey = | |
182 PEM_read_bio_PrivateKey(bio, nullptr, nullptr, const_cast<char*>("\0")); | |
183 BIO_free(bio); // Frees the BIO, but not the pointed-to string. | |
184 if (!pkey) { | |
185 LOG(LS_ERROR) << "Failed to create the private key from PEM string."; | |
186 return nullptr; | |
187 } | |
188 if (EVP_PKEY_missing_parameters(pkey) != 0) { | |
189 LOG(LS_ERROR) << "The resulting key pair is missing public key parameters."; | |
190 EVP_PKEY_free(pkey); | |
191 return nullptr; | |
192 } | |
193 return new OpenSSLKeyPair(pkey); | |
194 } | |
195 | |
196 OpenSSLKeyPair::~OpenSSLKeyPair() { | |
197 EVP_PKEY_free(pkey_); | |
198 } | |
199 | |
200 OpenSSLKeyPair* OpenSSLKeyPair::GetReference() { | |
201 AddReference(); | |
202 return new OpenSSLKeyPair(pkey_); | |
203 } | |
204 | |
205 void OpenSSLKeyPair::AddReference() { | |
206 #if defined(OPENSSL_IS_BORINGSSL) | |
207 EVP_PKEY_up_ref(pkey_); | |
208 #else | |
209 CRYPTO_add(&pkey_->references, 1, CRYPTO_LOCK_EVP_PKEY); | |
210 #endif | |
211 } | |
212 | |
213 std::string OpenSSLKeyPair::PrivateKeyToPEMString() const { | |
214 BIO* temp_memory_bio = BIO_new(BIO_s_mem()); | |
215 if (!temp_memory_bio) { | |
216 LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; | |
217 RTC_NOTREACHED(); | |
218 return ""; | |
219 } | |
220 if (!PEM_write_bio_PrivateKey( | |
221 temp_memory_bio, pkey_, nullptr, nullptr, 0, nullptr, nullptr)) { | |
222 LOG_F(LS_ERROR) << "Failed to write private key"; | |
223 BIO_free(temp_memory_bio); | |
224 RTC_NOTREACHED(); | |
225 return ""; | |
226 } | |
227 BIO_write(temp_memory_bio, "\0", 1); | |
228 char* buffer; | |
229 BIO_get_mem_data(temp_memory_bio, &buffer); | |
230 std::string priv_key_str = buffer; | |
231 BIO_free(temp_memory_bio); | |
232 return priv_key_str; | |
233 } | |
234 | |
235 std::string OpenSSLKeyPair::PublicKeyToPEMString() const { | |
236 BIO* temp_memory_bio = BIO_new(BIO_s_mem()); | |
237 if (!temp_memory_bio) { | |
238 LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; | |
239 RTC_NOTREACHED(); | |
240 return ""; | |
241 } | |
242 if (!PEM_write_bio_PUBKEY(temp_memory_bio, pkey_)) { | |
243 LOG_F(LS_ERROR) << "Failed to write public key"; | |
244 BIO_free(temp_memory_bio); | |
245 RTC_NOTREACHED(); | |
246 return ""; | |
247 } | |
248 BIO_write(temp_memory_bio, "\0", 1); | |
249 char* buffer; | |
250 BIO_get_mem_data(temp_memory_bio, &buffer); | |
251 std::string pub_key_str = buffer; | |
252 BIO_free(temp_memory_bio); | |
253 return pub_key_str; | |
254 } | |
255 | |
256 bool OpenSSLKeyPair::operator==(const OpenSSLKeyPair& other) const { | |
257 return EVP_PKEY_cmp(this->pkey_, other.pkey_) == 1; | |
258 } | |
259 | |
260 bool OpenSSLKeyPair::operator!=(const OpenSSLKeyPair& other) const { | |
261 return !(*this == other); | |
262 } | |
263 | |
264 #if !defined(NDEBUG) | |
265 // Print a certificate to the log, for debugging. | |
266 static void PrintCert(X509* x509) { | |
267 BIO* temp_memory_bio = BIO_new(BIO_s_mem()); | |
268 if (!temp_memory_bio) { | |
269 LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; | |
270 return; | |
271 } | |
272 X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0); | |
273 BIO_write(temp_memory_bio, "\0", 1); | |
274 char* buffer; | |
275 BIO_get_mem_data(temp_memory_bio, &buffer); | |
276 LOG(LS_VERBOSE) << buffer; | |
277 BIO_free(temp_memory_bio); | |
278 } | |
279 #endif | |
280 | |
281 OpenSSLCertificate* OpenSSLCertificate::Generate( | |
282 OpenSSLKeyPair* key_pair, const SSLIdentityParams& params) { | |
283 SSLIdentityParams actual_params(params); | |
284 if (actual_params.common_name.empty()) { | |
285 // Use a random string, arbitrarily 8chars long. | |
286 actual_params.common_name = CreateRandomString(8); | |
287 } | |
288 X509* x509 = MakeCertificate(key_pair->pkey(), actual_params); | |
289 if (!x509) { | |
290 LogSSLErrors("Generating certificate"); | |
291 return nullptr; | |
292 } | |
293 #if !defined(NDEBUG) | |
294 PrintCert(x509); | |
295 #endif | |
296 OpenSSLCertificate* ret = new OpenSSLCertificate(x509); | |
297 X509_free(x509); | |
298 return ret; | |
299 } | |
300 | |
301 OpenSSLCertificate* OpenSSLCertificate::FromPEMString( | |
302 const std::string& pem_string) { | |
303 BIO* bio = BIO_new_mem_buf(const_cast<char*>(pem_string.c_str()), -1); | |
304 if (!bio) | |
305 return nullptr; | |
306 BIO_set_mem_eof_return(bio, 0); | |
307 X509* x509 = | |
308 PEM_read_bio_X509(bio, nullptr, nullptr, const_cast<char*>("\0")); | |
309 BIO_free(bio); // Frees the BIO, but not the pointed-to string. | |
310 | |
311 if (!x509) | |
312 return nullptr; | |
313 | |
314 OpenSSLCertificate* ret = new OpenSSLCertificate(x509); | |
315 X509_free(x509); | |
316 return ret; | |
317 } | |
318 | |
319 // NOTE: This implementation only functions correctly after InitializeSSL | |
320 // and before CleanupSSL. | |
321 bool OpenSSLCertificate::GetSignatureDigestAlgorithm( | |
322 std::string* algorithm) const { | |
323 int nid = OBJ_obj2nid(x509_->sig_alg->algorithm); | |
324 switch (nid) { | |
325 case NID_md5WithRSA: | |
326 case NID_md5WithRSAEncryption: | |
327 *algorithm = DIGEST_MD5; | |
328 break; | |
329 case NID_ecdsa_with_SHA1: | |
330 case NID_dsaWithSHA1: | |
331 case NID_dsaWithSHA1_2: | |
332 case NID_sha1WithRSA: | |
333 case NID_sha1WithRSAEncryption: | |
334 *algorithm = DIGEST_SHA_1; | |
335 break; | |
336 case NID_ecdsa_with_SHA224: | |
337 case NID_sha224WithRSAEncryption: | |
338 case NID_dsa_with_SHA224: | |
339 *algorithm = DIGEST_SHA_224; | |
340 break; | |
341 case NID_ecdsa_with_SHA256: | |
342 case NID_sha256WithRSAEncryption: | |
343 case NID_dsa_with_SHA256: | |
344 *algorithm = DIGEST_SHA_256; | |
345 break; | |
346 case NID_ecdsa_with_SHA384: | |
347 case NID_sha384WithRSAEncryption: | |
348 *algorithm = DIGEST_SHA_384; | |
349 break; | |
350 case NID_ecdsa_with_SHA512: | |
351 case NID_sha512WithRSAEncryption: | |
352 *algorithm = DIGEST_SHA_512; | |
353 break; | |
354 default: | |
355 // Unknown algorithm. There are several unhandled options that are less | |
356 // common and more complex. | |
357 LOG(LS_ERROR) << "Unknown signature algorithm NID: " << nid; | |
358 algorithm->clear(); | |
359 return false; | |
360 } | |
361 return true; | |
362 } | |
363 | |
364 std::unique_ptr<SSLCertChain> OpenSSLCertificate::GetChain() const { | |
365 // Chains are not yet supported when using OpenSSL. | |
366 // OpenSSLStreamAdapter::SSLVerifyCallback currently requires the remote | |
367 // certificate to be self-signed. | |
368 return nullptr; | |
369 } | |
370 | |
371 bool OpenSSLCertificate::ComputeDigest(const std::string& algorithm, | |
372 unsigned char* digest, | |
373 size_t size, | |
374 size_t* length) const { | |
375 return ComputeDigest(x509_, algorithm, digest, size, length); | |
376 } | |
377 | |
378 bool OpenSSLCertificate::ComputeDigest(const X509* x509, | |
379 const std::string& algorithm, | |
380 unsigned char* digest, | |
381 size_t size, | |
382 size_t* length) { | |
383 const EVP_MD* md; | |
384 unsigned int n; | |
385 | |
386 if (!OpenSSLDigest::GetDigestEVP(algorithm, &md)) | |
387 return false; | |
388 | |
389 if (size < static_cast<size_t>(EVP_MD_size(md))) | |
390 return false; | |
391 | |
392 X509_digest(x509, md, digest, &n); | |
393 | |
394 *length = n; | |
395 | |
396 return true; | |
397 } | |
398 | |
399 OpenSSLCertificate::~OpenSSLCertificate() { | |
400 X509_free(x509_); | |
401 } | |
402 | |
403 OpenSSLCertificate* OpenSSLCertificate::GetReference() const { | |
404 return new OpenSSLCertificate(x509_); | |
405 } | |
406 | |
407 std::string OpenSSLCertificate::ToPEMString() const { | |
408 BIO* bio = BIO_new(BIO_s_mem()); | |
409 if (!bio) { | |
410 FATAL() << "unreachable code"; | |
411 } | |
412 if (!PEM_write_bio_X509(bio, x509_)) { | |
413 BIO_free(bio); | |
414 FATAL() << "unreachable code"; | |
415 } | |
416 BIO_write(bio, "\0", 1); | |
417 char* buffer; | |
418 BIO_get_mem_data(bio, &buffer); | |
419 std::string ret(buffer); | |
420 BIO_free(bio); | |
421 return ret; | |
422 } | |
423 | |
424 void OpenSSLCertificate::ToDER(Buffer* der_buffer) const { | |
425 // In case of failure, make sure to leave the buffer empty. | |
426 der_buffer->SetSize(0); | |
427 | |
428 // Calculates the DER representation of the certificate, from scratch. | |
429 BIO* bio = BIO_new(BIO_s_mem()); | |
430 if (!bio) { | |
431 FATAL() << "unreachable code"; | |
432 } | |
433 if (!i2d_X509_bio(bio, x509_)) { | |
434 BIO_free(bio); | |
435 FATAL() << "unreachable code"; | |
436 } | |
437 char* data; | |
438 size_t length = BIO_get_mem_data(bio, &data); | |
439 der_buffer->SetData(data, length); | |
440 BIO_free(bio); | |
441 } | |
442 | |
443 void OpenSSLCertificate::AddReference() const { | |
444 RTC_DCHECK(x509_ != nullptr); | |
445 #if defined(OPENSSL_IS_BORINGSSL) | |
446 X509_up_ref(x509_); | |
447 #else | |
448 CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509); | |
449 #endif | |
450 } | |
451 | |
452 bool OpenSSLCertificate::operator==(const OpenSSLCertificate& other) const { | |
453 return X509_cmp(this->x509_, other.x509_) == 0; | |
454 } | |
455 | |
456 bool OpenSSLCertificate::operator!=(const OpenSSLCertificate& other) const { | |
457 return !(*this == other); | |
458 } | |
459 | |
460 // Documented in sslidentity.h. | |
461 int64_t OpenSSLCertificate::CertificateExpirationTime() const { | |
462 ASN1_TIME* expire_time = X509_get_notAfter(x509_); | |
463 bool long_format; | |
464 | |
465 if (expire_time->type == V_ASN1_UTCTIME) { | |
466 long_format = false; | |
467 } else if (expire_time->type == V_ASN1_GENERALIZEDTIME) { | |
468 long_format = true; | |
469 } else { | |
470 return -1; | |
471 } | |
472 | |
473 return ASN1TimeToSec(expire_time->data, expire_time->length, long_format); | |
474 } | |
475 | |
476 OpenSSLIdentity::OpenSSLIdentity(OpenSSLKeyPair* key_pair, | |
477 OpenSSLCertificate* certificate) | |
478 : key_pair_(key_pair), certificate_(certificate) { | |
479 RTC_DCHECK(key_pair != nullptr); | |
480 RTC_DCHECK(certificate != nullptr); | |
481 } | |
482 | |
483 OpenSSLIdentity::~OpenSSLIdentity() = default; | |
484 | |
485 OpenSSLIdentity* OpenSSLIdentity::GenerateInternal( | |
486 const SSLIdentityParams& params) { | |
487 OpenSSLKeyPair* key_pair = OpenSSLKeyPair::Generate(params.key_params); | |
488 if (key_pair) { | |
489 OpenSSLCertificate* certificate = | |
490 OpenSSLCertificate::Generate(key_pair, params); | |
491 if (certificate) | |
492 return new OpenSSLIdentity(key_pair, certificate); | |
493 delete key_pair; | |
494 } | |
495 LOG(LS_INFO) << "Identity generation failed"; | |
496 return nullptr; | |
497 } | |
498 | |
499 OpenSSLIdentity* OpenSSLIdentity::GenerateWithExpiration( | |
500 const std::string& common_name, | |
501 const KeyParams& key_params, | |
502 time_t certificate_lifetime) { | |
503 SSLIdentityParams params; | |
504 params.key_params = key_params; | |
505 params.common_name = common_name; | |
506 time_t now = time(nullptr); | |
507 params.not_before = now + kCertificateWindowInSeconds; | |
508 params.not_after = now + certificate_lifetime; | |
509 if (params.not_before > params.not_after) | |
510 return nullptr; | |
511 return GenerateInternal(params); | |
512 } | |
513 | |
514 OpenSSLIdentity* OpenSSLIdentity::GenerateForTest( | |
515 const SSLIdentityParams& params) { | |
516 return GenerateInternal(params); | |
517 } | |
518 | |
519 SSLIdentity* OpenSSLIdentity::FromPEMStrings( | |
520 const std::string& private_key, | |
521 const std::string& certificate) { | |
522 std::unique_ptr<OpenSSLCertificate> cert( | |
523 OpenSSLCertificate::FromPEMString(certificate)); | |
524 if (!cert) { | |
525 LOG(LS_ERROR) << "Failed to create OpenSSLCertificate from PEM string."; | |
526 return nullptr; | |
527 } | |
528 | |
529 OpenSSLKeyPair* key_pair = | |
530 OpenSSLKeyPair::FromPrivateKeyPEMString(private_key); | |
531 if (!key_pair) { | |
532 LOG(LS_ERROR) << "Failed to create key pair from PEM string."; | |
533 return nullptr; | |
534 } | |
535 | |
536 return new OpenSSLIdentity(key_pair, | |
537 cert.release()); | |
538 } | |
539 | |
540 const OpenSSLCertificate& OpenSSLIdentity::certificate() const { | |
541 return *certificate_; | |
542 } | |
543 | |
544 OpenSSLIdentity* OpenSSLIdentity::GetReference() const { | |
545 return new OpenSSLIdentity(key_pair_->GetReference(), | |
546 certificate_->GetReference()); | |
547 } | |
548 | |
549 bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) { | |
550 // 1 is the documented success return code. | |
551 if (SSL_CTX_use_certificate(ctx, certificate_->x509()) != 1 || | |
552 SSL_CTX_use_PrivateKey(ctx, key_pair_->pkey()) != 1) { | |
553 LogSSLErrors("Configuring key and certificate"); | |
554 return false; | |
555 } | |
556 return true; | |
557 } | |
558 | |
559 std::string OpenSSLIdentity::PrivateKeyToPEMString() const { | |
560 return key_pair_->PrivateKeyToPEMString(); | |
561 } | |
562 | |
563 std::string OpenSSLIdentity::PublicKeyToPEMString() const { | |
564 return key_pair_->PublicKeyToPEMString(); | |
565 } | |
566 | |
567 bool OpenSSLIdentity::operator==(const OpenSSLIdentity& other) const { | |
568 return *this->key_pair_ == *other.key_pair_ && | |
569 *this->certificate_ == *other.certificate_; | |
570 } | |
571 | |
572 bool OpenSSLIdentity::operator!=(const OpenSSLIdentity& other) const { | |
573 return !(*this == other); | |
574 } | |
575 | |
576 } // namespace rtc | |
OLD | NEW |