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