• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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