Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / net / cert / cert_database_mac.cc
blob037558cf919b849e8df16841b17b496ecc098396
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.
5 #include "net/cert/cert_database.h"
7 #include <Security/Security.h>
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/mac/mac_logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/observer_list_threadsafe.h"
14 #include "base/process/process_handle.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/synchronization/lock.h"
17 #include "crypto/mac_security_services_lock.h"
18 #include "net/base/net_errors.h"
19 #include "net/cert/x509_certificate.h"
21 namespace net {
23 // Helper that observes events from the Keychain and forwards them to the
24 // given CertDatabase.
25 class CertDatabase::Notifier {
26 public:
27 // Creates a new Notifier that will forward Keychain events to |cert_db|.
28 // |message_loop| must refer to a thread with an associated CFRunLoop - a
29 // TYPE_UI thread. Events will be dispatched from this message loop.
30 Notifier(CertDatabase* cert_db, base::MessageLoop* message_loop)
31 : cert_db_(cert_db),
32 registered_(false),
33 called_shutdown_(false) {
34 // Ensure an associated CFRunLoop.
35 DCHECK(base::MessageLoopForUI::IsCurrent());
36 task_runner_ = message_loop->task_runner();
37 task_runner_->PostTask(FROM_HERE,
38 base::Bind(&Notifier::Init,
39 base::Unretained(this)));
42 // Should be called from the |task_runner_|'s thread. Use Shutdown()
43 // to shutdown on arbitrary threads.
44 ~Notifier() {
45 DCHECK(called_shutdown_);
46 // Only unregister from the same thread where registration was performed.
47 if (registered_ && task_runner_->RunsTasksOnCurrentThread())
48 SecKeychainRemoveCallback(&Notifier::KeychainCallback);
51 void Shutdown() {
52 called_shutdown_ = true;
53 if (!task_runner_->DeleteSoon(FROM_HERE, this)) {
54 // If the task runner is no longer running, it's safe to just delete
55 // the object, since no further events will or can be delivered by
56 // Keychain Services.
57 delete this;
61 private:
62 void Init() {
63 SecKeychainEventMask event_mask =
64 kSecKeychainListChangedMask | kSecTrustSettingsChangedEventMask;
65 OSStatus status = SecKeychainAddCallback(&Notifier::KeychainCallback,
66 event_mask, this);
67 if (status == noErr)
68 registered_ = true;
71 // SecKeychainCallback function that receives notifications from securityd
72 // and forwards them to the |cert_db_|.
73 static OSStatus KeychainCallback(SecKeychainEvent keychain_event,
74 SecKeychainCallbackInfo* info,
75 void* context);
77 CertDatabase* const cert_db_;
78 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
79 bool registered_;
80 bool called_shutdown_;
83 // static
84 OSStatus CertDatabase::Notifier::KeychainCallback(
85 SecKeychainEvent keychain_event,
86 SecKeychainCallbackInfo* info,
87 void* context) {
88 Notifier* that = reinterpret_cast<Notifier*>(context);
90 if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) {
91 NOTREACHED();
92 return errSecWrongSecVersion;
95 if (info->pid == base::GetCurrentProcId()) {
96 // Ignore events generated by the current process, as the assumption is
97 // that they have already been handled. This may miss events that
98 // originated as a result of spawning native dialogs that allow the user
99 // to modify Keychain settings. However, err on the side of missing
100 // events rather than sending too many events.
101 return errSecSuccess;
104 switch (keychain_event) {
105 case kSecKeychainListChangedEvent:
106 case kSecTrustSettingsChangedEvent:
107 that->cert_db_->NotifyObserversOfCACertChanged(NULL);
108 break;
111 return errSecSuccess;
114 void CertDatabase::SetMessageLoopForKeychainEvents() {
115 // Shutdown will take care to delete the notifier on the right thread.
116 if (notifier_.get())
117 notifier_.release()->Shutdown();
119 notifier_.reset(new Notifier(this, base::MessageLoopForUI::current()));
122 CertDatabase::CertDatabase()
123 : observer_list_(new base::ObserverListThreadSafe<Observer>) {
126 CertDatabase::~CertDatabase() {
127 // Shutdown will take care to delete the notifier on the right thread.
128 if (notifier_.get())
129 notifier_.release()->Shutdown();
132 int CertDatabase::CheckUserCert(X509Certificate* cert) {
133 if (!cert)
134 return ERR_CERT_INVALID;
135 if (cert->HasExpired())
136 return ERR_CERT_DATE_INVALID;
138 // Verify the Keychain already has the corresponding private key:
139 SecIdentityRef identity = NULL;
140 OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(),
141 &identity);
142 if (err == errSecItemNotFound)
143 return ERR_NO_PRIVATE_KEY_FOR_CERT;
145 if (err != noErr || !identity) {
146 // TODO(snej): Map the error code more intelligently.
147 return ERR_CERT_INVALID;
150 CFRelease(identity);
151 return OK;
154 int CertDatabase::AddUserCert(X509Certificate* cert) {
155 OSStatus err;
157 base::AutoLock locked(crypto::GetMacSecurityServicesLock());
158 err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL);
160 switch (err) {
161 case noErr:
162 CertDatabase::NotifyObserversOfCertAdded(cert);
163 // Fall through.
164 case errSecDuplicateItem:
165 return OK;
166 default:
167 OSSTATUS_LOG(ERROR, err) << "CertDatabase failed to add cert to keychain";
168 // TODO(snej): Map the error code more intelligently.
169 return ERR_ADD_USER_CERT_FAILED;
173 } // namespace net