// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/cert/cert_database.h" #include #include "base/location.h" #include "base/logging.h" #include "base/mac/mac_logging.h" #include "base/message_loop/message_loop.h" #include "base/observer_list_threadsafe.h" #include "base/process/process_handle.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" #include "crypto/mac_security_services_lock.h" #include "net/base/net_errors.h" #include "net/cert/x509_certificate.h" namespace net { // Helper that observes events from the Keychain and forwards them to the // given CertDatabase. class CertDatabase::Notifier { public: // Creates a new Notifier that will forward Keychain events to |cert_db|. // |message_loop| must refer to a thread with an associated CFRunLoop - a // TYPE_UI thread. Events will be dispatched from this message loop. Notifier(CertDatabase* cert_db, base::MessageLoop* message_loop) : cert_db_(cert_db), registered_(false), called_shutdown_(false) { // Ensure an associated CFRunLoop. DCHECK(base::MessageLoopForUI::IsCurrent()); task_runner_ = message_loop->task_runner(); task_runner_->PostTask(FROM_HERE, base::Bind(&Notifier::Init, base::Unretained(this))); } // Should be called from the |task_runner_|'s thread. Use Shutdown() // to shutdown on arbitrary threads. ~Notifier() { DCHECK(called_shutdown_); // Only unregister from the same thread where registration was performed. if (registered_ && task_runner_->RunsTasksOnCurrentThread()) SecKeychainRemoveCallback(&Notifier::KeychainCallback); } void Shutdown() { called_shutdown_ = true; if (!task_runner_->DeleteSoon(FROM_HERE, this)) { // If the task runner is no longer running, it's safe to just delete // the object, since no further events will or can be delivered by // Keychain Services. delete this; } } private: void Init() { SecKeychainEventMask event_mask = kSecKeychainListChangedMask | kSecTrustSettingsChangedEventMask; OSStatus status = SecKeychainAddCallback(&Notifier::KeychainCallback, event_mask, this); if (status == noErr) registered_ = true; } // SecKeychainCallback function that receives notifications from securityd // and forwards them to the |cert_db_|. static OSStatus KeychainCallback(SecKeychainEvent keychain_event, SecKeychainCallbackInfo* info, void* context); CertDatabase* const cert_db_; scoped_refptr task_runner_; bool registered_; bool called_shutdown_; }; // static OSStatus CertDatabase::Notifier::KeychainCallback( SecKeychainEvent keychain_event, SecKeychainCallbackInfo* info, void* context) { Notifier* that = reinterpret_cast(context); if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) { NOTREACHED(); return errSecWrongSecVersion; } if (info->pid == base::GetCurrentProcId()) { // Ignore events generated by the current process, as the assumption is // that they have already been handled. This may miss events that // originated as a result of spawning native dialogs that allow the user // to modify Keychain settings. However, err on the side of missing // events rather than sending too many events. return errSecSuccess; } switch (keychain_event) { case kSecKeychainListChangedEvent: case kSecTrustSettingsChangedEvent: that->cert_db_->NotifyObserversOfCACertChanged(NULL); break; default: break; } return errSecSuccess; } void CertDatabase::SetMessageLoopForKeychainEvents() { // Shutdown will take care to delete the notifier on the right thread. if (notifier_.get()) notifier_.release()->Shutdown(); notifier_.reset(new Notifier(this, base::MessageLoopForUI::current())); } CertDatabase::CertDatabase() : observer_list_(new base::ObserverListThreadSafe) { } CertDatabase::~CertDatabase() { // Shutdown will take care to delete the notifier on the right thread. if (notifier_.get()) notifier_.release()->Shutdown(); } int CertDatabase::CheckUserCert(X509Certificate* cert) { if (!cert) return ERR_CERT_INVALID; if (cert->HasExpired()) return ERR_CERT_DATE_INVALID; // Verify the Keychain already has the corresponding private key: SecIdentityRef identity = NULL; OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(), &identity); if (err == errSecItemNotFound) return ERR_NO_PRIVATE_KEY_FOR_CERT; if (err != noErr || !identity) { // TODO(snej): Map the error code more intelligently. return ERR_CERT_INVALID; } CFRelease(identity); return OK; } int CertDatabase::AddUserCert(X509Certificate* cert) { OSStatus err; { base::AutoLock locked(crypto::GetMacSecurityServicesLock()); err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); } switch (err) { case noErr: CertDatabase::NotifyObserversOfCertAdded(cert); // Fall through. case errSecDuplicateItem: return OK; default: OSSTATUS_LOG(ERROR, err) << "CertDatabase failed to add cert to keychain"; // TODO(snej): Map the error code more intelligently. return ERR_ADD_USER_CERT_FAILED; } } } // namespace net