• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
Notifier(CertDatabase * cert_db,base::MessageLoop * 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(base::MessageLoopForUI::IsCurrent());
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.
~Notifier()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 
Shutdown()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:
Init()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
KeychainCallback(SecKeychainEvent keychain_event,SecKeychainCallbackInfo * info,void * context)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_->NotifyObserversOfCACertChanged(NULL);
107       break;
108   }
109 
110   return errSecSuccess;
111 }
112 
SetMessageLoopForKeychainEvents()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 
CertDatabase()121 CertDatabase::CertDatabase()
122     : observer_list_(new ObserverListThreadSafe<Observer>) {
123 }
124 
~CertDatabase()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 
CheckUserCert(X509Certificate * cert)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 
AddUserCert(X509Certificate * cert)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