tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

KeychainSecret.cpp (8122B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 *
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "KeychainSecret.h"
      8 
      9 #include <Security/Security.h>
     10 
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/glean/SecurityManagerSslMetrics.h"
     13 #include "nsPrintfCString.h"
     14 
     15 // This is the implementation of KeychainSecret, an instantiation of OSKeyStore
     16 // for OS X. It uses the system keychain, hence the name.
     17 
     18 using namespace mozilla;
     19 
     20 LazyLogModule gKeychainSecretLog("keychainsecret");
     21 
     22 KeychainSecret::KeychainSecret() {}
     23 
     24 KeychainSecret::~KeychainSecret() {}
     25 
     26 ScopedCFType<CFStringRef> MozillaStringToCFString(const nsACString& stringIn) {
     27  // https://developer.apple.com/documentation/corefoundation/1543419-cfstringcreatewithbytes
     28  ScopedCFType<CFStringRef> stringOut(CFStringCreateWithBytes(
     29      nullptr, reinterpret_cast<const UInt8*>(stringIn.BeginReading()),
     30      stringIn.Length(), kCFStringEncodingUTF8, false));
     31  return stringOut;
     32 }
     33 
     34 nsresult KeychainSecret::StoreSecret(const nsACString& aSecret,
     35                                     const nsACString& aLabel) {
     36  // This creates a CFDictionary of the form:
     37  // { class: generic password,
     38  //   account: the given label,
     39  //   value: the given secret }
     40  // "account" is the way we differentiate different secrets.
     41  // By default, secrets stored by the application (Firefox) in this way are not
     42  // accessible to other applications, so we shouldn't need to worry about
     43  // unauthorized access or namespace collisions. This will be the case as long
     44  // as we never set the kSecAttrAccessGroup attribute on the CFDictionary. The
     45  // platform enforces this restriction using the application-identifier
     46  // entitlement that each application bundle should have. See
     47  // https://developer.apple.com/documentation/security/1401659-secitemadd?language=objc#discussion
     48 
     49  // The keychain does not overwrite secrets by default (unlike other backends
     50  // like libsecret and credential manager). To be consistent, we first delete
     51  // any previously-stored secrets that use the given label.
     52  nsresult rv = DeleteSecret(aLabel);
     53  if (NS_FAILED(rv)) {
     54    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
     55            ("DeleteSecret before StoreSecret failed"));
     56    return rv;
     57  }
     58  const CFStringRef keys[] = {kSecClass, kSecAttrAccount, kSecValueData};
     59  ScopedCFType<CFStringRef> label(MozillaStringToCFString(aLabel));
     60  if (!label) {
     61    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
     62            ("MozillaStringToCFString failed"));
     63    return NS_ERROR_FAILURE;
     64  }
     65  ScopedCFType<CFDataRef> secret(CFDataCreate(
     66      nullptr, reinterpret_cast<const UInt8*>(aSecret.BeginReading()),
     67      aSecret.Length()));
     68  if (!secret) {
     69    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug, ("CFDataCreate failed"));
     70    return NS_ERROR_FAILURE;
     71  }
     72  const void* values[] = {kSecClassGenericPassword, label.get(), secret.get()};
     73  static_assert(std::size(keys) == std::size(values),
     74                "mismatched SecItemAdd key/value array sizes");
     75  ScopedCFType<CFDictionaryRef> addDictionary(CFDictionaryCreate(
     76      nullptr, (const void**)&keys, (const void**)&values, std::size(keys),
     77      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
     78  // https://developer.apple.com/documentation/security/1401659-secitemadd
     79  OSStatus osrv = SecItemAdd(addDictionary.get(), nullptr);
     80  if (osrv != errSecSuccess) {
     81    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
     82            ("SecItemAdd failed: %d", osrv));
     83    nsPrintfCString osrvString("%d", osrv);
     84    mozilla::glean::oskeystore::ReturnCodesExtra extra = {};
     85    extra.function = Some("StoreSecret_SecItemAdd"_ns);
     86    extra.result = Some(osrvString);
     87    glean::oskeystore::return_codes.Record(Some(extra));
     88    return NS_ERROR_FAILURE;
     89  }
     90  return NS_OK;
     91 }
     92 
     93 nsresult KeychainSecret::DeleteSecret(const nsACString& aLabel) {
     94  // To delete a secret, we create a CFDictionary of the form:
     95  // { class: generic password,
     96  //   account: the given label }
     97  // and then call SecItemDelete.
     98  const CFStringRef keys[] = {kSecClass, kSecAttrAccount};
     99  ScopedCFType<CFStringRef> label(MozillaStringToCFString(aLabel));
    100  if (!label) {
    101    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
    102            ("MozillaStringToCFString failed"));
    103    return NS_ERROR_FAILURE;
    104  }
    105  const void* values[] = {kSecClassGenericPassword, label.get()};
    106  static_assert(std::size(keys) == std::size(values),
    107                "mismatched SecItemDelete key/value array sizes");
    108  ScopedCFType<CFDictionaryRef> deleteDictionary(CFDictionaryCreate(
    109      nullptr, (const void**)&keys, (const void**)&values, std::size(keys),
    110      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
    111  // https://developer.apple.com/documentation/security/1395547-secitemdelete
    112  OSStatus osrv = SecItemDelete(deleteDictionary.get());
    113  if (osrv != errSecSuccess && osrv != errSecItemNotFound) {
    114    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
    115            ("SecItemDelete failed: %d", osrv));
    116    mozilla::glean::oskeystore::ReturnCodesExtra extra = {};
    117    extra.function = Some("DeleteSecret_SecItemDelete"_ns);
    118    nsPrintfCString osrvString("%d", osrv);
    119    extra.result = Some(osrvString);
    120    glean::oskeystore::return_codes.Record(Some(extra));
    121    return NS_ERROR_FAILURE;
    122  }
    123  return NS_OK;
    124 }
    125 
    126 nsresult KeychainSecret::RetrieveSecret(const nsACString& aLabel,
    127                                        /* out */ nsACString& aSecret) {
    128  // To retrieve a secret, we create a CFDictionary of the form:
    129  // { class: generic password,
    130  //   account: the given label,
    131  //   match limit: match one,
    132  //   return attributes: true,
    133  //   return data: true }
    134  // This searches for and returns the attributes and data for the secret
    135  // matching the given label. We then extract the data (i.e. the secret) and
    136  // return it.
    137  const CFStringRef keys[] = {kSecClass, kSecAttrAccount, kSecMatchLimit,
    138                              kSecReturnAttributes, kSecReturnData};
    139  ScopedCFType<CFStringRef> label(MozillaStringToCFString(aLabel));
    140  if (!label) {
    141    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
    142            ("MozillaStringToCFString failed"));
    143    return NS_ERROR_FAILURE;
    144  }
    145  const void* values[] = {kSecClassGenericPassword, label.get(),
    146                          kSecMatchLimitOne, kCFBooleanTrue, kCFBooleanTrue};
    147  static_assert(std::size(keys) == std::size(values),
    148                "mismatched SecItemCopyMatching key/value array sizes");
    149  ScopedCFType<CFDictionaryRef> searchDictionary(CFDictionaryCreate(
    150      nullptr, (const void**)&keys, (const void**)&values, std::size(keys),
    151      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
    152  CFTypeRef item;
    153  // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
    154  OSStatus osrv = SecItemCopyMatching(searchDictionary.get(), &item);
    155  if (osrv == errSecItemNotFound) {
    156    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
    157            ("Key not found in key store"));
    158    return NS_ERROR_NOT_AVAILABLE;
    159  }
    160  if (osrv != errSecSuccess) {
    161    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
    162            ("SecItemCopyMatching failed: %d", osrv));
    163    mozilla::glean::oskeystore::ReturnCodesExtra extra = {};
    164    extra.function = Some("RetrieveSecret_SecItemCopyMatching"_ns);
    165    nsPrintfCString osrvString("%d", osrv);
    166    extra.result = Some(osrvString);
    167    glean::oskeystore::return_codes.Record(Some(extra));
    168    return NS_ERROR_FAILURE;
    169  }
    170  ScopedCFType<CFDictionaryRef> dictionary(
    171      reinterpret_cast<CFDictionaryRef>(item));
    172  CFDataRef secret = reinterpret_cast<CFDataRef>(
    173      CFDictionaryGetValue(dictionary.get(), kSecValueData));
    174  if (!secret) {
    175    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
    176            ("CFDictionaryGetValue failed"));
    177    return NS_ERROR_FAILURE;
    178  }
    179  aSecret.Assign(reinterpret_cast<const char*>(CFDataGetBytePtr(secret)),
    180                 CFDataGetLength(secret));
    181  return NS_OK;
    182 }