1/*
2 * Copyright (c) 2003-2007 Apple Computer, 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 * TokenIDHelper.cpp
24 */
25
26#include "TokenIDHelper.h"
27
28#include <Security/SecKeychain.h>
29#include <Security/SecKeychainPriv.h>
30#include <Security/SecCertificate.h>
31#include <Security/SecKey.h>
32#include <security_utilities/cfutilities.h>
33#include <security_utilities/errors.h>
34#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
35
36static void extract_certificate_from_identity(const void *value, void *context);
37static bool encryptionEnabled(SecKeyRef privateKeyRef);
38static OSStatus findCertificatePublicKeyHash(SecCertificateRef certificate, CFDataRef *label);
39
40int findFirstEncryptionPublicKeyOnToken(SecKeyRef *publicKey, SecKeychainRef *keychainRef, CFDataRef *label)
41{
42	if (!publicKey || !keychainRef)
43		return paramErr;
44
45	OSStatus status = noErr;
46	CFArrayRef identityArray = NULL;
47	SecKeyRef tmpKeyRef = NULL;
48	SecCertificateRef certificate = NULL;
49	SecKeychainRef tmpKeychainRef = NULL;
50
51	try
52	{
53		status = findEncryptionIdentities((CFTypeRef *)&identityArray);
54		if (status)
55			MacOSError::throwMe(status);
56
57		if (!identityArray ||
58			(CFGetTypeID(identityArray)!=CFArrayGetTypeID()) ||
59			(CFArrayGetCount(identityArray)==0))
60			MacOSError::throwMe(paramErr);
61
62		CFTypeRef tmpref = CFArrayGetValueAtIndex(identityArray, 0);
63		if (CFGetTypeID(tmpref)!=SecIdentityGetTypeID())
64			MacOSError::throwMe(paramErr);
65
66		status = SecIdentityCopyCertificate(SecIdentityRef(tmpref), &certificate);
67		if (status)
68			MacOSError::throwMe(status);
69
70		if (!certificate)
71			MacOSError::throwMe(errKCItemNotFound);
72
73		status = findCertificatePublicKeyHash(certificate, label);
74		if (status)
75			MacOSError::throwMe(status);
76
77		status = SecKeychainItemCopyKeychain(SecKeychainItemRef(certificate), &tmpKeychainRef);
78		if (status)
79			MacOSError::throwMe(status);
80
81		status = SecCertificateCopyPublicKey(certificate, &tmpKeyRef);
82		if (status)
83			MacOSError::throwMe(status);
84
85		// Found an encryption key
86		*publicKey = tmpKeyRef;
87		*keychainRef = tmpKeychainRef;
88	}
89	catch (const MacOSError &err)
90	{
91		status = err.osStatus();
92		cssmPerror("findFirstEncryptionPublicKeyOnToken", status);
93	}
94	catch (...)
95	{
96		fprintf(stderr, "findFirstEncryptionPublicKeyOnToken: unknown exception\n");
97		status = errKCItemNotFound;
98	}
99
100	if (status)
101	{
102		if (identityArray)
103			CFRelease(identityArray);
104		if (certificate)
105			CFRelease(certificate);
106	}
107
108	if (identityArray)
109		CFRelease(identityArray);
110	if (certificate)
111		CFRelease(certificate);
112
113	return status;
114}
115
116OSStatus findCertificatePublicKeyHash(SecCertificateRef certificate, CFDataRef *label)
117{
118	UInt32 tag[1] = { kSecPublicKeyHashItemAttr };	// kSecKeyLabel == hash public key	[kSecPublicKeyHashItemAttr ??kSecKeyLabel]
119	UInt32 format[1] = { CSSM_DB_ATTRIBUTE_FORMAT_BLOB };
120	SecKeychainAttributeInfo info = { 1, tag, format }; // attrs to retrieve
121
122	SecKeychainAttributeList *attrList = NULL;
123
124	OSStatus status = SecKeychainItemCopyAttributesAndData(SecKeychainItemRef(certificate), &info, NULL, &attrList, 0, NULL);
125	if (status || !attrList || !attrList->count)
126		return status;
127
128	const uint32_t index = 0;
129	if (attrList->attr[index].tag == kSecPublicKeyHashItemAttr)
130		*label = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)attrList->attr[index].data, attrList->attr[index].length);
131
132	SecKeychainItemFreeAttributesAndData(attrList, NULL);
133	return noErr;
134}
135
136int findEncryptionIdentities(CFTypeRef *identityOrArray)
137{
138	/*
139		Similar code is available in Leopard9A311 and later as "DIHLFVCopyEncryptionIdentities".
140		See <rdar://problem/4816811> FV: Add SecTokenBasedEncryptionIdentities call
141		We reproduce it here for two reasons:
142		1)	The semantics of DIHLFVCopyEncryptionIdentities are different,
143			returning either a CFData or CFArray
144		2)	We don' have to introduce a dependence on DiskImages.framework here
145
146
147		Since CSSM searching for attributes is an AND, not an OR, we need to get all
148		identities then check each one for a good key usage. If we built up a search
149		using an OR predicate, we would want to specify this for key usage:
150
151		uint32_t keyuse = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP;
152	*/
153	OSStatus status = noErr;
154	CFArrayRef searchList = NULL;
155	CFMutableArrayRef idArray = NULL;			// holds all SecIdentityRefs found
156
157	status = SecKeychainCopyDomainSearchList(kSecPreferencesDomainDynamic, &searchList);
158	if (status)
159		return status;
160
161	CFIndex count = searchList ? CFArrayGetCount(searchList) : 0;
162	if (!count)
163		return errSecNoSuchKeychain;
164
165	// Search for all identities
166	uint32_t keyuse = 0;
167	SecIdentitySearchRef srchRef = NULL;
168	status = SecIdentitySearchCreate(searchList, keyuse, &srchRef);
169	if (status)
170		return status;
171
172	while (!status)
173	{
174		SecIdentityRef identity = NULL;
175		status = SecIdentitySearchCopyNext(srchRef, &identity);
176		if (status == errSecItemNotFound)	// done
177			break;
178		if (status)
179			return status;
180
181		SecKeyRef privateKeyRef = NULL;
182		status = SecIdentityCopyPrivateKey(identity, &privateKeyRef);
183		if (status)
184			continue;
185		bool canEncrypt = encryptionEnabled(privateKeyRef);
186		CFRelease(privateKeyRef);
187		if (!canEncrypt)
188			continue;
189
190		// add the identity to the array
191		if (!idArray)
192			idArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
193		CFArrayAppendValue(idArray, identity);
194	}
195
196	if ((status == noErr || status == errSecItemNotFound) && idArray && CFArrayGetCount(idArray))
197	{
198		if (idArray)
199		{
200			*identityOrArray = idArray;
201			::CFRetain(*identityOrArray);
202		}
203		status = noErr;
204	}
205	else
206	if (idArray)
207		CFRelease(idArray);
208
209	return status;
210}
211
212int unlockToken(const char *password)
213{
214	OSStatus status = noErr;
215	if (!password)
216		return paramErr;
217
218	CFArrayRef searchList = NULL;
219
220	status = SecKeychainCopyDomainSearchList(kSecPreferencesDomainDynamic, &searchList);
221	if (status)
222		return status;
223
224	CFIndex count = searchList ? CFArrayGetCount(searchList) : 0;
225	if (count)
226	{
227		SecKeychainRef keychainRef = (SecKeychainRef)CFArrayGetValueAtIndex(searchList, 0);	// only first dynamic keychain!
228		status = SecKeychainUnlock(keychainRef, (UInt32)strlen(password), password, 1);
229		if (keychainRef)
230			CFRelease(keychainRef);
231	}
232	else
233		status = errSecNoSuchKeychain;
234	if (searchList)
235		CFRelease(searchList);
236	return status;
237}
238
239void extractCertificatesFromIdentities(CFTypeRef identityOrArray, CFArrayRef *certificateArrayOut)
240{
241	if (!identityOrArray || !certificateArrayOut)
242		return;
243
244	CFIndex cnt = (CFGetTypeID(identityOrArray)==CFArrayGetTypeID())?CFArrayGetCount((CFArrayRef)identityOrArray):1;
245	CFMutableArrayRef certificateArray = CFArrayCreateMutable(kCFAllocatorDefault, cnt, &kCFTypeArrayCallBacks);
246
247	if (CFGetTypeID(identityOrArray)==CFArrayGetTypeID())
248		CFArrayApplyFunction((CFArrayRef)identityOrArray, CFRangeMake(0, cnt),
249			extract_certificate_from_identity,
250			certificateArray);
251	else
252		extract_certificate_from_identity(identityOrArray, certificateArray);
253	*certificateArrayOut = certificateArray;
254}
255
256void extract_certificate_from_identity(const void *value, void *context)
257{
258	if (!context || !value)
259		return;
260
261	CSSM_DATA certData = {0,};
262	SecCertificateRef certificateRef;
263	OSStatus status = SecIdentityCopyCertificate((SecIdentityRef)value, &certificateRef);
264	if (!status)
265	{
266		status = SecCertificateGetData(certificateRef, &certData);
267			CFRelease(certificateRef);
268
269		if (!status)
270		{
271			CFDataRef cert = CFDataCreate(kCFAllocatorDefault, (UInt8 *)certData.Data, certData.Length);
272			CFArrayAppendValue((CFMutableArrayRef)context, cert);
273			CFRelease(cert);
274			if (certData.Data)
275				free(certData.Data);
276		}
277	}
278}
279
280bool encryptionEnabled(SecKeyRef privateKeyRef)
281{
282	/*
283		Since CSSM searching for attributes is an AND, not an OR, we need to get all
284		identities then check each one for a good key usage. Note that for the CAC
285		card, the "Email Encryption Private Key" only has the unwrap bit set (0x1A).
286		Return true if this identity supports appropriate encryption.
287	*/
288
289	UInt32 tag[] = { kSecKeyEncrypt, kSecKeyDecrypt, kSecKeyDerive, kSecKeyWrap, kSecKeyUnwrap };
290	UInt32 format[] = { CSSM_DB_ATTRIBUTE_FORMAT_UINT32, CSSM_DB_ATTRIBUTE_FORMAT_UINT32,
291		CSSM_DB_ATTRIBUTE_FORMAT_UINT32, CSSM_DB_ATTRIBUTE_FORMAT_UINT32, CSSM_DB_ATTRIBUTE_FORMAT_UINT32};
292	SecKeychainAttributeInfo info = { 5, tag, format }; // attrs to retrieve
293
294	SecKeychainAttributeList *attrList = NULL;
295	OSStatus status = SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)privateKeyRef, &info, NULL, &attrList, 0, NULL);
296	if (status || !attrList)
297		return false;
298
299	bool canEncrypt = false;
300	for (uint32_t index = 0; index < attrList->count; ++index)
301	{
302		if (attrList->attr[index].length != sizeof(uint32_t) || !attrList->attr[index].data ||
303			0 == *(uint32_t*)attrList->attr[index].data)
304			continue;
305		canEncrypt = true;
306		break;
307	}
308
309	status = SecKeychainItemFreeAttributesAndData(attrList, NULL);
310	return canEncrypt;
311}
312
313