summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/KeychainSecret.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/KeychainSecret.cpp')
-rw-r--r--security/manager/ssl/KeychainSecret.cpp186
1 files changed, 186 insertions, 0 deletions
diff --git a/security/manager/ssl/KeychainSecret.cpp b/security/manager/ssl/KeychainSecret.cpp
new file mode 100644
index 0000000000..376e2f14c3
--- /dev/null
+++ b/security/manager/ssl/KeychainSecret.cpp
@@ -0,0 +1,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;
+}