| // 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/base/cert_database.h" |
| |
| #include <Security/Security.h> |
| |
| #include "base/logging.h" |
| #include "base/mac/mac_logging.h" |
| #include "base/message_loop.h" |
| #include "base/observer_list_threadsafe.h" |
| #include "base/process_util.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/base/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, MessageLoop* message_loop) |
| : cert_db_(cert_db), |
| registered_(false), |
| called_shutdown_(false) { |
| // Ensure an associated CFRunLoop. |
| DCHECK(message_loop->IsType(MessageLoop::TYPE_UI)); |
| task_runner_ = message_loop->message_loop_proxy(); |
| 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<base::SingleThreadTaskRunner> task_runner_; |
| bool registered_; |
| bool called_shutdown_; |
| }; |
| |
| // static |
| OSStatus CertDatabase::Notifier::KeychainCallback( |
| SecKeychainEvent keychain_event, |
| SecKeychainCallbackInfo* info, |
| void* context) { |
| Notifier* that = reinterpret_cast<Notifier*>(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_->NotifyObserversOfCertTrustChanged(NULL); |
| 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, MessageLoopForUI::current())); |
| } |
| |
| CertDatabase::CertDatabase() |
| : observer_list_(new ObserverListThreadSafe<Observer>) { |
| } |
| |
| 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 |