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. 4 5#include "crypto/apple_keychain.h" 6 7#import <Foundation/Foundation.h> 8 9#include "base/apple/bridging.h" 10#include "base/apple/foundation_util.h" 11#include "base/apple/scoped_cftyperef.h" 12 13using base::apple::CFToNSPtrCast; 14using base::apple::NSToCFOwnershipCast; 15 16namespace { 17 18enum KeychainAction { 19 kKeychainActionCreate, 20 kKeychainActionUpdate 21}; 22 23NSString* StringWithBytesAndLength(const char* bytes, UInt32 length) { 24 return [[NSString alloc] initWithBytes:bytes 25 length:length 26 encoding:NSUTF8StringEncoding]; 27} 28 29// Creates a dictionary that can be used to query the keystore. 30base::apple::ScopedCFTypeRef<CFDictionaryRef> MakeGenericPasswordQuery( 31 UInt32 serviceNameLength, 32 const char* serviceName, 33 UInt32 accountNameLength, 34 const char* accountName) { 35 NSDictionary* query = @{ 36 // Type of element is generic password. 37 CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassGenericPassword), 38 39 // Set the service name. 40 CFToNSPtrCast(kSecAttrService) : 41 StringWithBytesAndLength(serviceName, serviceNameLength), 42 43 // Set the account name. 44 CFToNSPtrCast(kSecAttrAccount) : 45 StringWithBytesAndLength(accountName, accountNameLength), 46 47 // Use the proper search constants, return only the data of the first match. 48 CFToNSPtrCast(kSecMatchLimit) : CFToNSPtrCast(kSecMatchLimitOne), 49 CFToNSPtrCast(kSecReturnData) : @YES, 50 }; 51 return base::apple::ScopedCFTypeRef<CFDictionaryRef>( 52 NSToCFOwnershipCast(query)); 53} 54 55// Creates a dictionary containing the data to save into the keychain. 56base::apple::ScopedCFTypeRef<CFDictionaryRef> MakeKeychainData( 57 UInt32 serviceNameLength, 58 const char* serviceName, 59 UInt32 accountNameLength, 60 const char* accountName, 61 UInt32 passwordLength, 62 const void* passwordData, 63 KeychainAction action) { 64 NSData* password = [NSData dataWithBytes:passwordData length:passwordLength]; 65 66 NSDictionary* keychain_data; 67 68 if (action != kKeychainActionCreate) { 69 // If this is not a creation, no structural information is needed, only the 70 // password. 71 keychain_data = @{ 72 // Set the password. 73 CFToNSPtrCast(kSecValueData) : password, 74 }; 75 } else { 76 keychain_data = @{ 77 // Set the password. 78 CFToNSPtrCast(kSecValueData) : password, 79 80 // Set the type of the data. 81 CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassGenericPassword), 82 83 // Only allow access when the device has been unlocked. 84 CFToNSPtrCast(kSecAttrAccessible) : 85 CFToNSPtrCast(kSecAttrAccessibleWhenUnlocked), 86 87 // Set the service name. 88 CFToNSPtrCast(kSecAttrService) : 89 StringWithBytesAndLength(serviceName, serviceNameLength), 90 91 // Set the account name. 92 CFToNSPtrCast(kSecAttrAccount) : 93 StringWithBytesAndLength(accountName, accountNameLength), 94 }; 95 } 96 97 return base::apple::ScopedCFTypeRef<CFDictionaryRef>( 98 NSToCFOwnershipCast(keychain_data)); 99} 100 101} // namespace 102 103namespace crypto { 104 105AppleKeychain::AppleKeychain() = default; 106 107AppleKeychain::~AppleKeychain() = default; 108 109OSStatus AppleKeychain::ItemFreeContent(void* data) const { 110 free(data); 111 return noErr; 112} 113 114OSStatus AppleKeychain::AddGenericPassword( 115 UInt32 serviceNameLength, 116 const char* serviceName, 117 UInt32 accountNameLength, 118 const char* accountName, 119 UInt32 passwordLength, 120 const void* passwordData, 121 AppleSecKeychainItemRef* itemRef) const { 122 base::apple::ScopedCFTypeRef<CFDictionaryRef> query = 123 MakeGenericPasswordQuery(serviceNameLength, serviceName, 124 accountNameLength, accountName); 125 // Check that there is not already a password. 126 OSStatus status = SecItemCopyMatching(query.get(), /*result=*/nullptr); 127 if (status == errSecItemNotFound) { 128 // A new entry must be created. 129 base::apple::ScopedCFTypeRef<CFDictionaryRef> keychain_data = 130 MakeKeychainData(serviceNameLength, serviceName, accountNameLength, 131 accountName, passwordLength, passwordData, 132 kKeychainActionCreate); 133 status = SecItemAdd(keychain_data.get(), /*result=*/nullptr); 134 } else if (status == noErr) { 135 // The entry must be updated. 136 base::apple::ScopedCFTypeRef<CFDictionaryRef> keychain_data = 137 MakeKeychainData(serviceNameLength, serviceName, accountNameLength, 138 accountName, passwordLength, passwordData, 139 kKeychainActionUpdate); 140 status = SecItemUpdate(query.get(), keychain_data.get()); 141 } 142 143 return status; 144} 145 146OSStatus AppleKeychain::FindGenericPassword( 147 UInt32 serviceNameLength, 148 const char* serviceName, 149 UInt32 accountNameLength, 150 const char* accountName, 151 UInt32* passwordLength, 152 void** passwordData, 153 AppleSecKeychainItemRef* itemRef) const { 154 DCHECK((passwordData && passwordLength) || 155 (!passwordData && !passwordLength)); 156 base::apple::ScopedCFTypeRef<CFDictionaryRef> query = 157 MakeGenericPasswordQuery(serviceNameLength, serviceName, 158 accountNameLength, accountName); 159 160 // Get the keychain item containing the password. 161 base::apple::ScopedCFTypeRef<CFTypeRef> result; 162 OSStatus status = SecItemCopyMatching(query.get(), result.InitializeInto()); 163 164 if (status != noErr) { 165 if (passwordData) { 166 *passwordData = nullptr; 167 *passwordLength = 0; 168 } 169 return status; 170 } 171 172 if (passwordData) { 173 CFDataRef data = base::apple::CFCast<CFDataRef>(result.get()); 174 NSUInteger length = CFDataGetLength(data); 175 *passwordData = malloc(length * sizeof(UInt8)); 176 CFDataGetBytes(data, CFRangeMake(0, length), (UInt8*)*passwordData); 177 *passwordLength = length; 178 } 179 return status; 180} 181 182} // namespace crypto 183