1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "KeychainSecret.h"
9 #include <Security/Security.h>
11 #include "mozilla/Logging.h"
13 // This is the implementation of KeychainSecret, an instantiation of OSKeyStore
14 // for OS X. It uses the system keychain, hence the name.
16 using namespace mozilla
;
18 LazyLogModule
gKeychainSecretLog("keychainsecret");
20 KeychainSecret::KeychainSecret() {}
22 KeychainSecret::~KeychainSecret() {}
24 ScopedCFType
<CFStringRef
> MozillaStringToCFString(const nsACString
& stringIn
) {
25 // https://developer.apple.com/documentation/corefoundation/1543419-cfstringcreatewithbytes
26 ScopedCFType
<CFStringRef
> stringOut(CFStringCreateWithBytes(
27 nullptr, reinterpret_cast<const UInt8
*>(stringIn
.BeginReading()),
28 stringIn
.Length(), kCFStringEncodingUTF8
, false));
32 nsresult
KeychainSecret::StoreSecret(const nsACString
& aSecret
,
33 const nsACString
& aLabel
) {
34 // This creates a CFDictionary of the form:
35 // { class: generic password,
36 // account: the given label,
37 // value: the given secret }
38 // "account" is the way we differentiate different secrets.
39 // By default, secrets stored by the application (Firefox) in this way are not
40 // accessible to other applications, so we shouldn't need to worry about
41 // unauthorized access or namespace collisions. This will be the case as long
42 // as we never set the kSecAttrAccessGroup attribute on the CFDictionary. The
43 // platform enforces this restriction using the application-identifier
44 // entitlement that each application bundle should have. See
45 // https://developer.apple.com/documentation/security/1401659-secitemadd?language=objc#discussion
47 // The keychain does not overwrite secrets by default (unlike other backends
48 // like libsecret and credential manager). To be consistent, we first delete
49 // any previously-stored secrets that use the given label.
50 nsresult rv
= DeleteSecret(aLabel
);
52 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
53 ("DeleteSecret before StoreSecret failed"));
56 const CFStringRef keys
[] = {kSecClass
, kSecAttrAccount
, kSecValueData
};
57 ScopedCFType
<CFStringRef
> label(MozillaStringToCFString(aLabel
));
59 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
60 ("MozillaStringToCFString failed"));
61 return NS_ERROR_FAILURE
;
63 ScopedCFType
<CFDataRef
> secret(CFDataCreate(
64 nullptr, reinterpret_cast<const UInt8
*>(aSecret
.BeginReading()),
67 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
, ("CFDataCreate failed"));
68 return NS_ERROR_FAILURE
;
70 const void* values
[] = {kSecClassGenericPassword
, label
.get(), secret
.get()};
71 static_assert(std::size(keys
) == std::size(values
),
72 "mismatched SecItemAdd key/value array sizes");
73 ScopedCFType
<CFDictionaryRef
> addDictionary(CFDictionaryCreate(
74 nullptr, (const void**)&keys
, (const void**)&values
, std::size(keys
),
75 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
));
76 // https://developer.apple.com/documentation/security/1401659-secitemadd
77 OSStatus osrv
= SecItemAdd(addDictionary
.get(), nullptr);
78 if (osrv
!= errSecSuccess
) {
79 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
80 ("SecItemAdd failed: %d", osrv
));
81 return NS_ERROR_FAILURE
;
86 nsresult
KeychainSecret::DeleteSecret(const nsACString
& aLabel
) {
87 // To delete a secret, we create a CFDictionary of the form:
88 // { class: generic password,
89 // account: the given label }
90 // and then call SecItemDelete.
91 const CFStringRef keys
[] = {kSecClass
, kSecAttrAccount
};
92 ScopedCFType
<CFStringRef
> label(MozillaStringToCFString(aLabel
));
94 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
95 ("MozillaStringToCFString failed"));
96 return NS_ERROR_FAILURE
;
98 const void* values
[] = {kSecClassGenericPassword
, label
.get()};
99 static_assert(std::size(keys
) == std::size(values
),
100 "mismatched SecItemDelete key/value array sizes");
101 ScopedCFType
<CFDictionaryRef
> deleteDictionary(CFDictionaryCreate(
102 nullptr, (const void**)&keys
, (const void**)&values
, std::size(keys
),
103 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
));
104 // https://developer.apple.com/documentation/security/1395547-secitemdelete
105 OSStatus rv
= SecItemDelete(deleteDictionary
.get());
106 if (rv
!= errSecSuccess
&& rv
!= errSecItemNotFound
) {
107 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
108 ("SecItemDelete failed: %d", rv
));
109 return NS_ERROR_FAILURE
;
114 nsresult
KeychainSecret::RetrieveSecret(const nsACString
& aLabel
,
115 /* out */ nsACString
& aSecret
) {
116 // To retrieve a secret, we create a CFDictionary of the form:
117 // { class: generic password,
118 // account: the given label,
119 // match limit: match one,
120 // return attributes: true,
121 // return data: true }
122 // This searches for and returns the attributes and data for the secret
123 // matching the given label. We then extract the data (i.e. the secret) and
125 const CFStringRef keys
[] = {kSecClass
, kSecAttrAccount
, kSecMatchLimit
,
126 kSecReturnAttributes
, kSecReturnData
};
127 ScopedCFType
<CFStringRef
> label(MozillaStringToCFString(aLabel
));
129 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
130 ("MozillaStringToCFString failed"));
131 return NS_ERROR_FAILURE
;
133 const void* values
[] = {kSecClassGenericPassword
, label
.get(),
134 kSecMatchLimitOne
, kCFBooleanTrue
, kCFBooleanTrue
};
135 static_assert(std::size(keys
) == std::size(values
),
136 "mismatched SecItemCopyMatching key/value array sizes");
137 ScopedCFType
<CFDictionaryRef
> searchDictionary(CFDictionaryCreate(
138 nullptr, (const void**)&keys
, (const void**)&values
, std::size(keys
),
139 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
));
141 // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
142 OSStatus rv
= SecItemCopyMatching(searchDictionary
.get(), &item
);
143 if (rv
!= errSecSuccess
) {
144 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
145 ("SecItemCopyMatching failed: %d", rv
));
146 return NS_ERROR_FAILURE
;
148 ScopedCFType
<CFDictionaryRef
> dictionary(
149 reinterpret_cast<CFDictionaryRef
>(item
));
150 CFDataRef secret
= reinterpret_cast<CFDataRef
>(
151 CFDictionaryGetValue(dictionary
.get(), kSecValueData
));
153 MOZ_LOG(gKeychainSecretLog
, LogLevel::Debug
,
154 ("CFDictionaryGetValue failed"));
155 return NS_ERROR_FAILURE
;
157 aSecret
.Assign(reinterpret_cast<const char*>(CFDataGetBytePtr(secret
)),
158 CFDataGetLength(secret
));