summaryrefslogtreecommitdiffstats
path: root/net/base/cert_database_mac.cc
blob: 2d3ef14616efafd12a85d492dbf727fb52ae2171 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/cert_database.h"

#include <Security/Security.h>

#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/message_loop.h"
#include "base/observer_list_threadsafe.h"
#include "base/process_util.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#include "crypto/mac_security_services_lock.h"
#include "net/base/net_errors.h"
#include "net/base/x509_certificate.h"

namespace net {

// Helper that observes events from the Keychain and forwards them to the
// given CertDatabase.
class CertDatabase::Notifier {
 public:
  // Creates a new Notifier that will forward Keychain events to |cert_db|.
  // |message_loop| must refer to a thread with an associated CFRunLoop - a
  // TYPE_UI thread. Events will be dispatched from this message loop.
  Notifier(CertDatabase* cert_db, MessageLoop* message_loop)
      : cert_db_(cert_db),
        registered_(false),
        called_shutdown_(false) {
    // Ensure an associated CFRunLoop.
    DCHECK(message_loop->IsType(MessageLoop::TYPE_UI));
    task_runner_ = message_loop->message_loop_proxy();
    task_runner_->PostTask(FROM_HERE,
                           base::Bind(&Notifier::Init,
                                      base::Unretained(this)));
  }

  // Should be called from the |task_runner_|'s thread. Use Shutdown()
  // to shutdown on arbitrary threads.
  ~Notifier() {
    DCHECK(called_shutdown_);
    // Only unregister from the same thread where registration was performed.
    if (registered_ && task_runner_->RunsTasksOnCurrentThread())
      SecKeychainRemoveCallback(&Notifier::KeychainCallback);
  }

  void Shutdown() {
    called_shutdown_ = true;
    if (!task_runner_->DeleteSoon(FROM_HERE, this)) {
      // If the task runner is no longer running, it's safe to just delete
      // the object, since no further events will or can be delivered by
      // Keychain Services.
      delete this;
    }
  }

 private:
  void Init() {
    SecKeychainEventMask event_mask =
        kSecKeychainListChangedMask | kSecTrustSettingsChangedEventMask;
    OSStatus status = SecKeychainAddCallback(&Notifier::KeychainCallback,
                                             event_mask, this);
    if (status == noErr)
      registered_ = true;
  }

  // SecKeychainCallback function that receives notifications from securityd
  // and forwards them to the |cert_db_|.
  static OSStatus KeychainCallback(SecKeychainEvent keychain_event,
                                   SecKeychainCallbackInfo* info,
                                   void* context);

  CertDatabase* const cert_db_;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
  bool registered_;
  bool called_shutdown_;
};

// static
OSStatus CertDatabase::Notifier::KeychainCallback(
    SecKeychainEvent keychain_event,
    SecKeychainCallbackInfo* info,
    void* context) {
  Notifier* that = reinterpret_cast<Notifier*>(context);

  if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) {
    NOTREACHED();
    return errSecWrongSecVersion;
  }

  if (info->pid == base::GetCurrentProcId()) {
    // Ignore events generated by the current process, as the assumption is
    // that they have already been handled. This may miss events that
    // originated as a result of spawning native dialogs that allow the user
    // to modify Keychain settings. However, err on the side of missing
    // events rather than sending too many events.
    return errSecSuccess;
  }

  switch (keychain_event) {
    case kSecKeychainListChangedEvent:
    case kSecTrustSettingsChangedEvent:
      that->cert_db_->NotifyObserversOfCertTrustChanged(NULL);
      break;
  }

  return errSecSuccess;
}

void CertDatabase::SetMessageLoopForKeychainEvents() {
  // Shutdown will take care to delete the notifier on the right thread.
  if (notifier_.get())
    notifier_.release()->Shutdown();

  notifier_.reset(new Notifier(this, MessageLoopForUI::current()));
}

CertDatabase::CertDatabase()
    : observer_list_(new ObserverListThreadSafe<Observer>) {
}

CertDatabase::~CertDatabase() {
  // Shutdown will take care to delete the notifier on the right thread.
  if (notifier_.get())
    notifier_.release()->Shutdown();
}

int CertDatabase::CheckUserCert(X509Certificate* cert) {
  if (!cert)
    return ERR_CERT_INVALID;
  if (cert->HasExpired())
    return ERR_CERT_DATE_INVALID;

  // Verify the Keychain already has the corresponding private key:
  SecIdentityRef identity = NULL;
  OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(),
                                                  &identity);
  if (err == errSecItemNotFound)
    return ERR_NO_PRIVATE_KEY_FOR_CERT;

  if (err != noErr || !identity) {
    // TODO(snej): Map the error code more intelligently.
    return ERR_CERT_INVALID;
  }

  CFRelease(identity);
  return OK;
}

int CertDatabase::AddUserCert(X509Certificate* cert) {
  OSStatus err;
  {
    base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL);
  }
  switch (err) {
    case noErr:
      CertDatabase::NotifyObserversOfCertAdded(cert);
      // Fall through.
    case errSecDuplicateItem:
      return OK;
    default:
      OSSTATUS_LOG(ERROR, err) << "CertDatabase failed to add cert to keychain";
      // TODO(snej): Map the error code more intelligently.
      return ERR_ADD_USER_CERT_FAILED;
  }
}

}  // namespace net