- add sources.
[platform/framework/web/crosswalk.git] / src / net / cert / cert_database_mac.cc
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_->NotifyObserversOfCACertChanged(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