1 // Copyright 2012 The Chromium Authors
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/check.h"
10 #include "base/functional/bind.h"
11 #include "base/location.h"
12 #include "base/mac/mac_logging.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/notreached.h"
15 #include "base/process/process_handle.h"
16 #include "base/synchronization/lock.h"
17 #include "base/task/current_thread.h"
18 #include "base/task/single_thread_task_runner.h"
19 #include "crypto/mac_security_services_lock.h"
20 #include "net/base/net_errors.h"
21 #include "net/cert/x509_certificate.h"
22
23 namespace net {
24
25 // Helper that observes events from the Keychain and forwards them to the
26 // given CertDatabase.
27 class CertDatabase::Notifier {
28 public:
29 // Creates a new Notifier that will forward Keychain events to |cert_db|.
30 // |message_loop| must refer to a thread with an associated CFRunLoop - a
31 // TYPE_UI thread. Events will be dispatched from this message loop.
Notifier(CertDatabase * cert_db,scoped_refptr<base::SingleThreadTaskRunner> task_runner)32 Notifier(CertDatabase* cert_db,
33 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
34 : cert_db_(cert_db), task_runner_(std::move(task_runner)) {
35 // Ensure an associated CFRunLoop.
36 DCHECK(base::CurrentUIThread::IsSet());
37 DCHECK(task_runner_->BelongsToCurrentThread());
38 task_runner_->PostTask(
39 FROM_HERE, base::BindOnce(&Notifier::Init, base::Unretained(this)));
40 }
41
42 // Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
43 // Removal of its use is tracked in https://crbug.com/1348251 but deprecation
44 // warnings are disabled in the meanwhile.
45 #pragma clang diagnostic push
46 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
47
48 // Should be called from the |task_runner_|'s sequence. Use Shutdown()
49 // to shutdown on arbitrary sequence.
~Notifier()50 ~Notifier() {
51 DCHECK(called_shutdown_);
52 // Only unregister from the same sequence where registration was performed.
53 if (registered_ && task_runner_->RunsTasksInCurrentSequence())
54 SecKeychainRemoveCallback(&Notifier::KeychainCallback);
55 }
56
57 #pragma clang diagnostic pop
58
Shutdown()59 void Shutdown() {
60 called_shutdown_ = true;
61 if (!task_runner_->DeleteSoon(FROM_HERE, this)) {
62 // If the task runner is no longer running, it's safe to just delete
63 // the object, since no further events will or can be delivered by
64 // Keychain Services.
65 delete this;
66 }
67 }
68
69 // Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
70 // Removal of its use is tracked in https://crbug.com/1348251 but deprecation
71 // warnings are disabled in the meanwhile.
72 #pragma clang diagnostic push
73 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
74
75 private:
Init()76 void Init() {
77 SecKeychainEventMask event_mask =
78 kSecKeychainListChangedMask | kSecTrustSettingsChangedEventMask;
79 OSStatus status = SecKeychainAddCallback(&Notifier::KeychainCallback,
80 event_mask, this);
81 if (status == noErr)
82 registered_ = true;
83 }
84
85 #pragma clang diagnostic pop
86
87 // SecKeychainCallback function that receives notifications from securityd
88 // and forwards them to the |cert_db_|.
89 static OSStatus KeychainCallback(SecKeychainEvent keychain_event,
90 SecKeychainCallbackInfo* info,
91 void* context);
92
93 const raw_ptr<CertDatabase> cert_db_;
94 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
95 bool registered_ = false;
96 bool called_shutdown_ = false;
97 };
98
99 // static
KeychainCallback(SecKeychainEvent keychain_event,SecKeychainCallbackInfo * info,void * context)100 OSStatus CertDatabase::Notifier::KeychainCallback(
101 SecKeychainEvent keychain_event,
102 SecKeychainCallbackInfo* info,
103 void* context) {
104 Notifier* that = reinterpret_cast<Notifier*>(context);
105
106 if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) {
107 NOTREACHED();
108 return errSecWrongSecVersion;
109 }
110
111 if (info->pid == base::GetCurrentProcId()) {
112 // Ignore events generated by the current process, as the assumption is
113 // that they have already been handled. This may miss events that
114 // originated as a result of spawning native dialogs that allow the user
115 // to modify Keychain settings. However, err on the side of missing
116 // events rather than sending too many events.
117 return errSecSuccess;
118 }
119
120 switch (keychain_event) {
121 case kSecKeychainListChangedEvent:
122 case kSecTrustSettingsChangedEvent:
123 that->cert_db_->NotifyObserversCertDBChanged();
124 break;
125
126 default:
127 break;
128 }
129
130 return errSecSuccess;
131 }
132
StartListeningForKeychainEvents()133 void CertDatabase::StartListeningForKeychainEvents() {
134 ReleaseNotifier();
135 notifier_ =
136 new Notifier(this, base::SingleThreadTaskRunner::GetCurrentDefault());
137 }
138
ReleaseNotifier()139 void CertDatabase::ReleaseNotifier() {
140 // Shutdown will take care to delete the notifier on the right thread.
141 if (notifier_) {
142 notifier_->Shutdown();
143 notifier_ = nullptr;
144 }
145 }
146
147 } // namespace net
148