blob: 74cf129ce1fd02790f390f051347bb1d60d192d9 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "crypto/apple_keychain.h"
6
7#import <Foundation/Foundation.h>
8
9#include "base/mac/foundation_util.h"
10#include "base/mac/scoped_cftyperef.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010011#include "base/mac/scoped_nsobject.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000012
13namespace {
14
15enum KeychainAction {
16 kKeychainActionCreate,
17 kKeychainActionUpdate
18};
19
20// Creates a dictionary that can be used to query the keystore.
21// Ownership follows the Create rule.
22CFDictionaryRef CreateGenericPasswordQuery(UInt32 serviceNameLength,
23 const char* serviceName,
24 UInt32 accountNameLength,
25 const char* accountName) {
26 CFMutableDictionaryRef query =
27 CFDictionaryCreateMutable(NULL,
28 5,
29 &kCFTypeDictionaryKeyCallBacks,
30 &kCFTypeDictionaryValueCallBacks);
31 // Type of element is generic password.
32 CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword);
33
34 // Set the service name.
Ben Murdocheb525c52013-07-10 11:40:50 +010035 base::scoped_nsobject<NSString> service_name_ns(
Torne (Richard Coles)58218062012-11-14 11:43:16 +000036 [[NSString alloc] initWithBytes:serviceName
37 length:serviceNameLength
38 encoding:NSUTF8StringEncoding]);
39 CFDictionarySetValue(query, kSecAttrService,
40 base::mac::NSToCFCast(service_name_ns));
41
42 // Set the account name.
Ben Murdocheb525c52013-07-10 11:40:50 +010043 base::scoped_nsobject<NSString> account_name_ns(
Torne (Richard Coles)58218062012-11-14 11:43:16 +000044 [[NSString alloc] initWithBytes:accountName
45 length:accountNameLength
46 encoding:NSUTF8StringEncoding]);
47 CFDictionarySetValue(query, kSecAttrAccount,
48 base::mac::NSToCFCast(account_name_ns));
49
50 // Use the proper search constants, return only the data of the first match.
51 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitOne);
52 CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
53 return query;
54}
55
56// Creates a dictionary conatining the data to save into the keychain.
57// Ownership follows the Create rule.
58CFDictionaryRef CreateKeychainData(UInt32 serviceNameLength,
59 const char* serviceName,
60 UInt32 accountNameLength,
61 const char* accountName,
62 UInt32 passwordLength,
63 const void* passwordData,
64 KeychainAction action) {
65 CFMutableDictionaryRef keychain_data =
66 CFDictionaryCreateMutable(NULL,
67 0,
68 &kCFTypeDictionaryKeyCallBacks,
69 &kCFTypeDictionaryValueCallBacks);
70
71 // Set the password.
72 NSData* password = [NSData dataWithBytes:passwordData length:passwordLength];
73 CFDictionarySetValue(keychain_data, kSecValueData,
74 base::mac::NSToCFCast(password));
75
76 // If this is not a creation, no structural information is needed.
77 if (action != kKeychainActionCreate)
78 return keychain_data;
79
80 // Set the type of the data.
81 CFDictionarySetValue(keychain_data, kSecClass, kSecClassGenericPassword);
82
83 // Only allow access when the device has been unlocked.
84 CFDictionarySetValue(keychain_data,
85 kSecAttrAccessible,
86 kSecAttrAccessibleWhenUnlocked);
87
88 // Set the service name.
Ben Murdocheb525c52013-07-10 11:40:50 +010089 base::scoped_nsobject<NSString> service_name_ns(
Torne (Richard Coles)58218062012-11-14 11:43:16 +000090 [[NSString alloc] initWithBytes:serviceName
91 length:serviceNameLength
92 encoding:NSUTF8StringEncoding]);
93 CFDictionarySetValue(keychain_data, kSecAttrService,
94 base::mac::NSToCFCast(service_name_ns));
95
96 // Set the account name.
Ben Murdocheb525c52013-07-10 11:40:50 +010097 base::scoped_nsobject<NSString> account_name_ns(
Torne (Richard Coles)58218062012-11-14 11:43:16 +000098 [[NSString alloc] initWithBytes:accountName
99 length:accountNameLength
100 encoding:NSUTF8StringEncoding]);
101 CFDictionarySetValue(keychain_data, kSecAttrAccount,
102 base::mac::NSToCFCast(account_name_ns));
103
104 return keychain_data;
105}
106
107} // namespace
108
109namespace crypto {
110
111AppleKeychain::AppleKeychain() {}
112
113AppleKeychain::~AppleKeychain() {}
114
115OSStatus AppleKeychain::ItemFreeContent(SecKeychainAttributeList* attrList,
116 void* data) const {
117 free(data);
118 return noErr;
119}
120
121OSStatus AppleKeychain::AddGenericPassword(SecKeychainRef keychain,
122 UInt32 serviceNameLength,
123 const char* serviceName,
124 UInt32 accountNameLength,
125 const char* accountName,
126 UInt32 passwordLength,
127 const void* passwordData,
128 SecKeychainItemRef* itemRef) const {
Ben Murdocheb525c52013-07-10 11:40:50 +0100129 base::ScopedCFTypeRef<CFDictionaryRef> query(CreateGenericPasswordQuery(
130 serviceNameLength, serviceName, accountNameLength, accountName));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000131 // Check that there is not already a password.
132 OSStatus status = SecItemCopyMatching(query, NULL);
133 if (status == errSecItemNotFound) {
134 // A new entry must be created.
Ben Murdocheb525c52013-07-10 11:40:50 +0100135 base::ScopedCFTypeRef<CFDictionaryRef> keychain_data(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000136 CreateKeychainData(serviceNameLength,
137 serviceName,
138 accountNameLength,
139 accountName,
140 passwordLength,
141 passwordData,
142 kKeychainActionCreate));
143 status = SecItemAdd(keychain_data, NULL);
144 } else if (status == noErr) {
145 // The entry must be updated.
Ben Murdocheb525c52013-07-10 11:40:50 +0100146 base::ScopedCFTypeRef<CFDictionaryRef> keychain_data(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000147 CreateKeychainData(serviceNameLength,
148 serviceName,
149 accountNameLength,
150 accountName,
151 passwordLength,
152 passwordData,
153 kKeychainActionUpdate));
154 status = SecItemUpdate(query, keychain_data);
155 }
156
157 return status;
158}
159
160OSStatus AppleKeychain::FindGenericPassword(CFTypeRef keychainOrArray,
161 UInt32 serviceNameLength,
162 const char* serviceName,
163 UInt32 accountNameLength,
164 const char* accountName,
165 UInt32* passwordLength,
166 void** passwordData,
167 SecKeychainItemRef* itemRef) const {
168 DCHECK((passwordData && passwordLength) ||
169 (!passwordData && !passwordLength));
Ben Murdocheb525c52013-07-10 11:40:50 +0100170 base::ScopedCFTypeRef<CFDictionaryRef> query(CreateGenericPasswordQuery(
171 serviceNameLength, serviceName, accountNameLength, accountName));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000172
173 // Get the keychain item containing the password.
174 CFTypeRef resultRef = NULL;
175 OSStatus status = SecItemCopyMatching(query, &resultRef);
Ben Murdocheb525c52013-07-10 11:40:50 +0100176 base::ScopedCFTypeRef<CFTypeRef> result(resultRef);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000177
178 if (status != noErr) {
179 if (passwordData) {
180 *passwordData = NULL;
181 *passwordLength = 0;
182 }
183 return status;
184 }
185
186 if (passwordData) {
187 CFDataRef data = base::mac::CFCast<CFDataRef>(result);
188 NSUInteger length = CFDataGetLength(data);
189 *passwordData = malloc(length * sizeof(UInt8));
190 CFDataGetBytes(data, CFRangeMake(0, length), (UInt8*)*passwordData);
191 *passwordLength = length;
192 }
193 return status;
194}
195
196} // namespace crypto