// Copyright 2012 The Chromium Authors // 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 #include "base/apple/bridging.h" #include "base/apple/foundation_util.h" #include "base/apple/scoped_cftyperef.h" using base::apple::CFToNSPtrCast; using base::apple::NSToCFOwnershipCast; namespace { enum KeychainAction { kKeychainActionCreate, kKeychainActionUpdate }; NSString* StringWithBytesAndLength(const char* bytes, UInt32 length) { return [[NSString alloc] initWithBytes:bytes length:length encoding:NSUTF8StringEncoding]; } // Creates a dictionary that can be used to query the keystore. base::apple::ScopedCFTypeRef MakeGenericPasswordQuery( UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName) { NSDictionary* query = @{ // Type of element is generic password. CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassGenericPassword), // Set the service name. CFToNSPtrCast(kSecAttrService) : StringWithBytesAndLength(serviceName, serviceNameLength), // Set the account name. CFToNSPtrCast(kSecAttrAccount) : StringWithBytesAndLength(accountName, accountNameLength), // Use the proper search constants, return only the data of the first match. CFToNSPtrCast(kSecMatchLimit) : CFToNSPtrCast(kSecMatchLimitOne), CFToNSPtrCast(kSecReturnData) : @YES, }; return base::apple::ScopedCFTypeRef( NSToCFOwnershipCast(query)); } // Creates a dictionary containing the data to save into the keychain. base::apple::ScopedCFTypeRef MakeKeychainData( UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName, UInt32 passwordLength, const void* passwordData, KeychainAction action) { NSData* password = [NSData dataWithBytes:passwordData length:passwordLength]; NSDictionary* keychain_data; if (action != kKeychainActionCreate) { // If this is not a creation, no structural information is needed, only the // password. keychain_data = @{ // Set the password. CFToNSPtrCast(kSecValueData) : password, }; } else { keychain_data = @{ // Set the password. CFToNSPtrCast(kSecValueData) : password, // Set the type of the data. CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassGenericPassword), // Only allow access when the device has been unlocked. CFToNSPtrCast(kSecAttrAccessible) : CFToNSPtrCast(kSecAttrAccessibleWhenUnlocked), // Set the service name. CFToNSPtrCast(kSecAttrService) : StringWithBytesAndLength(serviceName, serviceNameLength), // Set the account name. CFToNSPtrCast(kSecAttrAccount) : StringWithBytesAndLength(accountName, accountNameLength), }; } return base::apple::ScopedCFTypeRef( NSToCFOwnershipCast(keychain_data)); } } // namespace namespace crypto { AppleKeychain::AppleKeychain() = default; AppleKeychain::~AppleKeychain() = default; OSStatus AppleKeychain::ItemFreeContent(void* data) const { free(data); return noErr; } OSStatus AppleKeychain::AddGenericPassword( UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName, UInt32 passwordLength, const void* passwordData, AppleSecKeychainItemRef* itemRef) const { base::apple::ScopedCFTypeRef query = MakeGenericPasswordQuery(serviceNameLength, serviceName, accountNameLength, accountName); // Check that there is not already a password. OSStatus status = SecItemCopyMatching(query.get(), /*result=*/nullptr); if (status == errSecItemNotFound) { // A new entry must be created. base::apple::ScopedCFTypeRef keychain_data = MakeKeychainData(serviceNameLength, serviceName, accountNameLength, accountName, passwordLength, passwordData, kKeychainActionCreate); status = SecItemAdd(keychain_data.get(), /*result=*/nullptr); } else if (status == noErr) { // The entry must be updated. base::apple::ScopedCFTypeRef keychain_data = MakeKeychainData(serviceNameLength, serviceName, accountNameLength, accountName, passwordLength, passwordData, kKeychainActionUpdate); status = SecItemUpdate(query.get(), keychain_data.get()); } return status; } OSStatus AppleKeychain::FindGenericPassword( UInt32 serviceNameLength, const char* serviceName, UInt32 accountNameLength, const char* accountName, UInt32* passwordLength, void** passwordData, AppleSecKeychainItemRef* itemRef) const { DCHECK((passwordData && passwordLength) || (!passwordData && !passwordLength)); base::apple::ScopedCFTypeRef query = MakeGenericPasswordQuery(serviceNameLength, serviceName, accountNameLength, accountName); // Get the keychain item containing the password. base::apple::ScopedCFTypeRef result; OSStatus status = SecItemCopyMatching(query.get(), result.InitializeInto()); if (status != noErr) { if (passwordData) { *passwordData = nullptr; *passwordLength = 0; } return status; } if (passwordData) { CFDataRef data = base::apple::CFCast(result.get()); NSUInteger length = CFDataGetLength(data); *passwordData = malloc(length * sizeof(UInt8)); CFDataGetBytes(data, CFRangeMake(0, length), (UInt8*)*passwordData); *passwordLength = length; } return status; } } // namespace crypto