DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (27bbc1fba015)

VCS Links

Line Code
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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "KeychainSecret.h"

#include <Security/Security.h>

#include "mozilla/Logging.h"

// This is the implementation of KeychainSecret, an instantiation of OSKeyStore
// for OS X. It uses the system keychain, hence the name.

using namespace mozilla;

LazyLogModule gKeychainSecretLog("keychainsecret");

KeychainSecret::KeychainSecret() {}

KeychainSecret::~KeychainSecret() {}

nsresult KeychainSecret::Lock() {
  // https://developer.apple.com/documentation/security/1402180-seckeychainlock
  // Passing `nullptr` locks the default keychain.
  OSStatus rv = SecKeychainLock(nullptr);
  if (rv != errSecSuccess) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("SecKeychainLock failed: %d", rv));
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult KeychainSecret::Unlock() {
  // https://developer.apple.com/documentation/security/1400341-seckeychainunlock
  // This attempts to unlock the default keychain. Using `false` indicates we
  // aren't passing in a password (if the keychain is locked, a dialog will
  // appear for the user).
  OSStatus rv = SecKeychainUnlock(nullptr, 0, nullptr, false);
  if (rv != errSecSuccess) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("SecKeychainUnlock failed: %d", rv));
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

ScopedCFType<CFStringRef> MozillaStringToCFString(const nsACString& stringIn) {
  // https://developer.apple.com/documentation/corefoundation/1543419-cfstringcreatewithbytes
  ScopedCFType<CFStringRef> stringOut(CFStringCreateWithBytes(
      nullptr, reinterpret_cast<const UInt8*>(stringIn.BeginReading()),
      stringIn.Length(), kCFStringEncodingUTF8, false));
  return stringOut;
}

nsresult KeychainSecret::StoreSecret(const nsACString& aSecret,
                                     const nsACString& aLabel) {
  // This creates a CFDictionary of the form:
  // { class: generic password,
  //   account: the given label,
  //   value: the given secret }
  // "account" is the way we differentiate different secrets.
  // By default, secrets stored by the application (Firefox) in this way are not
  // accessible to other applications, so we shouldn't need to worry about
  // unauthorized access or namespace collisions. This will be the case as long
  // as we never set the kSecAttrAccessGroup attribute on the CFDictionary. The
  // platform enforces this restriction using the application-identifier
  // entitlement that each application bundle should have. See
  // https://developer.apple.com/documentation/security/1401659-secitemadd?language=objc#discussion

  // The keychain does not overwrite secrets by default (unlike other backends
  // like libsecret and credential manager). To be consistent, we first delete
  // any previously-stored secrets that use the given label.
  nsresult rv = DeleteSecret(aLabel);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("DeleteSecret before StoreSecret failed"));
    return rv;
  }
  const CFStringRef keys[] = {kSecClass, kSecAttrAccount, kSecValueData};
  ScopedCFType<CFStringRef> label(MozillaStringToCFString(aLabel));
  if (!label) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("MozillaStringToCFString failed"));
    return NS_ERROR_FAILURE;
  }
  ScopedCFType<CFDataRef> secret(CFDataCreate(
      nullptr, reinterpret_cast<const UInt8*>(aSecret.BeginReading()),
      aSecret.Length()));
  if (!secret) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug, ("CFDataCreate failed"));
    return NS_ERROR_FAILURE;
  }
  const void* values[] = {kSecClassGenericPassword, label.get(), secret.get()};
  static_assert(ArrayLength(keys) == ArrayLength(values),
                "mismatched SecItemAdd key/value array sizes");
  ScopedCFType<CFDictionaryRef> addDictionary(CFDictionaryCreate(
      nullptr, (const void**)&keys, (const void**)&values, ArrayLength(keys),
      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
  // https://developer.apple.com/documentation/security/1401659-secitemadd
  OSStatus osrv = SecItemAdd(addDictionary.get(), nullptr);
  if (osrv != errSecSuccess) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("SecItemAdd failed: %d", osrv));
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult KeychainSecret::DeleteSecret(const nsACString& aLabel) {
  // To delete a secret, we create a CFDictionary of the form:
  // { class: generic password,
  //   account: the given label }
  // and then call SecItemDelete.
  const CFStringRef keys[] = {kSecClass, kSecAttrAccount};
  ScopedCFType<CFStringRef> label(MozillaStringToCFString(aLabel));
  if (!label) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("MozillaStringToCFString failed"));
    return NS_ERROR_FAILURE;
  }
  const void* values[] = {kSecClassGenericPassword, label.get()};
  static_assert(ArrayLength(keys) == ArrayLength(values),
                "mismatched SecItemDelete key/value array sizes");
  ScopedCFType<CFDictionaryRef> deleteDictionary(CFDictionaryCreate(
      nullptr, (const void**)&keys, (const void**)&values, ArrayLength(keys),
      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
  // https://developer.apple.com/documentation/security/1395547-secitemdelete
  OSStatus rv = SecItemDelete(deleteDictionary.get());
  if (rv != errSecSuccess && rv != errSecItemNotFound) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("SecItemDelete failed: %d", rv));
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult KeychainSecret::RetrieveSecret(const nsACString& aLabel,
                                        /* out */ nsACString& aSecret) {
  // To retrieve a secret, we create a CFDictionary of the form:
  // { class: generic password,
  //   account: the given label,
  //   match limit: match one,
  //   return attributes: true,
  //   return data: true }
  // This searches for and returns the attributes and data for the secret
  // matching the given label. We then extract the data (i.e. the secret) and
  // return it.
  const CFStringRef keys[] = {kSecClass, kSecAttrAccount, kSecMatchLimit,
                              kSecReturnAttributes, kSecReturnData};
  ScopedCFType<CFStringRef> label(MozillaStringToCFString(aLabel));
  if (!label) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("MozillaStringToCFString failed"));
    return NS_ERROR_FAILURE;
  }
  const void* values[] = {kSecClassGenericPassword, label.get(),
                          kSecMatchLimitOne, kCFBooleanTrue, kCFBooleanTrue};
  static_assert(ArrayLength(keys) == ArrayLength(values),
                "mismatched SecItemCopyMatching key/value array sizes");
  ScopedCFType<CFDictionaryRef> searchDictionary(CFDictionaryCreate(
      nullptr, (const void**)&keys, (const void**)&values, ArrayLength(keys),
      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
  CFTypeRef item;
  // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
  OSStatus rv = SecItemCopyMatching(searchDictionary.get(), &item);
  if (rv != errSecSuccess) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("SecItemCopyMatching failed: %d", rv));
    return NS_ERROR_FAILURE;
  }
  ScopedCFType<CFDictionaryRef> dictionary(
      reinterpret_cast<CFDictionaryRef>(item));
  CFDataRef secret = reinterpret_cast<CFDataRef>(
      CFDictionaryGetValue(dictionary.get(), kSecValueData));
  if (!secret) {
    MOZ_LOG(gKeychainSecretLog, LogLevel::Debug,
            ("CFDictionaryGetValue failed"));
    return NS_ERROR_FAILURE;
  }
  aSecret.Assign(reinterpret_cast<const char*>(CFDataGetBytePtr(secret)),
                 CFDataGetLength(secret));
  return NS_OK;
}