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