1// 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" 11#include "base/mac/scoped_nsobject.h" 12 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. 35 base::scoped_nsobject<NSString> service_name_ns( 36 [[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. 43 base::scoped_nsobject<NSString> account_name_ns( 44 [[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. 89 base::scoped_nsobject<NSString> service_name_ns( 90 [[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. 97 base::scoped_nsobject<NSString> account_name_ns( 98 [[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 { 129 base::ScopedCFTypeRef<CFDictionaryRef> query(CreateGenericPasswordQuery( 130 serviceNameLength, serviceName, accountNameLength, accountName)); 131 // 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. 135 base::ScopedCFTypeRef<CFDictionaryRef> keychain_data( 136 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. 146 base::ScopedCFTypeRef<CFDictionaryRef> keychain_data( 147 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)); 170 base::ScopedCFTypeRef<CFDictionaryRef> query(CreateGenericPasswordQuery( 171 serviceNameLength, serviceName, accountNameLength, accountName)); 172 173 // Get the keychain item containing the password. 174 CFTypeRef resultRef = NULL; 175 OSStatus status = SecItemCopyMatching(query, &resultRef); 176 base::ScopedCFTypeRef<CFTypeRef> result(resultRef); 177 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 197