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 */
42OSStatus
43SecIdentityFindPreferenceItemWithNameAndKeyUsage(
44	CFTypeRef keychainOrArray,
45	CFStringRef name,
46	int32_t keyUsage,
47	SecKeychainItemRef *itemRef);
48
49OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage(
50	CFTypeRef keychainOrArray,
51	CFStringRef name,
52	int32_t keyUsage);
53
54
55CSSM_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
106CFTypeID
107SecIdentityGetTypeID(void)
108{
109	BEGIN_SECAPI
110
111	return gTypes().Identity.typeID;
112
113	END_SECAPI1(_kCFRuntimeNotATypeID)
114}
115
116
117OSStatus
118SecIdentityCopyCertificate(
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
131OSStatus
132SecIdentityCopyPrivateKey(
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
144OSStatus
145SecIdentityCreateWithCertificate(
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
161SecIdentityRef
162SecIdentityCreate(
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
184CFComparisonResult
185SecIdentityCompare(
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
215static
216CFArrayRef _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
318static
319OSStatus _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
379SecIdentityRef 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
397OSStatus 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
490OSStatus 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
600OSStatus
601SecIdentitySetPreferred(SecIdentityRef identity, CFStringRef name, CFArrayRef keyUsage)
602{
603	CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage);
604	return SecIdentitySetPreference(identity, name, keyUse);
605}
606
607OSStatus
608SecIdentityFindPreferenceItem(
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
644OSStatus
645SecIdentityFindPreferenceItemWithNameAndKeyUsage(
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
684OSStatus 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
710static
711OSStatus _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
806OSStatus 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 */
870OSStatus 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
938OSStatus 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 */
996ModuleNexus<Mutex> systemIdentityLock;
997
998OSStatus 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
1067OSStatus 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
1117const CFStringRef kSecIdentityDomainDefault = CFSTR("com.apple.systemdefault");
1118const CFStringRef kSecIdentityDomainKerberosKDC = CFSTR("com.apple.kerberos.kdc");
1119
1120