henrike@webrtc.org | f7795df | 2014-05-13 18:00:26 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2012 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 <algorithm> |
| 12 | #include <string> |
| 13 | #include <vector> |
| 14 | |
| 15 | #if HAVE_CONFIG_H |
| 16 | #include "config.h" |
| 17 | #endif // HAVE_CONFIG_H |
| 18 | |
| 19 | #if HAVE_NSS_SSL_H |
| 20 | |
| 21 | #include "webrtc/base/nssidentity.h" |
| 22 | |
| 23 | #include "cert.h" |
| 24 | #include "cryptohi.h" |
| 25 | #include "keyhi.h" |
| 26 | #include "nss.h" |
| 27 | #include "pk11pub.h" |
| 28 | #include "sechash.h" |
| 29 | |
| 30 | #include "webrtc/base/logging.h" |
| 31 | #include "webrtc/base/helpers.h" |
| 32 | #include "webrtc/base/nssstreamadapter.h" |
| 33 | #include "webrtc/base/safe_conversions.h" |
| 34 | |
| 35 | namespace rtc { |
| 36 | |
| 37 | // Certificate validity lifetime in seconds. |
| 38 | static const int CERTIFICATE_LIFETIME = 60*60*24*30; // 30 days, arbitrarily |
| 39 | // Certificate validity window in seconds. |
| 40 | // This is to compensate for slightly incorrect system clocks. |
| 41 | static const int CERTIFICATE_WINDOW = -60*60*24; |
| 42 | |
| 43 | NSSKeyPair::~NSSKeyPair() { |
| 44 | if (privkey_) |
| 45 | SECKEY_DestroyPrivateKey(privkey_); |
| 46 | if (pubkey_) |
| 47 | SECKEY_DestroyPublicKey(pubkey_); |
| 48 | } |
| 49 | |
| 50 | NSSKeyPair *NSSKeyPair::Generate() { |
| 51 | SECKEYPrivateKey *privkey = NULL; |
| 52 | SECKEYPublicKey *pubkey = NULL; |
| 53 | PK11RSAGenParams rsaparams; |
| 54 | rsaparams.keySizeInBits = 1024; |
| 55 | rsaparams.pe = 0x010001; // 65537 -- a common RSA public exponent. |
| 56 | |
| 57 | privkey = PK11_GenerateKeyPair(NSSContext::GetSlot(), |
| 58 | CKM_RSA_PKCS_KEY_PAIR_GEN, |
| 59 | &rsaparams, &pubkey, PR_FALSE /*permanent*/, |
| 60 | PR_FALSE /*sensitive*/, NULL); |
| 61 | if (!privkey) { |
| 62 | LOG(LS_ERROR) << "Couldn't generate key pair"; |
| 63 | return NULL; |
| 64 | } |
| 65 | |
| 66 | return new NSSKeyPair(privkey, pubkey); |
| 67 | } |
| 68 | |
| 69 | // Just make a copy. |
| 70 | NSSKeyPair *NSSKeyPair::GetReference() { |
| 71 | SECKEYPrivateKey *privkey = SECKEY_CopyPrivateKey(privkey_); |
| 72 | if (!privkey) |
| 73 | return NULL; |
| 74 | |
| 75 | SECKEYPublicKey *pubkey = SECKEY_CopyPublicKey(pubkey_); |
| 76 | if (!pubkey) { |
| 77 | SECKEY_DestroyPrivateKey(privkey); |
| 78 | return NULL; |
| 79 | } |
| 80 | |
| 81 | return new NSSKeyPair(privkey, pubkey); |
| 82 | } |
| 83 | |
| 84 | NSSCertificate::NSSCertificate(CERTCertificate* cert) |
| 85 | : certificate_(CERT_DupCertificate(cert)) { |
| 86 | ASSERT(certificate_ != NULL); |
| 87 | } |
| 88 | |
| 89 | static void DeleteCert(SSLCertificate* cert) { |
| 90 | delete cert; |
| 91 | } |
| 92 | |
| 93 | NSSCertificate::NSSCertificate(CERTCertList* cert_list) { |
| 94 | // Copy the first cert into certificate_. |
| 95 | CERTCertListNode* node = CERT_LIST_HEAD(cert_list); |
| 96 | certificate_ = CERT_DupCertificate(node->cert); |
| 97 | |
| 98 | // Put any remaining certificates into the chain. |
| 99 | node = CERT_LIST_NEXT(node); |
| 100 | std::vector<SSLCertificate*> certs; |
| 101 | for (; !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { |
| 102 | certs.push_back(new NSSCertificate(node->cert)); |
| 103 | } |
| 104 | |
| 105 | if (!certs.empty()) |
| 106 | chain_.reset(new SSLCertChain(certs)); |
| 107 | |
| 108 | // The SSLCertChain constructor copies its input, so now we have to delete |
| 109 | // the originals. |
| 110 | std::for_each(certs.begin(), certs.end(), DeleteCert); |
| 111 | } |
| 112 | |
| 113 | NSSCertificate::NSSCertificate(CERTCertificate* cert, SSLCertChain* chain) |
| 114 | : certificate_(CERT_DupCertificate(cert)) { |
| 115 | ASSERT(certificate_ != NULL); |
| 116 | if (chain) |
| 117 | chain_.reset(chain->Copy()); |
| 118 | } |
| 119 | |
| 120 | |
| 121 | NSSCertificate *NSSCertificate::FromPEMString(const std::string &pem_string) { |
| 122 | std::string der; |
| 123 | if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der)) |
| 124 | return NULL; |
| 125 | |
| 126 | SECItem der_cert; |
| 127 | der_cert.data = reinterpret_cast<unsigned char *>(const_cast<char *>( |
| 128 | der.data())); |
| 129 | der_cert.len = checked_cast<unsigned int>(der.size()); |
| 130 | CERTCertificate *cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), |
| 131 | &der_cert, NULL, PR_FALSE, PR_TRUE); |
| 132 | |
| 133 | if (!cert) |
| 134 | return NULL; |
| 135 | |
| 136 | NSSCertificate* ret = new NSSCertificate(cert); |
| 137 | CERT_DestroyCertificate(cert); |
| 138 | return ret; |
| 139 | } |
| 140 | |
| 141 | NSSCertificate *NSSCertificate::GetReference() const { |
| 142 | return new NSSCertificate(certificate_, chain_.get()); |
| 143 | } |
| 144 | |
| 145 | std::string NSSCertificate::ToPEMString() const { |
| 146 | return SSLIdentity::DerToPem(kPemTypeCertificate, |
| 147 | certificate_->derCert.data, |
| 148 | certificate_->derCert.len); |
| 149 | } |
| 150 | |
| 151 | void NSSCertificate::ToDER(Buffer* der_buffer) const { |
| 152 | der_buffer->SetData(certificate_->derCert.data, certificate_->derCert.len); |
| 153 | } |
| 154 | |
| 155 | static bool Certifies(CERTCertificate* parent, CERTCertificate* child) { |
| 156 | // TODO(bemasc): Identify stricter validation checks to use here. In the |
| 157 | // context of some future identity standard, it might make sense to check |
| 158 | // the certificates' roles, expiration dates, self-signatures (if |
| 159 | // self-signed), certificate transparency logging, or many other attributes. |
| 160 | // NOTE: Future changes to this validation may reject some previously allowed |
| 161 | // certificate chains. Users should be advised not to deploy chained |
| 162 | // certificates except in controlled environments until the validity |
| 163 | // requirements are finalized. |
| 164 | |
| 165 | // Check that the parent's name is the same as the child's claimed issuer. |
| 166 | SECComparison name_status = |
| 167 | CERT_CompareName(&child->issuer, &parent->subject); |
| 168 | if (name_status != SECEqual) |
| 169 | return false; |
| 170 | |
| 171 | // Extract the parent's public key, or fail if the key could not be read |
| 172 | // (e.g. certificate is corrupted). |
| 173 | SECKEYPublicKey* parent_key = CERT_ExtractPublicKey(parent); |
| 174 | if (!parent_key) |
| 175 | return false; |
| 176 | |
| 177 | // Check that the parent's privkey was actually used to generate the child's |
| 178 | // signature. |
| 179 | SECStatus verified = CERT_VerifySignedDataWithPublicKey( |
| 180 | &child->signatureWrap, parent_key, NULL); |
| 181 | SECKEY_DestroyPublicKey(parent_key); |
| 182 | return verified == SECSuccess; |
| 183 | } |
| 184 | |
| 185 | bool NSSCertificate::IsValidChain(const CERTCertList* cert_list) { |
| 186 | CERTCertListNode* child = CERT_LIST_HEAD(cert_list); |
| 187 | for (CERTCertListNode* parent = CERT_LIST_NEXT(child); |
| 188 | !CERT_LIST_END(parent, cert_list); |
| 189 | child = parent, parent = CERT_LIST_NEXT(parent)) { |
| 190 | if (!Certifies(parent->cert, child->cert)) |
| 191 | return false; |
| 192 | } |
| 193 | return true; |
| 194 | } |
| 195 | |
| 196 | bool NSSCertificate::GetDigestLength(const std::string& algorithm, |
| 197 | size_t* length) { |
| 198 | const SECHashObject *ho; |
| 199 | |
| 200 | if (!GetDigestObject(algorithm, &ho)) |
| 201 | return false; |
| 202 | |
| 203 | *length = ho->length; |
| 204 | |
| 205 | return true; |
| 206 | } |
| 207 | |
| 208 | bool NSSCertificate::GetSignatureDigestAlgorithm(std::string* algorithm) const { |
| 209 | // The function sec_DecodeSigAlg in NSS provides this mapping functionality. |
| 210 | // Unfortunately it is private, so the functionality must be duplicated here. |
| 211 | // See https://bugzilla.mozilla.org/show_bug.cgi?id=925165 . |
| 212 | SECOidTag sig_alg = SECOID_GetAlgorithmTag(&certificate_->signature); |
| 213 | switch (sig_alg) { |
| 214 | case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: |
| 215 | *algorithm = DIGEST_MD5; |
| 216 | break; |
| 217 | case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: |
| 218 | case SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE: |
| 219 | case SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE: |
| 220 | case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST: |
| 221 | case SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST: |
| 222 | case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE: |
| 223 | case SEC_OID_MISSI_DSS: |
| 224 | case SEC_OID_MISSI_KEA_DSS: |
| 225 | case SEC_OID_MISSI_KEA_DSS_OLD: |
| 226 | case SEC_OID_MISSI_DSS_OLD: |
| 227 | *algorithm = DIGEST_SHA_1; |
| 228 | break; |
| 229 | case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE: |
| 230 | case SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION: |
| 231 | case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST: |
| 232 | *algorithm = DIGEST_SHA_224; |
| 233 | break; |
| 234 | case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE: |
| 235 | case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: |
| 236 | case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST: |
| 237 | *algorithm = DIGEST_SHA_256; |
| 238 | break; |
| 239 | case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE: |
| 240 | case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION: |
| 241 | *algorithm = DIGEST_SHA_384; |
| 242 | break; |
| 243 | case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE: |
| 244 | case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: |
| 245 | *algorithm = DIGEST_SHA_512; |
| 246 | break; |
| 247 | default: |
| 248 | // Unknown algorithm. There are several unhandled options that are less |
| 249 | // common and more complex. |
| 250 | algorithm->clear(); |
| 251 | return false; |
| 252 | } |
| 253 | return true; |
| 254 | } |
| 255 | |
| 256 | bool NSSCertificate::ComputeDigest(const std::string& algorithm, |
| 257 | unsigned char* digest, |
| 258 | size_t size, |
| 259 | size_t* length) const { |
| 260 | const SECHashObject *ho; |
| 261 | |
| 262 | if (!GetDigestObject(algorithm, &ho)) |
| 263 | return false; |
| 264 | |
| 265 | if (size < ho->length) // Sanity check for fit |
| 266 | return false; |
| 267 | |
| 268 | SECStatus rv = HASH_HashBuf(ho->type, digest, |
| 269 | certificate_->derCert.data, |
| 270 | certificate_->derCert.len); |
| 271 | if (rv != SECSuccess) |
| 272 | return false; |
| 273 | |
| 274 | *length = ho->length; |
| 275 | |
| 276 | return true; |
| 277 | } |
| 278 | |
| 279 | bool NSSCertificate::GetChain(SSLCertChain** chain) const { |
| 280 | if (!chain_) |
| 281 | return false; |
| 282 | |
| 283 | *chain = chain_->Copy(); |
| 284 | return true; |
| 285 | } |
| 286 | |
| 287 | bool NSSCertificate::Equals(const NSSCertificate *tocompare) const { |
| 288 | if (!certificate_->derCert.len) |
| 289 | return false; |
| 290 | if (!tocompare->certificate_->derCert.len) |
| 291 | return false; |
| 292 | |
| 293 | if (certificate_->derCert.len != tocompare->certificate_->derCert.len) |
| 294 | return false; |
| 295 | |
| 296 | return memcmp(certificate_->derCert.data, |
| 297 | tocompare->certificate_->derCert.data, |
| 298 | certificate_->derCert.len) == 0; |
| 299 | } |
| 300 | |
| 301 | |
| 302 | bool NSSCertificate::GetDigestObject(const std::string &algorithm, |
| 303 | const SECHashObject **hop) { |
| 304 | const SECHashObject *ho; |
| 305 | HASH_HashType hash_type; |
| 306 | |
| 307 | if (algorithm == DIGEST_SHA_1) { |
| 308 | hash_type = HASH_AlgSHA1; |
| 309 | // HASH_AlgSHA224 is not supported in the chromium linux build system. |
| 310 | #if 0 |
| 311 | } else if (algorithm == DIGEST_SHA_224) { |
| 312 | hash_type = HASH_AlgSHA224; |
| 313 | #endif |
| 314 | } else if (algorithm == DIGEST_SHA_256) { |
| 315 | hash_type = HASH_AlgSHA256; |
| 316 | } else if (algorithm == DIGEST_SHA_384) { |
| 317 | hash_type = HASH_AlgSHA384; |
| 318 | } else if (algorithm == DIGEST_SHA_512) { |
| 319 | hash_type = HASH_AlgSHA512; |
| 320 | } else { |
| 321 | return false; |
| 322 | } |
| 323 | |
| 324 | ho = HASH_GetHashObject(hash_type); |
| 325 | |
| 326 | ASSERT(ho->length >= 20); // Can't happen |
| 327 | *hop = ho; |
| 328 | |
| 329 | return true; |
| 330 | } |
| 331 | |
| 332 | |
| 333 | NSSIdentity* NSSIdentity::GenerateInternal(const SSLIdentityParams& params) { |
| 334 | std::string subject_name_string = "CN=" + params.common_name; |
| 335 | CERTName *subject_name = CERT_AsciiToName( |
| 336 | const_cast<char *>(subject_name_string.c_str())); |
| 337 | NSSIdentity *identity = NULL; |
| 338 | CERTSubjectPublicKeyInfo *spki = NULL; |
| 339 | CERTCertificateRequest *certreq = NULL; |
| 340 | CERTValidity *validity = NULL; |
| 341 | CERTCertificate *certificate = NULL; |
| 342 | NSSKeyPair *keypair = NSSKeyPair::Generate(); |
| 343 | SECItem inner_der; |
| 344 | SECStatus rv; |
| 345 | PLArenaPool* arena; |
| 346 | SECItem signed_cert; |
| 347 | PRTime now = PR_Now(); |
| 348 | PRTime not_before = |
| 349 | now + static_cast<PRTime>(params.not_before) * PR_USEC_PER_SEC; |
| 350 | PRTime not_after = |
| 351 | now + static_cast<PRTime>(params.not_after) * PR_USEC_PER_SEC; |
| 352 | |
| 353 | inner_der.len = 0; |
| 354 | inner_der.data = NULL; |
| 355 | |
| 356 | if (!keypair) { |
| 357 | LOG(LS_ERROR) << "Couldn't generate key pair"; |
| 358 | goto fail; |
| 359 | } |
| 360 | |
| 361 | if (!subject_name) { |
| 362 | LOG(LS_ERROR) << "Couldn't convert subject name " << subject_name; |
| 363 | goto fail; |
| 364 | } |
| 365 | |
| 366 | spki = SECKEY_CreateSubjectPublicKeyInfo(keypair->pubkey()); |
| 367 | if (!spki) { |
| 368 | LOG(LS_ERROR) << "Couldn't create SPKI"; |
| 369 | goto fail; |
| 370 | } |
| 371 | |
| 372 | certreq = CERT_CreateCertificateRequest(subject_name, spki, NULL); |
| 373 | if (!certreq) { |
| 374 | LOG(LS_ERROR) << "Couldn't create certificate signing request"; |
| 375 | goto fail; |
| 376 | } |
| 377 | |
| 378 | validity = CERT_CreateValidity(not_before, not_after); |
| 379 | if (!validity) { |
| 380 | LOG(LS_ERROR) << "Couldn't create validity"; |
| 381 | goto fail; |
| 382 | } |
| 383 | |
| 384 | unsigned long serial; |
| 385 | // Note: This serial in principle could collide, but it's unlikely |
| 386 | rv = PK11_GenerateRandom(reinterpret_cast<unsigned char *>(&serial), |
| 387 | sizeof(serial)); |
| 388 | if (rv != SECSuccess) { |
| 389 | LOG(LS_ERROR) << "Couldn't generate random serial"; |
| 390 | goto fail; |
| 391 | } |
| 392 | |
| 393 | certificate = CERT_CreateCertificate(serial, subject_name, validity, certreq); |
| 394 | if (!certificate) { |
| 395 | LOG(LS_ERROR) << "Couldn't create certificate"; |
| 396 | goto fail; |
| 397 | } |
| 398 | |
| 399 | arena = certificate->arena; |
| 400 | |
| 401 | rv = SECOID_SetAlgorithmID(arena, &certificate->signature, |
| 402 | SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, NULL); |
| 403 | if (rv != SECSuccess) |
| 404 | goto fail; |
| 405 | |
| 406 | // Set version to X509v3. |
| 407 | *(certificate->version.data) = 2; |
| 408 | certificate->version.len = 1; |
| 409 | |
| 410 | if (!SEC_ASN1EncodeItem(arena, &inner_der, certificate, |
| 411 | SEC_ASN1_GET(CERT_CertificateTemplate))) |
| 412 | goto fail; |
| 413 | |
| 414 | rv = SEC_DerSignData(arena, &signed_cert, inner_der.data, inner_der.len, |
| 415 | keypair->privkey(), |
| 416 | SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION); |
| 417 | if (rv != SECSuccess) { |
| 418 | LOG(LS_ERROR) << "Couldn't sign certificate"; |
| 419 | goto fail; |
| 420 | } |
| 421 | certificate->derCert = signed_cert; |
| 422 | |
| 423 | identity = new NSSIdentity(keypair, new NSSCertificate(certificate)); |
| 424 | |
| 425 | goto done; |
| 426 | |
| 427 | fail: |
| 428 | delete keypair; |
| 429 | |
| 430 | done: |
| 431 | if (certificate) CERT_DestroyCertificate(certificate); |
| 432 | if (subject_name) CERT_DestroyName(subject_name); |
| 433 | if (spki) SECKEY_DestroySubjectPublicKeyInfo(spki); |
| 434 | if (certreq) CERT_DestroyCertificateRequest(certreq); |
| 435 | if (validity) CERT_DestroyValidity(validity); |
| 436 | return identity; |
| 437 | } |
| 438 | |
| 439 | NSSIdentity* NSSIdentity::Generate(const std::string &common_name) { |
| 440 | SSLIdentityParams params; |
| 441 | params.common_name = common_name; |
| 442 | params.not_before = CERTIFICATE_WINDOW; |
| 443 | params.not_after = CERTIFICATE_LIFETIME; |
| 444 | return GenerateInternal(params); |
| 445 | } |
| 446 | |
| 447 | NSSIdentity* NSSIdentity::GenerateForTest(const SSLIdentityParams& params) { |
| 448 | return GenerateInternal(params); |
| 449 | } |
| 450 | |
| 451 | SSLIdentity* NSSIdentity::FromPEMStrings(const std::string& private_key, |
| 452 | const std::string& certificate) { |
| 453 | std::string private_key_der; |
| 454 | if (!SSLIdentity::PemToDer( |
| 455 | kPemTypeRsaPrivateKey, private_key, &private_key_der)) |
| 456 | return NULL; |
| 457 | |
| 458 | SECItem private_key_item; |
| 459 | private_key_item.data = reinterpret_cast<unsigned char *>( |
| 460 | const_cast<char *>(private_key_der.c_str())); |
| 461 | private_key_item.len = checked_cast<unsigned int>(private_key_der.size()); |
| 462 | |
| 463 | const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT | |
| 464 | KU_DIGITAL_SIGNATURE; |
| 465 | |
| 466 | SECKEYPrivateKey* privkey = NULL; |
| 467 | SECStatus rv = |
| 468 | PK11_ImportDERPrivateKeyInfoAndReturnKey(NSSContext::GetSlot(), |
| 469 | &private_key_item, |
| 470 | NULL, NULL, PR_FALSE, PR_FALSE, |
| 471 | key_usage, &privkey, NULL); |
| 472 | if (rv != SECSuccess) { |
| 473 | LOG(LS_ERROR) << "Couldn't import private key"; |
| 474 | return NULL; |
| 475 | } |
| 476 | |
| 477 | SECKEYPublicKey *pubkey = SECKEY_ConvertToPublicKey(privkey); |
| 478 | if (rv != SECSuccess) { |
| 479 | SECKEY_DestroyPrivateKey(privkey); |
| 480 | LOG(LS_ERROR) << "Couldn't convert private key to public key"; |
| 481 | return NULL; |
| 482 | } |
| 483 | |
| 484 | // Assign to a scoped_ptr so we don't leak on error. |
| 485 | scoped_ptr<NSSKeyPair> keypair(new NSSKeyPair(privkey, pubkey)); |
| 486 | |
| 487 | scoped_ptr<NSSCertificate> cert(NSSCertificate::FromPEMString(certificate)); |
| 488 | if (!cert) { |
| 489 | LOG(LS_ERROR) << "Couldn't parse certificate"; |
| 490 | return NULL; |
| 491 | } |
| 492 | |
| 493 | // TODO(ekr@rtfm.com): Check the public key against the certificate. |
| 494 | |
| 495 | return new NSSIdentity(keypair.release(), cert.release()); |
| 496 | } |
| 497 | |
| 498 | NSSIdentity *NSSIdentity::GetReference() const { |
| 499 | NSSKeyPair *keypair = keypair_->GetReference(); |
| 500 | if (!keypair) |
| 501 | return NULL; |
| 502 | |
| 503 | NSSCertificate *certificate = certificate_->GetReference(); |
| 504 | if (!certificate) { |
| 505 | delete keypair; |
| 506 | return NULL; |
| 507 | } |
| 508 | |
| 509 | return new NSSIdentity(keypair, certificate); |
| 510 | } |
| 511 | |
| 512 | |
| 513 | NSSCertificate &NSSIdentity::certificate() const { |
| 514 | return *certificate_; |
| 515 | } |
| 516 | |
| 517 | |
| 518 | } // rtc namespace |
| 519 | |
| 520 | #endif // HAVE_NSS_SSL_H |
| 521 | |