summaryrefslogtreecommitdiff
path: root/chromium/net/cert/cert_database_mac.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/cert/cert_database_mac.cc')
-rw-r--r--chromium/net/cert/cert_database_mac.cc172
1 files changed, 172 insertions, 0 deletions
diff --git a/chromium/net/cert/cert_database_mac.cc b/chromium/net/cert/cert_database_mac.cc
new file mode 100644
index 00000000000..76701963fd5
--- /dev/null
+++ b/chromium/net/cert/cert_database_mac.cc
@@ -0,0 +1,172 @@
+// 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 <Security/Security.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(message_loop->IsType(base::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, base::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