// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "crypto/apple_keychain.h" #import <Foundation/Foundation.h> #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_nsobject.h" namespace { enum KeychainAction { kKeychainActionCreate, kKeychainActionUpdate }; // Creates a dictionary that can be used to query the keystore. // Ownership follows the Create rule. CFDictionaryRef CreateGenericPasswordQuery(UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName) { CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // Type of element is generic password. CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); // Set the service name. base::scoped_nsobject<NSString> service_name_ns( [[NSString alloc] initWithBytes:serviceName length:serviceNameLength encoding:NSUTF8StringEncoding]); CFDictionarySetValue(query, kSecAttrService, base::mac::NSToCFCast(service_name_ns)); // Set the account name. base::scoped_nsobject<NSString> account_name_ns( [[NSString alloc] initWithBytes:accountName length:accountNameLength encoding:NSUTF8StringEncoding]); CFDictionarySetValue(query, kSecAttrAccount, base::mac::NSToCFCast(account_name_ns)); // Use the proper search constants, return only the data of the first match. CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitOne); CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue); return query; } // Creates a dictionary conatining the data to save into the keychain. // Ownership follows the Create rule. CFDictionaryRef CreateKeychainData(UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName, UInt32 passwordLength, const void* passwordData, KeychainAction action) { CFMutableDictionaryRef keychain_data = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // Set the password. NSData* password = [NSData dataWithBytes:passwordData length:passwordLength]; CFDictionarySetValue(keychain_data, kSecValueData, base::mac::NSToCFCast(password)); // If this is not a creation, no structural information is needed. if (action != kKeychainActionCreate) return keychain_data; // Set the type of the data. CFDictionarySetValue(keychain_data, kSecClass, kSecClassGenericPassword); // Only allow access when the device has been unlocked. CFDictionarySetValue(keychain_data, kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked); // Set the service name. base::scoped_nsobject<NSString> service_name_ns( [[NSString alloc] initWithBytes:serviceName length:serviceNameLength encoding:NSUTF8StringEncoding]); CFDictionarySetValue(keychain_data, kSecAttrService, base::mac::NSToCFCast(service_name_ns)); // Set the account name. base::scoped_nsobject<NSString> account_name_ns( [[NSString alloc] initWithBytes:accountName length:accountNameLength encoding:NSUTF8StringEncoding]); CFDictionarySetValue(keychain_data, kSecAttrAccount, base::mac::NSToCFCast(account_name_ns)); return keychain_data; } } // namespace namespace crypto { AppleKeychain::AppleKeychain() {} AppleKeychain::~AppleKeychain() {} OSStatus AppleKeychain::ItemFreeContent(SecKeychainAttributeList* attrList, void* data) const { free(data); return noErr; } OSStatus AppleKeychain::AddGenericPassword(SecKeychainRef keychain, UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName, UInt32 passwordLength, const void* passwordData, SecKeychainItemRef* itemRef) const { base::ScopedCFTypeRef<CFDictionaryRef> query(CreateGenericPasswordQuery( serviceNameLength, serviceName, accountNameLength, accountName)); // Check that there is not already a password. OSStatus status = SecItemCopyMatching(query, NULL); if (status == errSecItemNotFound) { // A new entry must be created. base::ScopedCFTypeRef<CFDictionaryRef> keychain_data( CreateKeychainData(serviceNameLength, serviceName, accountNameLength, accountName, passwordLength, passwordData, kKeychainActionCreate)); status = SecItemAdd(keychain_data, NULL); } else if (status == noErr) { // The entry must be updated. base::ScopedCFTypeRef<CFDictionaryRef> keychain_data( CreateKeychainData(serviceNameLength, serviceName, accountNameLength, accountName, passwordLength, passwordData, kKeychainActionUpdate)); status = SecItemUpdate(query, keychain_data); } return status; } OSStatus AppleKeychain::FindGenericPassword(CFTypeRef keychainOrArray, UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName, UInt32* passwordLength, void** passwordData, SecKeychainItemRef* itemRef) const { DCHECK((passwordData && passwordLength) || (!passwordData && !passwordLength)); base::ScopedCFTypeRef<CFDictionaryRef> query(CreateGenericPasswordQuery( serviceNameLength, serviceName, accountNameLength, accountName)); // Get the keychain item containing the password. CFTypeRef resultRef = NULL; OSStatus status = SecItemCopyMatching(query, &resultRef); base::ScopedCFTypeRef<CFTypeRef> result(resultRef); if (status != noErr) { if (passwordData) { *passwordData = NULL; *passwordLength = 0; } return status; } if (passwordData) { CFDataRef data = base::mac::CFCast<CFDataRef>(result); NSUInteger length = CFDataGetLength(data); *passwordData = malloc(length * sizeof(UInt8)); CFDataGetBytes(data, CFRangeMake(0, length), (UInt8*)*passwordData); *passwordLength = length; } return status; } } // namespace crypto