1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "crypto/apple_keychain.h"
7 #import <Foundation/Foundation.h>
9 #include "base/apple/bridging.h"
10 #include "base/apple/foundation_util.h"
11 #include "base/apple/scoped_cftyperef.h"
16 kKeychainActionCreate,
20 base::apple::ScopedCFTypeRef<CFStringRef> StringWithBytesAndLength(
23 return base::apple::ScopedCFTypeRef<CFStringRef>(
24 CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(bytes),
25 length, kCFStringEncodingUTF8,
26 /*isExternalRepresentation=*/false));
29 // Creates a dictionary that can be used to query the keystore.
30 base::apple::ScopedCFTypeRef<CFDictionaryRef> MakeGenericPasswordQuery(
31 UInt32 serviceNameLength,
32 const char* serviceName,
33 UInt32 accountNameLength,
34 const char* accountName) {
35 CFMutableDictionaryRef query =
36 CFDictionaryCreateMutable(nullptr, 5, &kCFTypeDictionaryKeyCallBacks,
37 &kCFTypeDictionaryValueCallBacks);
38 // Type of element is generic password.
39 CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
41 // Set the service name.
43 query, kSecAttrService,
44 StringWithBytesAndLength(serviceName, serviceNameLength));
46 // Set the account name.
48 query, kSecAttrAccount,
49 StringWithBytesAndLength(accountName, accountNameLength));
51 // Use the proper search constants, return only the data of the first match.
52 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitOne);
53 CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
55 return base::apple::ScopedCFTypeRef<CFDictionaryRef>(query);
58 // Creates a dictionary containing the data to save into the keychain.
59 base::apple::ScopedCFTypeRef<CFDictionaryRef> MakeKeychainData(
60 UInt32 serviceNameLength,
61 const char* serviceName,
62 UInt32 accountNameLength,
63 const char* accountName,
64 UInt32 passwordLength,
65 const void* passwordData,
66 KeychainAction action) {
67 CFMutableDictionaryRef keychain_data =
68 CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
69 &kCFTypeDictionaryValueCallBacks);
72 NSData* password = [NSData dataWithBytes:passwordData length:passwordLength];
73 CFDictionarySetValue(keychain_data, kSecValueData,
74 base::apple::NSToCFPtrCast(password));
76 // If this is not a creation, no structural information is needed.
77 if (action != kKeychainActionCreate) {
78 return base::apple::ScopedCFTypeRef<CFDictionaryRef>(keychain_data);
81 // Set the type of the data.
82 CFDictionarySetValue(keychain_data, kSecClass, kSecClassGenericPassword);
84 // Only allow access when the device has been unlocked.
85 CFDictionarySetValue(keychain_data,
87 kSecAttrAccessibleWhenUnlocked);
89 // Set the service name.
91 keychain_data, kSecAttrService,
92 StringWithBytesAndLength(serviceName, serviceNameLength));
94 // Set the account name.
96 keychain_data, kSecAttrAccount,
97 StringWithBytesAndLength(accountName, accountNameLength));
99 return base::apple::ScopedCFTypeRef<CFDictionaryRef>(keychain_data);
106 AppleKeychain::AppleKeychain() = default;
108 AppleKeychain::~AppleKeychain() = default;
110 OSStatus AppleKeychain::ItemFreeContent(void* data) const {
115 OSStatus AppleKeychain::AddGenericPassword(
116 UInt32 serviceNameLength,
117 const char* serviceName,
118 UInt32 accountNameLength,
119 const char* accountName,
120 UInt32 passwordLength,
121 const void* passwordData,
122 AppleSecKeychainItemRef* itemRef) const {
123 base::apple::ScopedCFTypeRef<CFDictionaryRef> query =
124 MakeGenericPasswordQuery(serviceNameLength, serviceName,
125 accountNameLength, accountName);
126 // Check that there is not already a password.
127 OSStatus status = SecItemCopyMatching(query, /*result=*/nullptr);
128 if (status == errSecItemNotFound) {
129 // A new entry must be created.
130 base::apple::ScopedCFTypeRef<CFDictionaryRef> keychain_data =
131 MakeKeychainData(serviceNameLength, serviceName, accountNameLength,
132 accountName, passwordLength, passwordData,
133 kKeychainActionCreate);
134 status = SecItemAdd(keychain_data, /*result=*/nullptr);
135 } else if (status == noErr) {
136 // The entry must be updated.
137 base::apple::ScopedCFTypeRef<CFDictionaryRef> keychain_data =
138 MakeKeychainData(serviceNameLength, serviceName, accountNameLength,
139 accountName, passwordLength, passwordData,
140 kKeychainActionUpdate);
141 status = SecItemUpdate(query, keychain_data);
147 OSStatus AppleKeychain::FindGenericPassword(
148 UInt32 serviceNameLength,
149 const char* serviceName,
150 UInt32 accountNameLength,
151 const char* accountName,
152 UInt32* passwordLength,
154 AppleSecKeychainItemRef* itemRef) const {
155 DCHECK((passwordData && passwordLength) ||
156 (!passwordData && !passwordLength));
157 base::apple::ScopedCFTypeRef<CFDictionaryRef> query =
158 MakeGenericPasswordQuery(serviceNameLength, serviceName,
159 accountNameLength, accountName);
161 // Get the keychain item containing the password.
162 base::apple::ScopedCFTypeRef<CFTypeRef> result;
163 OSStatus status = SecItemCopyMatching(query, result.InitializeInto());
165 if (status != noErr) {
167 *passwordData = nullptr;
174 CFDataRef data = base::apple::CFCast<CFDataRef>(result);
175 NSUInteger length = CFDataGetLength(data);
176 *passwordData = malloc(length * sizeof(UInt8));
177 CFDataGetBytes(data, CFRangeMake(0, length), (UInt8*)*passwordData);
178 *passwordLength = length;
183 } // namespace crypto