| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/cert/nss_cert_database.h" | |
| 6 | |
| 7 #include <cert.h> | |
| 8 #include <certdb.h> | |
| 9 #include <keyhi.h> | |
| 10 #include <pk11pub.h> | |
| 11 #include <secmod.h> | |
| 12 | |
| 13 #include "base/bind.h" | |
| 14 #include "base/callback.h" | |
| 15 #include "base/logging.h" | |
| 16 #include "base/memory/scoped_ptr.h" | |
| 17 #include "base/observer_list_threadsafe.h" | |
| 18 #include "base/task_runner.h" | |
| 19 #include "base/task_runner_util.h" | |
| 20 #include "base/threading/worker_pool.h" | |
| 21 #include "crypto/scoped_nss_types.h" | |
| 22 #include "net/base/crypto_module.h" | |
| 23 #include "net/base/net_errors.h" | |
| 24 #include "net/cert/cert_database.h" | |
| 25 #include "net/cert/x509_certificate.h" | |
| 26 #include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h" | |
| 27 #include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h" | |
| 28 | |
| 29 // In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use | |
| 30 // the new name of the macro. | |
| 31 #if !defined(CERTDB_TERMINAL_RECORD) | |
| 32 #define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER | |
| 33 #endif | |
| 34 | |
| 35 // PSM = Mozilla's Personal Security Manager. | |
| 36 namespace psm = mozilla_security_manager; | |
| 37 | |
| 38 namespace net { | |
| 39 | |
| 40 namespace { | |
| 41 | |
| 42 // TODO(pneubeck): Move this class out of NSSCertDatabase and to the caller of | |
| 43 // the c'tor of NSSCertDatabase, see https://crbug.com/395983 . | |
| 44 // Helper that observes events from the NSSCertDatabase and forwards them to | |
| 45 // the given CertDatabase. | |
| 46 class CertNotificationForwarder : public NSSCertDatabase::Observer { | |
| 47 public: | |
| 48 explicit CertNotificationForwarder(CertDatabase* cert_db) | |
| 49 : cert_db_(cert_db) {} | |
| 50 | |
| 51 ~CertNotificationForwarder() override {} | |
| 52 | |
| 53 // NSSCertDatabase::Observer implementation: | |
| 54 void OnCertAdded(const X509Certificate* cert) override { | |
| 55 cert_db_->NotifyObserversOfCertAdded(cert); | |
| 56 } | |
| 57 | |
| 58 void OnCertRemoved(const X509Certificate* cert) override { | |
| 59 cert_db_->NotifyObserversOfCertRemoved(cert); | |
| 60 } | |
| 61 | |
| 62 void OnCACertChanged(const X509Certificate* cert) override { | |
| 63 cert_db_->NotifyObserversOfCACertChanged(cert); | |
| 64 } | |
| 65 | |
| 66 private: | |
| 67 CertDatabase* cert_db_; | |
| 68 | |
| 69 DISALLOW_COPY_AND_ASSIGN(CertNotificationForwarder); | |
| 70 }; | |
| 71 | |
| 72 } // namespace | |
| 73 | |
| 74 NSSCertDatabase::ImportCertFailure::ImportCertFailure( | |
| 75 const scoped_refptr<X509Certificate>& cert, | |
| 76 int err) | |
| 77 : certificate(cert), net_error(err) {} | |
| 78 | |
| 79 NSSCertDatabase::ImportCertFailure::~ImportCertFailure() {} | |
| 80 | |
| 81 NSSCertDatabase::NSSCertDatabase(crypto::ScopedPK11Slot public_slot, | |
| 82 crypto::ScopedPK11Slot private_slot) | |
| 83 : public_slot_(public_slot.Pass()), | |
| 84 private_slot_(private_slot.Pass()), | |
| 85 observer_list_(new ObserverListThreadSafe<Observer>), | |
| 86 weak_factory_(this) { | |
| 87 CHECK(public_slot_); | |
| 88 | |
| 89 // This also makes sure that NSS has been initialized. | |
| 90 CertDatabase* cert_db = CertDatabase::GetInstance(); | |
| 91 cert_notification_forwarder_.reset(new CertNotificationForwarder(cert_db)); | |
| 92 AddObserver(cert_notification_forwarder_.get()); | |
| 93 | |
| 94 psm::EnsurePKCS12Init(); | |
| 95 } | |
| 96 | |
| 97 NSSCertDatabase::~NSSCertDatabase() {} | |
| 98 | |
| 99 void NSSCertDatabase::ListCertsSync(CertificateList* certs) { | |
| 100 ListCertsImpl(crypto::ScopedPK11Slot(), certs); | |
| 101 } | |
| 102 | |
| 103 void NSSCertDatabase::ListCerts( | |
| 104 const base::Callback<void(scoped_ptr<CertificateList> certs)>& callback) { | |
| 105 scoped_ptr<CertificateList> certs(new CertificateList()); | |
| 106 | |
| 107 // base::Passed will NULL out |certs|, so cache the underlying pointer here. | |
| 108 CertificateList* raw_certs = certs.get(); | |
| 109 GetSlowTaskRunner()->PostTaskAndReply( | |
| 110 FROM_HERE, | |
| 111 base::Bind(&NSSCertDatabase::ListCertsImpl, | |
| 112 base::Passed(crypto::ScopedPK11Slot()), | |
| 113 base::Unretained(raw_certs)), | |
| 114 base::Bind(callback, base::Passed(&certs))); | |
| 115 } | |
| 116 | |
| 117 void NSSCertDatabase::ListCertsInSlot(const ListCertsCallback& callback, | |
| 118 PK11SlotInfo* slot) { | |
| 119 DCHECK(slot); | |
| 120 scoped_ptr<CertificateList> certs(new CertificateList()); | |
| 121 | |
| 122 // base::Passed will NULL out |certs|, so cache the underlying pointer here. | |
| 123 CertificateList* raw_certs = certs.get(); | |
| 124 GetSlowTaskRunner()->PostTaskAndReply( | |
| 125 FROM_HERE, | |
| 126 base::Bind(&NSSCertDatabase::ListCertsImpl, | |
| 127 base::Passed(crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot))), | |
| 128 base::Unretained(raw_certs)), | |
| 129 base::Bind(callback, base::Passed(&certs))); | |
| 130 } | |
| 131 | |
| 132 #if defined(OS_CHROMEOS) | |
| 133 crypto::ScopedPK11Slot NSSCertDatabase::GetSystemSlot() const { | |
| 134 return crypto::ScopedPK11Slot(); | |
| 135 } | |
| 136 #endif | |
| 137 | |
| 138 crypto::ScopedPK11Slot NSSCertDatabase::GetPublicSlot() const { | |
| 139 return crypto::ScopedPK11Slot(PK11_ReferenceSlot(public_slot_.get())); | |
| 140 } | |
| 141 | |
| 142 crypto::ScopedPK11Slot NSSCertDatabase::GetPrivateSlot() const { | |
| 143 if (!private_slot_) | |
| 144 return crypto::ScopedPK11Slot(); | |
| 145 return crypto::ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())); | |
| 146 } | |
| 147 | |
| 148 CryptoModule* NSSCertDatabase::GetPublicModule() const { | |
| 149 crypto::ScopedPK11Slot slot(GetPublicSlot()); | |
| 150 return CryptoModule::CreateFromHandle(slot.get()); | |
| 151 } | |
| 152 | |
| 153 CryptoModule* NSSCertDatabase::GetPrivateModule() const { | |
| 154 crypto::ScopedPK11Slot slot(GetPrivateSlot()); | |
| 155 return CryptoModule::CreateFromHandle(slot.get()); | |
| 156 } | |
| 157 | |
| 158 void NSSCertDatabase::ListModules(CryptoModuleList* modules, | |
| 159 bool need_rw) const { | |
| 160 modules->clear(); | |
| 161 | |
| 162 // The wincx arg is unused since we don't call PK11_SetIsLoggedInFunc. | |
| 163 crypto::ScopedPK11SlotList slot_list( | |
| 164 PK11_GetAllTokens(CKM_INVALID_MECHANISM, | |
| 165 need_rw ? PR_TRUE : PR_FALSE, // needRW | |
| 166 PR_TRUE, // loadCerts (unused) | |
| 167 NULL)); // wincx | |
| 168 if (!slot_list) { | |
| 169 LOG(ERROR) << "PK11_GetAllTokens failed: " << PORT_GetError(); | |
| 170 return; | |
| 171 } | |
| 172 | |
| 173 PK11SlotListElement* slot_element = PK11_GetFirstSafe(slot_list.get()); | |
| 174 while (slot_element) { | |
| 175 modules->push_back(CryptoModule::CreateFromHandle(slot_element->slot)); | |
| 176 slot_element = PK11_GetNextSafe(slot_list.get(), slot_element, | |
| 177 PR_FALSE); // restart | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 int NSSCertDatabase::ImportFromPKCS12( | |
| 182 CryptoModule* module, | |
| 183 const std::string& data, | |
| 184 const base::string16& password, | |
| 185 bool is_extractable, | |
| 186 net::CertificateList* imported_certs) { | |
| 187 DVLOG(1) << __func__ << " " | |
| 188 << PK11_GetModuleID(module->os_module_handle()) << ":" | |
| 189 << PK11_GetSlotID(module->os_module_handle()); | |
| 190 int result = psm::nsPKCS12Blob_Import(module->os_module_handle(), | |
| 191 data.data(), data.size(), | |
| 192 password, | |
| 193 is_extractable, | |
| 194 imported_certs); | |
| 195 if (result == net::OK) | |
| 196 NotifyObserversOfCertAdded(NULL); | |
| 197 | |
| 198 return result; | |
| 199 } | |
| 200 | |
| 201 int NSSCertDatabase::ExportToPKCS12( | |
| 202 const CertificateList& certs, | |
| 203 const base::string16& password, | |
| 204 std::string* output) const { | |
| 205 return psm::nsPKCS12Blob_Export(output, certs, password); | |
| 206 } | |
| 207 | |
| 208 X509Certificate* NSSCertDatabase::FindRootInList( | |
| 209 const CertificateList& certificates) const { | |
| 210 DCHECK_GT(certificates.size(), 0U); | |
| 211 | |
| 212 if (certificates.size() == 1) | |
| 213 return certificates[0].get(); | |
| 214 | |
| 215 X509Certificate* cert0 = certificates[0].get(); | |
| 216 X509Certificate* cert1 = certificates[1].get(); | |
| 217 X509Certificate* certn_2 = certificates[certificates.size() - 2].get(); | |
| 218 X509Certificate* certn_1 = certificates[certificates.size() - 1].get(); | |
| 219 | |
| 220 if (CERT_CompareName(&cert1->os_cert_handle()->issuer, | |
| 221 &cert0->os_cert_handle()->subject) == SECEqual) | |
| 222 return cert0; | |
| 223 if (CERT_CompareName(&certn_2->os_cert_handle()->issuer, | |
| 224 &certn_1->os_cert_handle()->subject) == SECEqual) | |
| 225 return certn_1; | |
| 226 | |
| 227 LOG(WARNING) << "certificate list is not a hierarchy"; | |
| 228 return cert0; | |
| 229 } | |
| 230 | |
| 231 bool NSSCertDatabase::ImportCACerts(const CertificateList& certificates, | |
| 232 TrustBits trust_bits, | |
| 233 ImportCertFailureList* not_imported) { | |
| 234 crypto::ScopedPK11Slot slot(GetPublicSlot()); | |
| 235 X509Certificate* root = FindRootInList(certificates); | |
| 236 bool success = psm::ImportCACerts( | |
| 237 slot.get(), certificates, root, trust_bits, not_imported); | |
| 238 if (success) | |
| 239 NotifyObserversOfCACertChanged(NULL); | |
| 240 | |
| 241 return success; | |
| 242 } | |
| 243 | |
| 244 bool NSSCertDatabase::ImportServerCert(const CertificateList& certificates, | |
| 245 TrustBits trust_bits, | |
| 246 ImportCertFailureList* not_imported) { | |
| 247 crypto::ScopedPK11Slot slot(GetPublicSlot()); | |
| 248 return psm::ImportServerCert( | |
| 249 slot.get(), certificates, trust_bits, not_imported); | |
| 250 } | |
| 251 | |
| 252 NSSCertDatabase::TrustBits NSSCertDatabase::GetCertTrust( | |
| 253 const X509Certificate* cert, | |
| 254 CertType type) const { | |
| 255 CERTCertTrust trust; | |
| 256 SECStatus srv = CERT_GetCertTrust(cert->os_cert_handle(), &trust); | |
| 257 if (srv != SECSuccess) { | |
| 258 LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); | |
| 259 return TRUST_DEFAULT; | |
| 260 } | |
| 261 // We define our own more "friendly" TrustBits, which means we aren't able to | |
| 262 // round-trip all possible NSS trust flag combinations. We try to map them in | |
| 263 // a sensible way. | |
| 264 switch (type) { | |
| 265 case CA_CERT: { | |
| 266 const unsigned kTrustedCA = CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA; | |
| 267 const unsigned kCAFlags = kTrustedCA | CERTDB_TERMINAL_RECORD; | |
| 268 | |
| 269 TrustBits trust_bits = TRUST_DEFAULT; | |
| 270 if ((trust.sslFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) | |
| 271 trust_bits |= DISTRUSTED_SSL; | |
| 272 else if (trust.sslFlags & kTrustedCA) | |
| 273 trust_bits |= TRUSTED_SSL; | |
| 274 | |
| 275 if ((trust.emailFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) | |
| 276 trust_bits |= DISTRUSTED_EMAIL; | |
| 277 else if (trust.emailFlags & kTrustedCA) | |
| 278 trust_bits |= TRUSTED_EMAIL; | |
| 279 | |
| 280 if ((trust.objectSigningFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) | |
| 281 trust_bits |= DISTRUSTED_OBJ_SIGN; | |
| 282 else if (trust.objectSigningFlags & kTrustedCA) | |
| 283 trust_bits |= TRUSTED_OBJ_SIGN; | |
| 284 | |
| 285 return trust_bits; | |
| 286 } | |
| 287 case SERVER_CERT: | |
| 288 if (trust.sslFlags & CERTDB_TERMINAL_RECORD) { | |
| 289 if (trust.sslFlags & CERTDB_TRUSTED) | |
| 290 return TRUSTED_SSL; | |
| 291 return DISTRUSTED_SSL; | |
| 292 } | |
| 293 return TRUST_DEFAULT; | |
| 294 default: | |
| 295 return TRUST_DEFAULT; | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 bool NSSCertDatabase::IsUntrusted(const X509Certificate* cert) const { | |
| 300 CERTCertTrust nsstrust; | |
| 301 SECStatus rv = CERT_GetCertTrust(cert->os_cert_handle(), &nsstrust); | |
| 302 if (rv != SECSuccess) { | |
| 303 LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); | |
| 304 return false; | |
| 305 } | |
| 306 | |
| 307 // The CERTCertTrust structure contains three trust records: | |
| 308 // sslFlags, emailFlags, and objectSigningFlags. The three | |
| 309 // trust records are independent of each other. | |
| 310 // | |
| 311 // If the CERTDB_TERMINAL_RECORD bit in a trust record is set, | |
| 312 // then that trust record is a terminal record. A terminal | |
| 313 // record is used for explicit trust and distrust of an | |
| 314 // end-entity or intermediate CA cert. | |
| 315 // | |
| 316 // In a terminal record, if neither CERTDB_TRUSTED_CA nor | |
| 317 // CERTDB_TRUSTED is set, then the terminal record means | |
| 318 // explicit distrust. On the other hand, if the terminal | |
| 319 // record has either CERTDB_TRUSTED_CA or CERTDB_TRUSTED bit | |
| 320 // set, then the terminal record means explicit trust. | |
| 321 // | |
| 322 // For a root CA, the trust record does not have | |
| 323 // the CERTDB_TERMINAL_RECORD bit set. | |
| 324 | |
| 325 static const unsigned int kTrusted = CERTDB_TRUSTED_CA | CERTDB_TRUSTED; | |
| 326 if ((nsstrust.sslFlags & CERTDB_TERMINAL_RECORD) != 0 && | |
| 327 (nsstrust.sslFlags & kTrusted) == 0) { | |
| 328 return true; | |
| 329 } | |
| 330 if ((nsstrust.emailFlags & CERTDB_TERMINAL_RECORD) != 0 && | |
| 331 (nsstrust.emailFlags & kTrusted) == 0) { | |
| 332 return true; | |
| 333 } | |
| 334 if ((nsstrust.objectSigningFlags & CERTDB_TERMINAL_RECORD) != 0 && | |
| 335 (nsstrust.objectSigningFlags & kTrusted) == 0) { | |
| 336 return true; | |
| 337 } | |
| 338 | |
| 339 // Self-signed certificates that don't have any trust bits set are untrusted. | |
| 340 // Other certificates that don't have any trust bits set may still be trusted | |
| 341 // if they chain up to a trust anchor. | |
| 342 if (CERT_CompareName(&cert->os_cert_handle()->issuer, | |
| 343 &cert->os_cert_handle()->subject) == SECEqual) { | |
| 344 return (nsstrust.sslFlags & kTrusted) == 0 && | |
| 345 (nsstrust.emailFlags & kTrusted) == 0 && | |
| 346 (nsstrust.objectSigningFlags & kTrusted) == 0; | |
| 347 } | |
| 348 | |
| 349 return false; | |
| 350 } | |
| 351 | |
| 352 bool NSSCertDatabase::SetCertTrust(const X509Certificate* cert, | |
| 353 CertType type, | |
| 354 TrustBits trust_bits) { | |
| 355 bool success = psm::SetCertTrust(cert, type, trust_bits); | |
| 356 if (success) | |
| 357 NotifyObserversOfCACertChanged(cert); | |
| 358 | |
| 359 return success; | |
| 360 } | |
| 361 | |
| 362 bool NSSCertDatabase::DeleteCertAndKey(X509Certificate* cert) { | |
| 363 if (!DeleteCertAndKeyImpl(cert)) | |
| 364 return false; | |
| 365 NotifyObserversOfCertRemoved(cert); | |
| 366 return true; | |
| 367 } | |
| 368 | |
| 369 void NSSCertDatabase::DeleteCertAndKeyAsync( | |
| 370 const scoped_refptr<X509Certificate>& cert, | |
| 371 const DeleteCertCallback& callback) { | |
| 372 base::PostTaskAndReplyWithResult( | |
| 373 GetSlowTaskRunner().get(), | |
| 374 FROM_HERE, | |
| 375 base::Bind(&NSSCertDatabase::DeleteCertAndKeyImpl, cert), | |
| 376 base::Bind(&NSSCertDatabase::NotifyCertRemovalAndCallBack, | |
| 377 weak_factory_.GetWeakPtr(), | |
| 378 cert, | |
| 379 callback)); | |
| 380 } | |
| 381 | |
| 382 bool NSSCertDatabase::IsReadOnly(const X509Certificate* cert) const { | |
| 383 PK11SlotInfo* slot = cert->os_cert_handle()->slot; | |
| 384 return slot && PK11_IsReadOnly(slot); | |
| 385 } | |
| 386 | |
| 387 bool NSSCertDatabase::IsHardwareBacked(const X509Certificate* cert) const { | |
| 388 PK11SlotInfo* slot = cert->os_cert_handle()->slot; | |
| 389 return slot && PK11_IsHW(slot); | |
| 390 } | |
| 391 | |
| 392 void NSSCertDatabase::AddObserver(Observer* observer) { | |
| 393 observer_list_->AddObserver(observer); | |
| 394 } | |
| 395 | |
| 396 void NSSCertDatabase::RemoveObserver(Observer* observer) { | |
| 397 observer_list_->RemoveObserver(observer); | |
| 398 } | |
| 399 | |
| 400 void NSSCertDatabase::SetSlowTaskRunnerForTest( | |
| 401 const scoped_refptr<base::TaskRunner>& task_runner) { | |
| 402 slow_task_runner_for_test_ = task_runner; | |
| 403 } | |
| 404 | |
| 405 // static | |
| 406 void NSSCertDatabase::ListCertsImpl(crypto::ScopedPK11Slot slot, | |
| 407 CertificateList* certs) { | |
| 408 certs->clear(); | |
| 409 | |
| 410 CERTCertList* cert_list = NULL; | |
| 411 if (slot) | |
| 412 cert_list = PK11_ListCertsInSlot(slot.get()); | |
| 413 else | |
| 414 cert_list = PK11_ListCerts(PK11CertListUnique, NULL); | |
| 415 | |
| 416 CERTCertListNode* node; | |
| 417 for (node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); | |
| 418 node = CERT_LIST_NEXT(node)) { | |
| 419 certs->push_back(X509Certificate::CreateFromHandle( | |
| 420 node->cert, X509Certificate::OSCertHandles())); | |
| 421 } | |
| 422 CERT_DestroyCertList(cert_list); | |
| 423 } | |
| 424 | |
| 425 scoped_refptr<base::TaskRunner> NSSCertDatabase::GetSlowTaskRunner() const { | |
| 426 if (slow_task_runner_for_test_.get()) | |
| 427 return slow_task_runner_for_test_; | |
| 428 return base::WorkerPool::GetTaskRunner(true /*task is slow*/); | |
| 429 } | |
| 430 | |
| 431 void NSSCertDatabase::NotifyCertRemovalAndCallBack( | |
| 432 scoped_refptr<X509Certificate> cert, | |
| 433 const DeleteCertCallback& callback, | |
| 434 bool success) { | |
| 435 if (success) | |
| 436 NotifyObserversOfCertRemoved(cert.get()); | |
| 437 callback.Run(success); | |
| 438 } | |
| 439 | |
| 440 void NSSCertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) { | |
| 441 observer_list_->Notify(FROM_HERE, &Observer::OnCertAdded, | |
| 442 make_scoped_refptr(cert)); | |
| 443 } | |
| 444 | |
| 445 void NSSCertDatabase::NotifyObserversOfCertRemoved( | |
| 446 const X509Certificate* cert) { | |
| 447 observer_list_->Notify(FROM_HERE, &Observer::OnCertRemoved, | |
| 448 make_scoped_refptr(cert)); | |
| 449 } | |
| 450 | |
| 451 void NSSCertDatabase::NotifyObserversOfCACertChanged( | |
| 452 const X509Certificate* cert) { | |
| 453 observer_list_->Notify(FROM_HERE, &Observer::OnCACertChanged, | |
| 454 make_scoped_refptr(cert)); | |
| 455 } | |
| 456 | |
| 457 // static | |
| 458 bool NSSCertDatabase::DeleteCertAndKeyImpl( | |
| 459 scoped_refptr<X509Certificate> cert) { | |
| 460 // For some reason, PK11_DeleteTokenCertAndKey only calls | |
| 461 // SEC_DeletePermCertificate if the private key is found. So, we check | |
| 462 // whether a private key exists before deciding which function to call to | |
| 463 // delete the cert. | |
| 464 SECKEYPrivateKey* privKey = | |
| 465 PK11_FindKeyByAnyCert(cert->os_cert_handle(), NULL); | |
| 466 if (privKey) { | |
| 467 SECKEY_DestroyPrivateKey(privKey); | |
| 468 if (PK11_DeleteTokenCertAndKey(cert->os_cert_handle(), NULL)) { | |
| 469 LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError(); | |
| 470 return false; | |
| 471 } | |
| 472 } else { | |
| 473 if (SEC_DeletePermCertificate(cert->os_cert_handle())) { | |
| 474 LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError(); | |
| 475 return false; | |
| 476 } | |
| 477 } | |
| 478 return true; | |
| 479 } | |
| 480 | |
| 481 } // namespace net | |
| OLD | NEW |