]> git.saurik.com Git - apple/security.git/blob - Security/libsecurity_keychain/lib/SecIdentity.cpp
Security-57031.10.10.tar.gz
[apple/security.git] / Security / libsecurity_keychain / lib / SecIdentity.cpp
1 /*
2 * Copyright (c) 2002-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <Security/SecIdentity.h>
25 #include <Security/SecIdentityPriv.h>
26 #include <Security/SecKeychainItemPriv.h>
27 #include <Security/SecItem.h>
28 #include <Security/SecIdentityPriv.h>
29
30 #include "SecBridge.h"
31 #include <security_keychain/Certificate.h>
32 #include <security_keychain/Identity.h>
33 #include <security_keychain/KeyItem.h>
34 #include <security_keychain/KCCursor.h>
35 #include <security_cdsa_utilities/Schema.h>
36 #include <security_utilities/simpleprefs.h>
37 #include <sys/param.h>
38 #include <syslog.h>
39
40
41 /* private function declarations */
42 OSStatus
43 SecIdentityFindPreferenceItemWithNameAndKeyUsage(
44 CFTypeRef keychainOrArray,
45 CFStringRef name,
46 int32_t keyUsage,
47 SecKeychainItemRef *itemRef);
48
49 OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage(
50 CFTypeRef keychainOrArray,
51 CFStringRef name,
52 int32_t keyUsage);
53
54
55 CSSM_KEYUSE ConvertArrayToKeyUsage(CFArrayRef usage)
56 {
57 CFIndex count = 0;
58 CSSM_KEYUSE result = (CSSM_KEYUSE) 0;
59
60 if ((NULL == usage) || (0 == (count = CFArrayGetCount(usage))))
61 {
62 return result;
63 }
64
65 for (CFIndex iCnt = 0; iCnt < count; iCnt++)
66 {
67 CFStringRef keyUsageStr = NULL;
68 keyUsageStr = (CFStringRef)CFArrayGetValueAtIndex(usage,iCnt);
69 if (NULL != keyUsageStr)
70 {
71 if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanEncrypt, keyUsageStr, 0))
72 {
73 result |= CSSM_KEYUSE_ENCRYPT;
74 }
75 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanDecrypt, keyUsageStr, 0))
76 {
77 result |= CSSM_KEYUSE_DECRYPT;
78 }
79 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanDerive, keyUsageStr, 0))
80 {
81 result |= CSSM_KEYUSE_DERIVE;
82 }
83 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanSign, keyUsageStr, 0))
84 {
85 result |= CSSM_KEYUSE_SIGN;
86 }
87 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanVerify, keyUsageStr, 0))
88 {
89 result |= CSSM_KEYUSE_VERIFY;
90 }
91 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanWrap, keyUsageStr, 0))
92 {
93 result |= CSSM_KEYUSE_WRAP;
94 }
95 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanUnwrap, keyUsageStr, 0))
96 {
97 result |= CSSM_KEYUSE_UNWRAP;
98 }
99 }
100 }
101
102 return result;
103 }
104
105
106 CFTypeID
107 SecIdentityGetTypeID(void)
108 {
109 BEGIN_SECAPI
110
111 return gTypes().Identity.typeID;
112
113 END_SECAPI1(_kCFRuntimeNotATypeID)
114 }
115
116
117 OSStatus
118 SecIdentityCopyCertificate(
119 SecIdentityRef identityRef,
120 SecCertificateRef *certificateRef)
121 {
122 BEGIN_SECAPI
123
124 SecPointer<Certificate> certificatePtr(Identity::required(identityRef)->certificate());
125 Required(certificateRef) = certificatePtr->handle();
126
127 END_SECAPI
128 }
129
130
131 OSStatus
132 SecIdentityCopyPrivateKey(
133 SecIdentityRef identityRef,
134 SecKeyRef *privateKeyRef)
135 {
136 BEGIN_SECAPI
137
138 SecPointer<KeyItem> keyItemPtr(Identity::required(identityRef)->privateKey());
139 Required(privateKeyRef) = keyItemPtr->handle();
140
141 END_SECAPI
142 }
143
144 OSStatus
145 SecIdentityCreateWithCertificate(
146 CFTypeRef keychainOrArray,
147 SecCertificateRef certificateRef,
148 SecIdentityRef *identityRef)
149 {
150 BEGIN_SECAPI
151
152 SecPointer<Certificate> certificatePtr(Certificate::required(certificateRef));
153 StorageManager::KeychainList keychains;
154 globals().storageManager.optionalSearchList(keychainOrArray, keychains);
155 SecPointer<Identity> identityPtr(new Identity(keychains, certificatePtr));
156 Required(identityRef) = identityPtr->handle();
157
158 END_SECAPI
159 }
160
161 SecIdentityRef
162 SecIdentityCreate(
163 CFAllocatorRef allocator,
164 SecCertificateRef certificate,
165 SecKeyRef privateKey)
166 {
167 SecIdentityRef identityRef = NULL;
168 OSStatus __secapiresult;
169 try {
170 SecPointer<Certificate> certificatePtr(Certificate::required(certificate));
171 SecPointer<KeyItem> keyItemPtr(KeyItem::required(privateKey));
172 SecPointer<Identity> identityPtr(new Identity(keyItemPtr, certificatePtr));
173 identityRef = identityPtr->handle();
174
175 __secapiresult=errSecSuccess;
176 }
177 catch (const MacOSError &err) { __secapiresult=err.osStatus(); }
178 catch (const CommonError &err) { __secapiresult=SecKeychainErrFromOSStatus(err.osStatus()); }
179 catch (const std::bad_alloc &) { __secapiresult=errSecAllocate; }
180 catch (...) { __secapiresult=errSecInternalComponent; }
181 return identityRef;
182 }
183
184 CFComparisonResult
185 SecIdentityCompare(
186 SecIdentityRef identity1,
187 SecIdentityRef identity2,
188 CFOptionFlags compareOptions)
189 {
190 if (!identity1 || !identity2)
191 {
192 if (identity1 == identity2)
193 return kCFCompareEqualTo;
194 else if (identity1 < identity2)
195 return kCFCompareLessThan;
196 else
197 return kCFCompareGreaterThan;
198 }
199
200 BEGIN_SECAPI
201
202 SecPointer<Identity> id1(Identity::required(identity1));
203 SecPointer<Identity> id2(Identity::required(identity2));
204
205 if (id1 == id2)
206 return kCFCompareEqualTo;
207 else if (id1 < id2)
208 return kCFCompareLessThan;
209 else
210 return kCFCompareGreaterThan;
211
212 END_SECAPI1(kCFCompareGreaterThan);
213 }
214
215 static
216 CFArrayRef _SecIdentityCopyPossiblePaths(
217 CFStringRef name)
218 {
219 // utility function to build and return an array of possible paths for the given name.
220 // if name is not a URL, this returns a single-element array.
221 // if name is a URL, the array may contain 1..N elements, one for each level of the path hierarchy.
222
223 CFMutableArrayRef names = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
224 if (!name) {
225 return names;
226 }
227 CFIndex oldLength = CFStringGetLength(name);
228 CFArrayAppendValue(names, name);
229
230 CFURLRef url = CFURLCreateWithString(NULL, name, NULL);
231 if (url) {
232 if (CFURLCanBeDecomposed(url)) {
233 // first, remove the query portion of this URL, if any
234 CFStringRef qs = CFURLCopyQueryString(url, NULL);
235 if (qs) {
236 CFMutableStringRef newName = CFStringCreateMutableCopy(NULL, oldLength, name);
237 if (newName) {
238 CFIndex qsLength = CFStringGetLength(qs) + 1; // include the '?'
239 CFStringDelete(newName, CFRangeMake(oldLength-qsLength, qsLength));
240 CFRelease(url);
241 url = CFURLCreateWithString(NULL, newName, NULL);
242 CFArraySetValueAtIndex(names, 0, newName);
243 CFRelease(newName);
244 }
245 CFRelease(qs);
246 }
247 // now add an entry for each level of the path
248 while (url) {
249 CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(NULL, url);
250 if (parent) {
251 CFStringRef parentURLString = CFURLGetString(parent);
252 if (parentURLString) {
253 CFIndex newLength = CFStringGetLength(parentURLString);
254 // check that string length has decreased as expected; for file URLs,
255 // CFURLCreateCopyDeletingLastPathComponent can insert './' or '../'
256 if ((newLength >= oldLength) || (!CFStringHasPrefix(name, parentURLString))) {
257 CFRelease(parent);
258 CFRelease(url);
259 break;
260 }
261 oldLength = newLength;
262 CFArrayAppendValue(names, parentURLString);
263 }
264 }
265 CFRelease(url);
266 url = parent;
267 }
268 }
269 else {
270 CFRelease(url);
271 }
272 }
273 // finally, add wildcard entries for each subdomain
274 url = CFURLCreateWithString(NULL, name, NULL);
275 if (url) {
276 if (CFURLCanBeDecomposed(url)) {
277 CFStringRef netLocString = CFURLCopyNetLocation(url);
278 if (netLocString) {
279 // first strip off port number, if present
280 CFStringRef tmpLocString = netLocString;
281 CFArrayRef hostnameArray = CFStringCreateArrayBySeparatingStrings(NULL, netLocString, CFSTR(":"));
282 tmpLocString = (CFStringRef)CFRetain((CFStringRef)CFArrayGetValueAtIndex(hostnameArray, 0));
283 CFRelease(netLocString);
284 CFRelease(hostnameArray);
285 netLocString = tmpLocString;
286 // split remaining string into domain components
287 hostnameArray = CFStringCreateArrayBySeparatingStrings(NULL, netLocString, CFSTR("."));
288 CFIndex subdomainCount = CFArrayGetCount(hostnameArray);
289 CFIndex i = 0;
290 while (++i < subdomainCount) {
291 CFIndex j = i;
292 CFMutableStringRef wildcardString = CFStringCreateMutable(NULL, 0);
293 if (wildcardString) {
294 CFStringAppendCString(wildcardString, "*", kCFStringEncodingUTF8);
295 while (j < subdomainCount) {
296 CFStringRef domainString = (CFStringRef)CFArrayGetValueAtIndex(hostnameArray, j++);
297 if (CFStringGetLength(domainString) > 0) {
298 CFStringAppendCString(wildcardString, ".", kCFStringEncodingUTF8);
299 CFStringAppend(wildcardString, domainString);
300 }
301 }
302 if (CFStringGetLength(wildcardString) > 1) {
303 CFArrayAppendValue(names, wildcardString);
304 }
305 CFRelease(wildcardString);
306 }
307 }
308 CFRelease(hostnameArray);
309 CFRelease(netLocString);
310 }
311 }
312 CFRelease(url);
313 }
314
315 return names;
316 }
317
318 static
319 OSStatus _SecIdentityCopyPreferenceMatchingName(
320 CFStringRef name,
321 CSSM_KEYUSE keyUsage,
322 CFArrayRef validIssuers,
323 SecIdentityRef *identity)
324 {
325 // this is NOT exported, and called only from SecIdentityCopyPreference (below), so no BEGIN/END macros here;
326 // caller must handle exceptions
327
328 StorageManager::KeychainList keychains;
329 globals().storageManager.getSearchList(keychains);
330 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL);
331
332 char idUTF8[MAXPATHLEN];
333 Required(name);
334 if (!CFStringGetCString(name, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8))
335 idUTF8[0] = (char)'\0';
336 CssmData service(const_cast<char *>(idUTF8), strlen(idUTF8));
337 FourCharCode itemType = 'iprf';
338 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service);
339 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), itemType);
340 if (keyUsage)
341 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage);
342
343 Item prefItem;
344 if (!cursor->next(prefItem))
345 return errSecItemNotFound;
346
347 // get persistent certificate reference
348 SecKeychainAttribute itemAttrs[] = { { kSecGenericItemAttr, 0, NULL } };
349 SecKeychainAttributeList itemAttrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };
350 prefItem->getContent(NULL, &itemAttrList, NULL, NULL);
351
352 // find certificate, given persistent reference data
353 CFDataRef pItemRef = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)itemAttrs[0].data, itemAttrs[0].length, kCFAllocatorNull);
354 SecKeychainItemRef certItemRef = nil;
355 OSStatus status = SecKeychainItemCopyFromPersistentReference(pItemRef, &certItemRef); //%%% need to make this a method of ItemImpl
356 prefItem->freeContent(&itemAttrList, NULL);
357 if (pItemRef)
358 CFRelease(pItemRef);
359 if (status)
360 return status;
361
362 // filter on valid issuers, if provided
363 if (validIssuers) {
364 //%%%TBI
365 }
366
367 // create identity reference, given certificate
368 Item certItem = ItemImpl::required(SecKeychainItemRef(certItemRef));
369 SecPointer<Certificate> certificate(static_cast<Certificate *>(certItem.get()));
370 SecPointer<Identity> identity_ptr(new Identity(keychains, certificate));
371 if (certItemRef)
372 CFRelease(certItemRef);
373
374 Required(identity) = identity_ptr->handle();
375
376 return status;
377 }
378
379 SecIdentityRef SecIdentityCopyPreferred(CFStringRef name, CFArrayRef keyUsage, CFArrayRef validIssuers)
380 {
381 // This function will look for a matching preference in the following order:
382 // - matches the name and the supplied key use
383 // - matches the name and the special 'ANY' key use
384 // - matches the name with no key usage constraint
385
386 SecIdentityRef identityRef = NULL;
387 CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage);
388 OSStatus status = SecIdentityCopyPreference(name, keyUse, validIssuers, &identityRef);
389 if (status != errSecSuccess && keyUse != CSSM_KEYUSE_ANY)
390 status = SecIdentityCopyPreference(name, CSSM_KEYUSE_ANY, validIssuers, &identityRef);
391 if (status != errSecSuccess && keyUse != 0)
392 status = SecIdentityCopyPreference(name, 0, validIssuers, &identityRef);
393
394 return identityRef;
395 }
396
397 OSStatus SecIdentityCopyPreference(
398 CFStringRef name,
399 CSSM_KEYUSE keyUsage,
400 CFArrayRef validIssuers,
401 SecIdentityRef *identity)
402 {
403 // The original implementation of SecIdentityCopyPreference matches the exact string only.
404 // That implementation has been moved to _SecIdentityCopyPreferenceMatchingName (above),
405 // and this function is a wrapper which calls it, so that existing clients will get the
406 // extended behavior of server domain matching for items that specify URLs.
407 // (Note that behavior is unchanged if the specified name is not a URL.)
408
409 BEGIN_SECAPI
410
411 CFTypeRef val = (CFTypeRef)CFPreferencesCopyValue(CFSTR("LogIdentityPreferenceLookup"),
412 CFSTR("com.apple.security"),
413 kCFPreferencesCurrentUser,
414 kCFPreferencesAnyHost);
415 Boolean logging = false;
416 if (val && CFGetTypeID(val) == CFBooleanGetTypeID()) {
417 logging = CFBooleanGetValue((CFBooleanRef)val);
418 CFRelease(val);
419 }
420
421 OSStatus status = errSecItemNotFound;
422 CFArrayRef names = _SecIdentityCopyPossiblePaths(name);
423 if (!names) {
424 return status;
425 }
426
427 CFIndex idx, total = CFArrayGetCount(names);
428 for (idx = 0; idx < total; idx++) {
429 CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, idx);
430 try {
431 status = _SecIdentityCopyPreferenceMatchingName(aName, keyUsage, validIssuers, identity);
432 }
433 catch (...) { status = errSecItemNotFound; }
434
435 if (logging) {
436 // get identity label
437 CFStringRef labelString = NULL;
438 if (!status && identity && *identity) {
439 try {
440 SecPointer<Certificate> cert(Identity::required(*identity)->certificate());
441 cert->inferLabel(false, &labelString);
442 }
443 catch (...) { labelString = NULL; };
444 }
445 char *labelBuf = NULL;
446 CFIndex labelBufSize = (labelString) ? CFStringGetLength(labelString) * 4 : 4;
447 labelBuf = (char *)malloc(labelBufSize);
448 if (!labelString || !CFStringGetCString(labelString, labelBuf, labelBufSize, kCFStringEncodingUTF8)) {
449 labelBuf[0] = 0;
450 }
451 if (labelString) {
452 CFRelease(labelString);
453 }
454
455 // get service name
456 char *serviceBuf = NULL;
457 CFIndex serviceBufSize = CFStringGetLength(aName) * 4;
458 serviceBuf = (char *)malloc(serviceBufSize);
459 if (!CFStringGetCString(aName, serviceBuf, serviceBufSize, kCFStringEncodingUTF8)) {
460 serviceBuf[0] = 0;
461 }
462
463 syslog(LOG_NOTICE, "preferred identity: \"%s\" found for \"%s\"\n", labelBuf, serviceBuf);
464 if (!status && name) {
465 char *nameBuf = NULL;
466 CFIndex nameBufSize = CFStringGetLength(name) * 4;
467 nameBuf = (char *)malloc(nameBufSize);
468 if (!CFStringGetCString(name, nameBuf, nameBufSize, kCFStringEncodingUTF8)) {
469 nameBuf[0] = 0;
470 }
471 syslog(LOG_NOTICE, "lookup complete; will use: \"%s\" for \"%s\"\n", labelBuf, nameBuf);
472 free(nameBuf);
473 }
474
475 free(labelBuf);
476 free(serviceBuf);
477 }
478
479 if (status == errSecSuccess) {
480 break; // match found
481 }
482 }
483
484 CFRelease(names);
485 return status;
486
487 END_SECAPI
488 }
489
490 OSStatus SecIdentitySetPreference(
491 SecIdentityRef identity,
492 CFStringRef name,
493 CSSM_KEYUSE keyUsage)
494 {
495 if (!name) {
496 return errSecParam;
497 }
498 if (!identity) {
499 // treat NULL identity as a request to clear the preference
500 // (note: if keyUsage is 0, this clears all key usage prefs for name)
501 return SecIdentityDeletePreferenceItemWithNameAndKeyUsage(NULL, name, keyUsage);
502 }
503
504 BEGIN_SECAPI
505
506 SecPointer<Certificate> certificate(Identity::required(identity)->certificate());
507
508 // determine the account attribute
509 //
510 // This attribute must be synthesized from certificate label + pref item type + key usage,
511 // as only the account and service attributes can make a generic keychain item unique.
512 // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that
513 // we can save a certificate preference if an identity preference already exists for the
514 // given service name, and vice-versa.
515 // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string.
516 //
517 CFStringRef labelStr = nil;
518 certificate->inferLabel(false, &labelStr);
519 if (!labelStr) {
520 MacOSError::throwMe(errSecDataTooLarge); // data is "in a format which cannot be displayed"
521 }
522 CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1;
523 const char *templateStr = "%s [key usage 0x%X]";
524 const int keyUsageMaxStrLen = 8;
525 accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen;
526 char accountUTF8[accountUTF8Len];
527 if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8))
528 accountUTF8[0] = (char)'\0';
529 if (keyUsage)
530 snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage);
531 snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8);
532 CssmData account(const_cast<char *>(accountUTF8), strlen(accountUTF8));
533 CFRelease(labelStr);
534
535 // service attribute (name provided by the caller)
536 CFIndex serviceUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(name), kCFStringEncodingUTF8) + 1;;
537 char serviceUTF8[serviceUTF8Len];
538 if (!CFStringGetCString(name, serviceUTF8, serviceUTF8Len-1, kCFStringEncodingUTF8))
539 serviceUTF8[0] = (char)'\0';
540 CssmData service(const_cast<char *>(serviceUTF8), strlen(serviceUTF8));
541
542 // look for existing identity preference item, in case this is an update
543 StorageManager::KeychainList keychains;
544 globals().storageManager.getSearchList(keychains);
545 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL);
546 FourCharCode itemType = 'iprf';
547 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service);
548 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), itemType);
549 if (keyUsage) {
550 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage);
551 }
552
553 Item item(kSecGenericPasswordItemClass, 'aapl', 0, NULL, false);
554 bool add = (!cursor->next(item));
555 // at this point, we either have a new item to add or an existing item to update
556
557 // set item attribute values
558 item->setAttribute(Schema::attributeInfo(kSecServiceItemAttr), service);
559 item->setAttribute(Schema::attributeInfo(kSecTypeItemAttr), itemType);
560 item->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account);
561 item->setAttribute(Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage);
562 item->setAttribute(Schema::attributeInfo(kSecLabelItemAttr), service);
563
564 // generic attribute (store persistent certificate reference)
565 CFDataRef pItemRef = nil;
566 certificate->copyPersistentReference(pItemRef);
567 if (!pItemRef) {
568 MacOSError::throwMe(errSecInvalidItemRef);
569 }
570 const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef);
571 CFIndex dataLen = CFDataGetLength(pItemRef);
572 CssmData pref(const_cast<void *>(reinterpret_cast<const void *>(dataPtr)), dataLen);
573 item->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref);
574 CFRelease(pItemRef);
575
576 if (add) {
577 Keychain keychain = nil;
578 try {
579 keychain = globals().storageManager.defaultKeychain();
580 if (!keychain->exists())
581 MacOSError::throwMe(errSecNoSuchKeychain); // Might be deleted or not available at this time.
582 }
583 catch(...) {
584 keychain = globals().storageManager.defaultKeychainUI(item);
585 }
586
587 try {
588 keychain->add(item);
589 }
590 catch (const MacOSError &err) {
591 if (err.osStatus() != errSecDuplicateItem)
592 throw; // if item already exists, fall through to update
593 }
594 }
595 item->update();
596
597 END_SECAPI
598 }
599
600 OSStatus
601 SecIdentitySetPreferred(SecIdentityRef identity, CFStringRef name, CFArrayRef keyUsage)
602 {
603 CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage);
604 return SecIdentitySetPreference(identity, name, keyUse);
605 }
606
607 OSStatus
608 SecIdentityFindPreferenceItem(
609 CFTypeRef keychainOrArray,
610 CFStringRef idString,
611 SecKeychainItemRef *itemRef)
612 {
613 BEGIN_SECAPI
614
615 StorageManager::KeychainList keychains;
616 globals().storageManager.optionalSearchList(keychainOrArray, keychains);
617 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL);
618
619 char idUTF8[MAXPATHLEN];
620 idUTF8[0] = (char)'\0';
621 if (idString)
622 {
623 if (!CFStringGetCString(idString, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8))
624 idUTF8[0] = (char)'\0';
625 }
626 size_t idUTF8Len = strlen(idUTF8);
627 if (!idUTF8Len)
628 MacOSError::throwMe(errSecParam);
629
630 CssmData service(const_cast<char *>(idUTF8), idUTF8Len);
631 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service);
632 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf');
633
634 Item item;
635 if (!cursor->next(item))
636 MacOSError::throwMe(errSecItemNotFound);
637
638 if (itemRef)
639 *itemRef=item->handle();
640
641 END_SECAPI
642 }
643
644 OSStatus
645 SecIdentityFindPreferenceItemWithNameAndKeyUsage(
646 CFTypeRef keychainOrArray,
647 CFStringRef name,
648 int32_t keyUsage,
649 SecKeychainItemRef *itemRef)
650 {
651 BEGIN_SECAPI
652
653 StorageManager::KeychainList keychains;
654 globals().storageManager.optionalSearchList(keychainOrArray, keychains);
655 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL);
656
657 char idUTF8[MAXPATHLEN];
658 idUTF8[0] = (char)'\0';
659 if (name)
660 {
661 if (!CFStringGetCString(name, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8))
662 idUTF8[0] = (char)'\0';
663 }
664 size_t idUTF8Len = strlen(idUTF8);
665 if (!idUTF8Len)
666 MacOSError::throwMe(errSecParam);
667
668 CssmData service(const_cast<char *>(idUTF8), idUTF8Len);
669 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service);
670 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf');
671 if (keyUsage)
672 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage);
673
674 Item item;
675 if (!cursor->next(item))
676 MacOSError::throwMe(errSecItemNotFound);
677
678 if (itemRef)
679 *itemRef=item->handle();
680
681 END_SECAPI
682 }
683
684 OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage(
685 CFTypeRef keychainOrArray,
686 CFStringRef name,
687 int32_t keyUsage)
688 {
689 // when a specific key usage is passed, we'll only match & delete that pref;
690 // when a key usage of 0 is passed, all matching prefs should be deleted.
691 // maxUsages represents the most matches there could theoretically be, so
692 // cut things off at that point if we're still finding items (if they can't
693 // be deleted for some reason, we'd never break out of the loop.)
694
695 OSStatus status;
696 SecKeychainItemRef item = NULL;
697 int count = 0, maxUsages = 12;
698 while (++count <= maxUsages &&
699 (status = SecIdentityFindPreferenceItemWithNameAndKeyUsage(keychainOrArray, name, keyUsage, &item)) == errSecSuccess) {
700 status = SecKeychainItemDelete(item);
701 CFRelease(item);
702 item = NULL;
703 }
704
705 // it's not an error if the item isn't found
706 return (status == errSecItemNotFound) ? errSecSuccess : status;
707 }
708
709
710 static
711 OSStatus _SecIdentityAddPreferenceItemWithName(
712 SecKeychainRef keychainRef,
713 SecIdentityRef identityRef,
714 CFStringRef idString,
715 SecKeychainItemRef *itemRef)
716 {
717 // this is NOT exported, and called only from SecIdentityAddPreferenceItem (below), so no BEGIN/END macros here;
718 // caller must handle exceptions
719
720 if (!identityRef || !idString)
721 return errSecParam;
722 SecPointer<Certificate> cert(Identity::required(identityRef)->certificate());
723 Item item(kSecGenericPasswordItemClass, 'aapl', 0, NULL, false);
724 sint32 keyUsage = 0;
725
726 // determine the account attribute
727 //
728 // This attribute must be synthesized from certificate label + pref item type + key usage,
729 // as only the account and service attributes can make a generic keychain item unique.
730 // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that
731 // we can save a certificate preference if an identity preference already exists for the
732 // given service name, and vice-versa.
733 // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string.
734 //
735 CFStringRef labelStr = nil;
736 cert->inferLabel(false, &labelStr);
737 if (!labelStr) {
738 return errSecDataTooLarge; // data is "in a format which cannot be displayed"
739 }
740 CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1;
741 const char *templateStr = "%s [key usage 0x%X]";
742 const int keyUsageMaxStrLen = 8;
743 accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen;
744 char accountUTF8[accountUTF8Len];
745 if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8))
746 accountUTF8[0] = (char)'\0';
747 if (keyUsage)
748 snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage);
749 snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8);
750 CssmData account(const_cast<char *>(accountUTF8), strlen(accountUTF8));
751 CFRelease(labelStr);
752
753 // service attribute (name provided by the caller)
754 CFIndex serviceUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(idString), kCFStringEncodingUTF8) + 1;;
755 char serviceUTF8[serviceUTF8Len];
756 if (!CFStringGetCString(idString, serviceUTF8, serviceUTF8Len-1, kCFStringEncodingUTF8))
757 serviceUTF8[0] = (char)'\0';
758 CssmData service(const_cast<char *>(serviceUTF8), strlen(serviceUTF8));
759
760 // set item attribute values
761 item->setAttribute(Schema::attributeInfo(kSecServiceItemAttr), service);
762 item->setAttribute(Schema::attributeInfo(kSecLabelItemAttr), service);
763 item->setAttribute(Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf');
764 item->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account);
765 item->setAttribute(Schema::attributeInfo(kSecScriptCodeItemAttr), keyUsage);
766
767 // generic attribute (store persistent certificate reference)
768 CFDataRef pItemRef = nil;
769 OSStatus status = SecKeychainItemCreatePersistentReference((SecKeychainItemRef)cert->handle(), &pItemRef);
770 if (!pItemRef)
771 status = errSecInvalidItemRef;
772 if (status)
773 return status;
774 const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef);
775 CFIndex dataLen = CFDataGetLength(pItemRef);
776 CssmData pref(const_cast<void *>(reinterpret_cast<const void *>(dataPtr)), dataLen);
777 item->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref);
778 CFRelease(pItemRef);
779
780 Keychain keychain = nil;
781 try {
782 keychain = Keychain::optional(keychainRef);
783 if (!keychain->exists())
784 MacOSError::throwMe(errSecNoSuchKeychain); // Might be deleted or not available at this time.
785 }
786 catch(...) {
787 keychain = globals().storageManager.defaultKeychainUI(item);
788 }
789
790 try {
791 keychain->add(item);
792 }
793 catch (const MacOSError &err) {
794 if (err.osStatus() != errSecDuplicateItem)
795 throw; // if item already exists, fall through to update
796 }
797
798 item->update();
799
800 if (itemRef)
801 *itemRef = item->handle();
802
803 return status;
804 }
805
806 OSStatus SecIdentityAddPreferenceItem(
807 SecKeychainRef keychainRef,
808 SecIdentityRef identityRef,
809 CFStringRef idString,
810 SecKeychainItemRef *itemRef)
811 {
812 // The original implementation of SecIdentityAddPreferenceItem adds the exact string only.
813 // That implementation has been moved to _SecIdentityAddPreferenceItemWithName (above),
814 // and this function is a wrapper which calls it, so that existing clients will get the
815 // extended behavior of server domain matching for items that specify URLs.
816 // (Note that behavior is unchanged if the specified idString is not a URL.)
817
818 BEGIN_SECAPI
819
820 OSStatus status = errSecInternalComponent;
821 CFArrayRef names = _SecIdentityCopyPossiblePaths(idString);
822 if (!names) {
823 return status;
824 }
825
826 CFIndex total = CFArrayGetCount(names);
827 if (total > 0) {
828 // add item for name (first element in array)
829 CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, 0);
830 try {
831 status = _SecIdentityAddPreferenceItemWithName(keychainRef, identityRef, aName, itemRef);
832 }
833 catch (const MacOSError &err) { status=err.osStatus(); }
834 catch (const CommonError &err) { status=SecKeychainErrFromOSStatus(err.osStatus()); }
835 catch (const std::bad_alloc &) { status=errSecAllocate; }
836 catch (...) { status=errSecInternalComponent; }
837 }
838 if (total > 2) {
839 Boolean setDomainDefaultIdentity = FALSE;
840 CFTypeRef val = (CFTypeRef)CFPreferencesCopyValue(CFSTR("SetDomainDefaultIdentity"),
841 CFSTR("com.apple.security.identities"),
842 kCFPreferencesCurrentUser,
843 kCFPreferencesAnyHost);
844 if (val) {
845 if (CFGetTypeID(val) == CFBooleanGetTypeID())
846 setDomainDefaultIdentity = CFBooleanGetValue((CFBooleanRef)val) ? TRUE : FALSE;
847 CFRelease(val);
848 }
849 if (setDomainDefaultIdentity) {
850 // add item for domain (second-to-last element in array, e.g. "*.apple.com")
851 OSStatus tmpStatus = errSecSuccess;
852 CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, total-2);
853 try {
854 tmpStatus = _SecIdentityAddPreferenceItemWithName(keychainRef, identityRef, aName, itemRef);
855 }
856 catch (const MacOSError &err) { tmpStatus=err.osStatus(); }
857 catch (const CommonError &err) { tmpStatus=SecKeychainErrFromOSStatus(err.osStatus()); }
858 catch (const std::bad_alloc &) { tmpStatus=errSecAllocate; }
859 catch (...) { tmpStatus=errSecInternalComponent; }
860 }
861 }
862
863 CFRelease(names);
864 return status;
865
866 END_SECAPI
867 }
868
869 /* deprecated in 10.5 */
870 OSStatus SecIdentityUpdatePreferenceItem(
871 SecKeychainItemRef itemRef,
872 SecIdentityRef identityRef)
873 {
874 BEGIN_SECAPI
875
876 if (!itemRef || !identityRef)
877 MacOSError::throwMe(errSecParam);
878 SecPointer<Certificate> certificate(Identity::required(identityRef)->certificate());
879 Item prefItem = ItemImpl::required(itemRef);
880
881 // get the current key usage value for this item
882 sint32 keyUsage = 0;
883 UInt32 actLen = 0;
884 SecKeychainAttribute attr = { kSecScriptCodeItemAttr, sizeof(sint32), &keyUsage };
885 try {
886 prefItem->getAttribute(attr, &actLen);
887 }
888 catch(...) {
889 keyUsage = 0;
890 };
891
892 // set the account attribute
893 //
894 // This attribute must be synthesized from certificate label + pref item type + key usage,
895 // as only the account and service attributes can make a generic keychain item unique.
896 // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that
897 // we can save a certificate preference if an identity preference already exists for the
898 // given service name, and vice-versa.
899 // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string.
900 //
901 CFStringRef labelStr = nil;
902 certificate->inferLabel(false, &labelStr);
903 if (!labelStr) {
904 MacOSError::throwMe(errSecDataTooLarge); // data is "in a format which cannot be displayed"
905 }
906 CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1;
907 const char *templateStr = "%s [key usage 0x%X]";
908 const int keyUsageMaxStrLen = 8;
909 accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen;
910 char accountUTF8[accountUTF8Len];
911 if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8))
912 accountUTF8[0] = (char)'\0';
913 if (keyUsage)
914 snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage);
915 snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8);
916 CssmData account(const_cast<char *>(accountUTF8), strlen(accountUTF8));
917 prefItem->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account);
918 CFRelease(labelStr);
919
920 // generic attribute (store persistent certificate reference)
921 CFDataRef pItemRef = nil;
922 OSStatus status = SecKeychainItemCreatePersistentReference((SecKeychainItemRef)certificate->handle(), &pItemRef);
923 if (!pItemRef)
924 status = errSecInvalidItemRef;
925 if (status)
926 MacOSError::throwMe(status);
927 const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef);
928 CFIndex dataLen = CFDataGetLength(pItemRef);
929 CssmData pref(const_cast<void *>(reinterpret_cast<const void *>(dataPtr)), dataLen);
930 prefItem->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref);
931 CFRelease(pItemRef);
932
933 prefItem->update();
934
935 END_SECAPI
936 }
937
938 OSStatus SecIdentityCopyFromPreferenceItem(
939 SecKeychainItemRef itemRef,
940 SecIdentityRef *identityRef)
941 {
942 BEGIN_SECAPI
943
944 if (!itemRef || !identityRef)
945 MacOSError::throwMe(errSecParam);
946 Item prefItem = ItemImpl::required(itemRef);
947
948 // get persistent certificate reference
949 SecKeychainAttribute itemAttrs[] = { { kSecGenericItemAttr, 0, NULL } };
950 SecKeychainAttributeList itemAttrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };
951 prefItem->getContent(NULL, &itemAttrList, NULL, NULL);
952
953 // find certificate, given persistent reference data
954 CFDataRef pItemRef = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)itemAttrs[0].data, itemAttrs[0].length, kCFAllocatorNull);
955 SecKeychainItemRef certItemRef = nil;
956 OSStatus status = SecKeychainItemCopyFromPersistentReference(pItemRef, &certItemRef); //%%% need to make this a method of ItemImpl
957 prefItem->freeContent(&itemAttrList, NULL);
958 if (pItemRef)
959 CFRelease(pItemRef);
960 if (status)
961 return status;
962
963 // create identity reference, given certificate
964 StorageManager::KeychainList keychains;
965 globals().storageManager.optionalSearchList((CFTypeRef)NULL, keychains);
966 Item certItem = ItemImpl::required(SecKeychainItemRef(certItemRef));
967 SecPointer<Certificate> certificate(static_cast<Certificate *>(certItem.get()));
968 SecPointer<Identity> identity(new Identity(keychains, certificate));
969 if (certItemRef)
970 CFRelease(certItemRef);
971
972 Required(identityRef) = identity->handle();
973
974 END_SECAPI
975 }
976
977 /*
978 * System Identity Support.
979 */
980
981 /* plist domain (in /Library/Preferences) */
982 #define IDENTITY_DOMAIN "com.apple.security.systemidentities"
983
984 /*
985 * Our plist is a dictionary whose entries have the following format:
986 * key = domain name as CFString
987 * value = public key hash as CFData
988 */
989
990 #define SYSTEM_KEYCHAIN_PATH kSystemKeychainDir "/" kSystemKeychainName
991
992 /*
993 * All accesses to system identities and its associated plist are
994 * protected by this lock.
995 */
996 ModuleNexus<Mutex> systemIdentityLock;
997
998 OSStatus SecIdentityCopySystemIdentity(
999 CFStringRef domain,
1000 SecIdentityRef *idRef,
1001 CFStringRef *actualDomain) /* optional */
1002 {
1003 BEGIN_SECAPI
1004
1005 StLock<Mutex> _(systemIdentityLock());
1006 auto_ptr<Dictionary> identDict;
1007
1008 /* get top-level dictionary - if not present, we're done */
1009 Dictionary* d = Dictionary::CreateDictionary(IDENTITY_DOMAIN, Dictionary::US_System);
1010 if (d == NULL)
1011 {
1012 return errSecNotAvailable;
1013 }
1014
1015 identDict.reset(d);
1016
1017 /* see if there's an entry for specified domain */
1018 CFDataRef entryValue = identDict->getDataValue(domain);
1019 if(entryValue == NULL) {
1020 /* try for default entry if we're not already looking for default */
1021 if(!CFEqual(domain, kSecIdentityDomainDefault)) {
1022 entryValue = identDict->getDataValue(kSecIdentityDomainDefault);
1023 }
1024 if(entryValue == NULL) {
1025 /* no default identity */
1026 MacOSError::throwMe(errSecItemNotFound);
1027 }
1028
1029 /* remember that we're not fetching the requested domain */
1030 domain = kSecIdentityDomainDefault;
1031 }
1032
1033 /* open system keychain - error here is fatal */
1034 Keychain systemKc = globals().storageManager.make(SYSTEM_KEYCHAIN_PATH, false);
1035 CFRef<SecKeychainRef> systemKcRef(systemKc->handle());
1036 StorageManager::KeychainList keychains;
1037 globals().storageManager.optionalSearchList(systemKcRef, keychains);
1038
1039 /* search for specified cert */
1040 SecKeychainAttributeList attrList;
1041 SecKeychainAttribute attr;
1042 attr.tag = kSecPublicKeyHashItemAttr;
1043 attr.length = (UInt32)CFDataGetLength(entryValue);
1044 attr.data = (void *)CFDataGetBytePtr(entryValue);
1045 attrList.count = 1;
1046 attrList.attr = &attr;
1047
1048 KCCursor cursor(keychains, kSecCertificateItemClass, &attrList);
1049 Item certItem;
1050 if(!cursor->next(certItem)) {
1051 MacOSError::throwMe(errSecItemNotFound);
1052 }
1053
1054 /* found the cert; try matching with key to cook up identity */
1055 SecPointer<Certificate> certificate(static_cast<Certificate *>(certItem.get()));
1056 SecPointer<Identity> identity(new Identity(keychains, certificate));
1057
1058 Required(idRef) = identity->handle();
1059 if(actualDomain) {
1060 *actualDomain = domain;
1061 CFRetain(*actualDomain);
1062 }
1063
1064 END_SECAPI
1065 }
1066
1067 OSStatus SecIdentitySetSystemIdentity(
1068 CFStringRef domain,
1069 SecIdentityRef idRef)
1070 {
1071 BEGIN_SECAPI
1072
1073 StLock<Mutex> _(systemIdentityLock());
1074 if(geteuid() != 0) {
1075 MacOSError::throwMe(errSecAuthFailed);
1076 }
1077
1078 auto_ptr<MutableDictionary> identDict;
1079 MutableDictionary *d = MutableDictionary::CreateMutableDictionary(IDENTITY_DOMAIN, Dictionary::US_System);
1080 if (d)
1081 {
1082 identDict.reset(d);
1083 }
1084 else
1085 {
1086 if(idRef == NULL) {
1087 /* nothing there, nothing to set - done */
1088 return errSecSuccess;
1089 }
1090 identDict.reset(new MutableDictionary());
1091 }
1092
1093 if(idRef == NULL) {
1094 /* Just delete the possible entry for this domain */
1095 identDict->removeValue(domain);
1096 }
1097 else {
1098 /* obtain public key hash of identity's cert */
1099 SecPointer<Identity> identity(Identity::required(idRef));
1100 SecPointer<Certificate> cert = identity->certificate();
1101 const CssmData &pubKeyHash = cert->publicKeyHash();
1102 CFRef<CFDataRef> pubKeyHashData(CFDataCreate(NULL, pubKeyHash.Data,
1103 pubKeyHash.Length));
1104
1105 /* add/replace to dictionary */
1106 identDict->setValue(domain, pubKeyHashData);
1107 }
1108
1109 /* flush to disk */
1110 if(!identDict->writePlistToPrefs(IDENTITY_DOMAIN, Dictionary::US_System)) {
1111 MacOSError::throwMe(errSecIO);
1112 }
1113
1114 END_SECAPI
1115 }
1116
1117 const CFStringRef kSecIdentityDomainDefault = CFSTR("com.apple.systemdefault");
1118 const CFStringRef kSecIdentityDomainKerberosKDC = CFSTR("com.apple.kerberos.kdc");
1119