1/*
2 * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "SecKeychainItemExtendedAttributes.h"
25#include "SecKeychainItemPriv.h"
26#include "ExtendedAttribute.h"
27#include "SecBridge.h"
28#include "StorageManager.h"
29#include "KCCursor.h"
30
31/* I'm not sure we need this */
32#if 0
33CFTypeID SecKeychainItemExtendedAttributesGetTypeID(void);
34
35static CFTypeID SecKeychainItemExtendedAttributesGetTypeID(void)
36{
37	BEGIN_SECAPI
38
39	return gTypes().ExtendedAttribute.typeID;
40
41	END_SECAPI1(_kCFRuntimeNotATypeID)
42}
43#endif
44
45/*
46 * Determine if incoming itemRef can be considered for
47 * this mechanism; throw if not.
48 */
49static void isItemRefCapable(
50	SecKeychainItemRef			itemRef)
51{
52	CFTypeID id = CFGetTypeID(itemRef);
53	if((id == gTypes().ItemImpl.typeID) ||
54	   (id == gTypes().Certificate.typeID) ||
55	   (id == gTypes().KeyItem.typeID)) {
56		return;
57	}
58	else {
59		MacOSError::throwMe(errSecNoSuchAttr);
60	}
61}
62
63static void cfStringToData(
64	CFStringRef cfStr,
65	CssmOwnedData &dst)
66{
67	CFDataRef cfData = CFStringCreateExternalRepresentation(NULL, cfStr,
68		kCFStringEncodingUTF8, 0);
69	if(cfData == NULL) {
70		/* can't convert to UTF8!? */
71		MacOSError::throwMe(errSecParam);
72	}
73	dst.copy(CFDataGetBytePtr(cfData), CFDataGetLength(cfData));
74	CFRelease(cfData);
75}
76
77/*
78 * Look up an ExtendedAttribute item associated with specified item.
79 * Returns true if found, false if not.
80 * Throws errSecNoSuchAttr if item does not reside on a keychain.
81 */
82static bool lookupExtendedAttr(
83	SecKeychainItemRef			itemRef,
84	CFStringRef					attrName,
85	Item						&foundItem)
86{
87	isItemRefCapable(itemRef);
88
89	/*
90	 * Get the info about the extended attribute to look up:
91	 * -- RecordType
92	 * -- ItemID (i.e., PrimaryKey blob)
93	 * -- AttributeName
94	 */
95
96	Item inItem = ItemImpl::required(itemRef);
97	const CssmData &itemID	= inItem->itemID();
98	CSSM_DB_RECORDTYPE recType = inItem->recordType();
99	if(!inItem->keychain()) {
100		/* item must reside on a keychain */
101		MacOSError::throwMe(errSecNoSuchAttr);
102	}
103
104	CssmAutoData nameData(Allocator::standard());
105	cfStringToData(attrName, nameData);
106	CssmData nameCData = nameData;
107
108	SecKeychainAttribute attrs[3];
109	attrs[0].tag    = kExtendedAttrRecordTypeAttr;
110	attrs[0].length = sizeof(UInt32);
111	attrs[0].data   = (void *)&recType;
112	attrs[1].tag    = kExtendedAttrItemIDAttr;
113	attrs[1].length = (UInt32)itemID.Length;
114	attrs[1].data   = itemID.Data;
115	attrs[2].tag    = kExtendedAttrAttributeNameAttr;
116	attrs[2].length = (UInt32)nameCData.Length;
117	attrs[2].data   = nameCData.Data;
118	SecKeychainAttributeList attrList = {3, attrs};
119
120	StorageManager::KeychainList kcList;
121	kcList.push_back(inItem->keychain());
122
123	KCCursor cursor(kcList, CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE, &attrList);
124	try {
125		return cursor->next(foundItem);
126	}
127	catch(const CssmError &err) {
128		if(err.error == CSSMERR_DL_INVALID_RECORDTYPE) {
129			/* this keychain not set up for extended attributes yet */
130			return false;
131		}
132		else {
133			throw;
134		}
135	}
136}
137
138OSStatus SecKeychainItemSetExtendedAttribute(
139	SecKeychainItemRef			itemRef,
140	CFStringRef					attrName,
141	CFDataRef					attrValue)			/* NULL means delete the attribute */
142{
143    BEGIN_SECAPI
144
145	if((itemRef == NULL) || (attrName == NULL)) {
146		return errSecParam;
147	}
148
149	/* is there already a matching ExtendedAttribute item? */
150	Item foundItem;
151	bool haveMatch = lookupExtendedAttr(itemRef, attrName, foundItem);
152	if(attrValue == NULL) {
153		/* caller asking us to delete existing record */
154		if(!foundItem) {
155			return errSecNoSuchAttr;
156		}
157		foundItem->keychain()->deleteItem(foundItem);
158		return errSecSuccess;
159	}
160
161	CSSM_DATA attrCValue = {CFDataGetLength(attrValue), (uint8 *)CFDataGetBytePtr(attrValue)};
162
163	if(haveMatch) {
164		/* update existing extended attribute record */
165		CssmDbAttributeInfo attrInfo(kExtendedAttrAttributeValueAttr, CSSM_DB_ATTRIBUTE_FORMAT_BLOB);
166		foundItem->setAttribute(attrInfo, attrCValue);
167		foundItem->update();
168	}
169	else {
170		/* create a new one, add it to the same keychain as itemRef */
171		Item inItem = ItemImpl::required(itemRef);
172		CssmAutoData nameData(Allocator::standard());
173		cfStringToData(attrName, nameData);
174		CssmData nameCData = nameData;
175		SecPointer<ExtendedAttribute> extAttr(new ExtendedAttribute(
176				inItem->recordType(), inItem->itemID(), nameCData,
177				CssmData::overlay(attrCValue)));
178		Item outItem(extAttr);
179		inItem->keychain()->add(outItem);
180	}
181
182	END_SECAPI
183}
184
185OSStatus SecKeychainItemCopyExtendedAttribute(
186	SecKeychainItemRef			itemRef,
187	CFStringRef					attrName,
188	CFDataRef					*attrValue)		/* RETURNED */
189{
190    BEGIN_SECAPI
191
192	if((itemRef == NULL) || (attrName == NULL) || (attrValue == NULL)) {
193		return errSecParam;
194	}
195
196	Item foundItem;
197	if(!lookupExtendedAttr(itemRef, attrName, foundItem)) {
198		return errSecNoSuchAttr;
199	}
200
201	/*
202	 * Found it - its kExtendedAttrAttributeValueAttr value is what the
203	 * caller is looking for.
204	 * We'd like to use getAttribute() here, but that requires that we know
205	 * the size of the attribute before hand...
206	 */
207	UInt32 tag = kExtendedAttrAttributeValueAttr;
208	UInt32 format = 0;
209	SecKeychainAttributeInfo attrInfo = {1, &tag, &format};
210	SecKeychainAttributeList *attrList = NULL;
211	foundItem->getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL);
212	if((attrList == NULL) || (attrList->count != 1)) {
213		/* should never happen... */
214		MacOSError::throwMe(errSecNoSuchAttr);
215	}
216	*attrValue = CFDataCreate(NULL, (const UInt8 *)attrList->attr->data,
217		attrList->attr->length);
218	ItemImpl::freeAttributesAndData(attrList, NULL);
219	END_SECAPI
220}
221
222OSStatus SecKeychainItemCopyAllExtendedAttributes(
223	SecKeychainItemRef			itemRef,
224	CFArrayRef					*attrNames,			/* RETURNED, each element is a CFStringRef */
225	CFArrayRef					*attrValues)		/* optional, RETURNED, each element is a
226													 *   CFDataRef */
227{
228    BEGIN_SECAPI
229
230	if((itemRef == NULL) || (attrNames == NULL)) {
231		return errSecParam;
232	}
233
234	isItemRefCapable(itemRef);
235
236	/*
237	 * Get the info about the extended attribute to look up:
238	 * -- RecordType
239	 * -- ItemID (i.e., PrimaryKey blob)
240	 */
241
242	Item inItem = ItemImpl::required(itemRef);
243	const CssmData &itemID	= inItem->itemID();
244	CSSM_DB_RECORDTYPE recType = inItem->recordType();
245	if(!inItem->keychain()) {
246		/* item must reside on a keychain */
247		MacOSError::throwMe(errSecNoSuchAttr);
248	}
249
250	SecKeychainAttribute attrs[2];
251	attrs[0].tag    = kExtendedAttrRecordTypeAttr;
252	attrs[0].length = sizeof(UInt32);
253	attrs[0].data   = (void *)&recType;
254	attrs[1].tag    = kExtendedAttrItemIDAttr;
255	attrs[1].length = (UInt32)itemID.Length;
256	attrs[1].data   = itemID.Data;
257	SecKeychainAttributeList attrList = {2, attrs};
258
259	StorageManager::KeychainList kcList;
260	kcList.push_back(inItem->keychain());
261
262	CFMutableArrayRef outNames = NULL;
263	CFMutableArrayRef outValues = NULL;
264	OSStatus ourRtn = errSecSuccess;
265
266	KCCursor cursor(kcList, CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE, &attrList);
267	for(;;) {
268		bool gotOne = false;
269		Item foundItem;
270		try {
271			gotOne = cursor->next(foundItem);
272		}
273		catch(...) {
274			break;
275		}
276		if(!gotOne) {
277			break;
278		}
279
280		/*
281		 * Found one - return its kExtendedAttrAttributeNameAttr and
282		 * (optionally) kExtendedAttrAttributeValueAttr attribute values
283		 * to caller.
284		 */
285		UInt32 tags[2] = { kExtendedAttrAttributeNameAttr, kExtendedAttrAttributeValueAttr };
286		UInt32 formats[2] = {0};
287		SecKeychainAttributeInfo attrInfo = {2, tags, formats};
288		SecKeychainAttributeList *attrList = NULL;
289		foundItem->getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL);
290		if((attrList == NULL) || (attrList->count != 2)) {
291			/* should never happen... */
292			ourRtn = errSecNoSuchAttr;
293			break;
294		}
295		if(outNames == NULL) {
296			outNames = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
297		}
298		if((outValues == NULL) && (attrValues != NULL)) {
299			/* this one's optional */
300			outValues = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
301		}
302
303		/*
304		 * I don't see how we can assume that the order of the returned
305		 * attributes is the same as the order of the tags we specified
306		 */
307		for(unsigned dex=0; dex<2; dex++) {
308			SecKeychainAttribute *attr = &attrList->attr[dex];
309			CFDataRef cfd = NULL;
310			CFStringRef cfs = NULL;
311			switch(attr->tag) {
312				case kExtendedAttrAttributeNameAttr:
313					cfd = CFDataCreate(NULL, (const UInt8 *)attr->data, attr->length);
314
315					/* We created this attribute's data via CFStringCreateExternalRepresentation, so
316					 * this should always work... */
317					cfs = CFStringCreateFromExternalRepresentation(NULL, cfd, kCFStringEncodingUTF8);
318					CFArrayAppendValue(outNames, cfs);
319					CFRelease(cfd);
320					CFRelease(cfs);
321					break;
322				case kExtendedAttrAttributeValueAttr:
323					if(outValues == NULL) {
324						break;
325					}
326					cfd = CFDataCreate(NULL, (const UInt8 *)attr->data, attr->length);
327					CFArrayAppendValue(outValues, cfd);
328					CFRelease(cfd);
329					break;
330				default:
331					/* should never happen, right? */
332					MacOSError::throwMe(errSecInternalComponent);
333			}
334		}
335		ItemImpl::freeAttributesAndData(attrList, NULL);
336	}	/* main loop fetching matching Extended Attr records */
337
338	if(ourRtn) {
339		if(outNames) {
340			CFRelease(outNames);
341		}
342		if(outValues) {
343			CFRelease(outValues);
344		}
345		MacOSError::throwMe(ourRtn);
346	}
347
348	if(outNames == NULL) {
349		/* no extended attributes found */
350		return errSecNoSuchAttr;
351	}
352	*attrNames = outNames;
353	if(outValues) {
354		*attrValues = outValues;
355	}
356
357	END_SECAPI
358}
359