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