Home | History | Annotate | Download | only in cert
      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_->NotifyObserversOfCertTrustChanged(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
    173