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. 4 5 #include "net/cert/cert_database.h" 6 7 #include <Security/Security.h> 8 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" 19 20 namespace net { 21 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))); 39 } 40 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); 48 } 49 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; 57 } 58 } 59 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; 68 } 69 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); 75 76 CertDatabase* const cert_db_; 77 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; 78 bool registered_; 79 bool called_shutdown_; 80 }; 81 82 // static 83 OSStatus CertDatabase::Notifier::KeychainCallback( 84 SecKeychainEvent keychain_event, 85 SecKeychainCallbackInfo* info, 86 void* context) { 87 Notifier* that = reinterpret_cast<Notifier*>(context); 88 89 if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) { 90 NOTREACHED(); 91 return errSecWrongSecVersion; 92 } 93 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; 101 } 102 103 switch (keychain_event) { 104 case kSecKeychainListChangedEvent: 105 case kSecTrustSettingsChangedEvent: 106 that->cert_db_->NotifyObserversOfCertTrustChanged(NULL); 107 break; 108 } 109 110 return errSecSuccess; 111 } 112 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(); 117 118 notifier_.reset(new Notifier(this, base::MessageLoopForUI::current())); 119 } 120 121 CertDatabase::CertDatabase() 122 : observer_list_(new ObserverListThreadSafe<Observer>) { 123 } 124 125 CertDatabase::~CertDatabase() { 126 // Shutdown will take care to delete the notifier on the right thread. 127 if (notifier_.get()) 128 notifier_.release()->Shutdown(); 129 } 130 131 int CertDatabase::CheckUserCert(X509Certificate* cert) { 132 if (!cert) 133 return ERR_CERT_INVALID; 134 if (cert->HasExpired()) 135 return ERR_CERT_DATE_INVALID; 136 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; 143 144 if (err != noErr || !identity) { 145 // TODO(snej): Map the error code more intelligently. 146 return ERR_CERT_INVALID; 147 } 148 149 CFRelease(identity); 150 return OK; 151 } 152 153 int CertDatabase::AddUserCert(X509Certificate* cert) { 154 OSStatus err; 155 { 156 base::AutoLock locked(crypto::GetMacSecurityServicesLock()); 157 err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); 158 } 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; 169 } 170 } 171 172 } // namespace net 173