• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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