summaryrefslogtreecommitdiff
path: root/chromium/net/cert/cert_database_mac.cc
blob: e210e05e2cf7443be12a9e2a24cc4f51ccd4654b (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
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert/cert_database.h"

#include <Security/Security.h>

#include "base/bind.h"
#include "base/check.h"
#include "base/location.h"
#include "base/mac/mac_logging.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/process/process_handle.h"
#include "base/synchronization/lock.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "crypto/mac_security_services_lock.h"
#include "net/base/net_errors.h"
#include "net/cert/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,
           scoped_refptr<base::SingleThreadTaskRunner> task_runner)
      : cert_db_(cert_db), task_runner_(std::move(task_runner)) {
    // Ensure an associated CFRunLoop.
    DCHECK(base::CurrentUIThread::IsSet());
    DCHECK(task_runner_->BelongsToCurrentThread());
    task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&Notifier::Init, base::Unretained(this)));
  }

// Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
// Removal of its use is tracked in https://crbug.com/1348251 but deprecation
// warnings are disabled in the meanwhile.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

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

#pragma clang diagnostic pop

  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;
    }
  }

// Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
// Removal of its use is tracked in https://crbug.com/1348251 but deprecation
// warnings are disabled in the meanwhile.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

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

#pragma clang diagnostic pop

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

  const raw_ptr<CertDatabase> cert_db_;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
  bool registered_ = false;
  bool called_shutdown_ = false;
};

// 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_->NotifyObserversCertDBChanged();
      break;

    default:
      break;
  }

  return errSecSuccess;
}

void CertDatabase::StartListeningForKeychainEvents() {
  ReleaseNotifier();
  notifier_ = new Notifier(this, base::ThreadTaskRunnerHandle::Get());
}

void CertDatabase::ReleaseNotifier() {
  // Shutdown will take care to delete the notifier on the right thread.
  if (notifier_) {
    notifier_->Shutdown();
    notifier_ = nullptr;
  }
}

}  // namespace net