/ OSX / libsecurity_keychain / lib / SecKeychainItemExtendedAttributes.cpp
SecKeychainItemExtendedAttributes.cpp
  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 <security_utilities/casts.h>
 25  #include "SecKeychainItemExtendedAttributes.h"
 26  #include "SecKeychainItemPriv.h"
 27  #include "ExtendedAttribute.h"
 28  #include "SecBridge.h"
 29  #include "StorageManager.h"
 30  #include "KCCursor.h"
 31  #include <os/activity.h>
 32  
 33  #include "LegacyAPICounts.h"
 34  
 35  extern "C" Boolean SecKeyIsCDSAKey(SecKeyRef ref);
 36  
 37  /*
 38   * Determine if incoming itemRef can be considered for 
 39   * this mechanism; throw if not.
 40   */
 41  static void isItemRefCapable(
 42  	SecKeychainItemRef			itemRef)
 43  {
 44  	CFTypeID id = CFGetTypeID(itemRef);
 45  	if((id == gTypes().ItemImpl.typeID) ||
 46  	   (id == gTypes().Certificate.typeID) || 
 47  	   (id == SecKeyGetTypeID() && SecKeyIsCDSAKey((SecKeyRef)itemRef))) {
 48  		return;
 49  	}
 50  	else {
 51  		MacOSError::throwMe(errSecNoSuchAttr);
 52  	}   
 53  }	
 54  
 55  static void cfStringToData(
 56  	CFStringRef cfStr,
 57  	CssmOwnedData &dst)
 58  {
 59  	CFDataRef cfData = CFStringCreateExternalRepresentation(NULL, cfStr,
 60  		kCFStringEncodingUTF8, 0);
 61  	if(cfData == NULL) {
 62  		/* can't convert to UTF8!? */
 63  		MacOSError::throwMe(errSecParam);
 64  	}
 65  	dst.copy(CFDataGetBytePtr(cfData), CFDataGetLength(cfData)); 
 66  	CFRelease(cfData);
 67  }
 68  
 69  /*
 70   * Look up an ExtendedAttribute item associated with specified item.
 71   * Returns true if found, false if not. 
 72   * Throws errSecNoSuchAttr if item does not reside on a keychain.
 73   */
 74  static bool lookupExtendedAttr(
 75  	SecKeychainItemRef			itemRef,
 76  	CFStringRef					attrName,
 77  	Item						&foundItem)
 78  {
 79  	isItemRefCapable(itemRef);
 80  	
 81  	/* 
 82  	 * Get the info about the extended attribute to look up:
 83  	 * -- RecordType
 84  	 * -- ItemID (i.e., PrimaryKey blob)
 85  	 * -- AttributeName
 86  	 */
 87  
 88  	Item inItem = ItemImpl::required(itemRef);
 89  	const CssmData &itemID	= inItem->itemID();
 90  	CSSM_DB_RECORDTYPE recType = inItem->recordType();
 91  	if(!inItem->keychain()) {
 92  		/* item must reside on a keychain */
 93  		MacOSError::throwMe(errSecNoSuchAttr);
 94  	}
 95  	
 96  	CssmAutoData nameData(Allocator::standard());
 97  	cfStringToData(attrName, nameData);
 98  	CssmData nameCData = nameData;
 99  	
100  	SecKeychainAttribute attrs[3];
101  	attrs[0].tag    = kExtendedAttrRecordTypeAttr;
102  	attrs[0].length = sizeof(UInt32);
103  	attrs[0].data   = (void *)&recType;
104  	attrs[1].tag    = kExtendedAttrItemIDAttr;
105  	attrs[1].length = (UInt32)itemID.Length;
106  	attrs[1].data   = itemID.Data;
107  	attrs[2].tag    = kExtendedAttrAttributeNameAttr;
108  	attrs[2].length = (UInt32)nameCData.Length;
109  	attrs[2].data   = nameCData.Data;
110  	SecKeychainAttributeList attrList = {3, attrs};
111  	
112  	StorageManager::KeychainList kcList;
113  	kcList.push_back(inItem->keychain());
114  	
115  	KCCursor cursor(kcList, (SecItemClass) CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE, &attrList);
116  	try {
117  		return cursor->next(foundItem);
118  	}
119  	catch(const CssmError &err) {
120  		if(err.error == CSSMERR_DL_INVALID_RECORDTYPE) {
121  			/* this keychain not set up for extended attributes yet */
122  			return false;
123  		}
124  		else {
125  			throw;
126  		}
127  	} 
128  }
129  
130  OSStatus SecKeychainItemSetExtendedAttribute(
131  	SecKeychainItemRef			itemRef,
132  	CFStringRef					attrName,
133  	CFDataRef					attrValue)			/* NULL means delete the attribute */
134  {
135      // <rdar://25635468>
136      //%%% This needs to detect SecCertificateRef items, and when it does, SecKeychainItemDelete must be updated
137  
138      BEGIN_SECAPI
139      os_activity_t activity = os_activity_create("SecKeychainItemSetExtendedAttribute", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT);
140      os_activity_scope(activity);
141      os_release(activity);
142  	
143  	if((itemRef == NULL) || (attrName == NULL)) {
144  		return errSecParam;
145  	}
146  	
147  	/* is there already a matching ExtendedAttribute item? */
148  	Item foundItem;
149  	bool haveMatch = lookupExtendedAttr(itemRef, attrName, foundItem);
150  	if(attrValue == NULL) {
151  		/* caller asking us to delete existing record */
152  		if(!foundItem) {
153  			return errSecNoSuchAttr;
154  		}
155  		foundItem->keychain()->deleteItem(foundItem);
156  		return errSecSuccess;
157  	}
158  
159  	CSSM_DATA attrCValue = {int_cast<CFIndex, CSSM_SIZE>(CFDataGetLength(attrValue)), (uint8 *)CFDataGetBytePtr(attrValue)};
160  	
161  	if(haveMatch) {
162  		/* update existing extended attribute record */
163  		CssmDbAttributeInfo attrInfo(kExtendedAttrAttributeValueAttr, CSSM_DB_ATTRIBUTE_FORMAT_BLOB);
164  		foundItem->setAttribute(attrInfo, attrCValue);
165  		foundItem->update();
166  	}
167  	else {
168  		/* create a new one, add it to the same keychain as itemRef */
169  		Item inItem = ItemImpl::required(itemRef);
170  		CssmAutoData nameData(Allocator::standard());
171  		cfStringToData(attrName, nameData);
172  		CssmData nameCData = nameData;
173  		SecPointer<ExtendedAttribute> extAttr(new ExtendedAttribute(
174  				inItem->recordType(), inItem->itemID(), nameCData,
175  				CssmData::overlay(attrCValue)));
176  		Item outItem(extAttr);
177  		inItem->keychain()->add(outItem);
178  	}
179  	
180  	END_SECAPI
181  }
182  
183  OSStatus SecKeychainItemCopyExtendedAttribute(
184  	SecKeychainItemRef			itemRef,
185  	CFStringRef					attrName,
186  	CFDataRef					*attrValue)		/* RETURNED */
187  {
188      // <rdar://25635468>
189      //%%% This needs to detect SecCertificateRef items
190  
191      BEGIN_SECAPI
192      os_activity_t activity = os_activity_create("SecKeychainItemCopyExtendedAttribute", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT);
193      os_activity_scope(activity);
194      os_release(activity);
195  	
196  	if((itemRef == NULL) || (attrName == NULL) || (attrValue == NULL)) {
197  		return errSecParam;
198  	}
199  	
200  	Item foundItem;
201  	if(!lookupExtendedAttr(itemRef, attrName, foundItem)) {
202  		return errSecNoSuchAttr;
203  	}
204  	
205  	/* 
206  	 * Found it - its kExtendedAttrAttributeValueAttr value is what the 
207  	 * caller is looking for.
208  	 * We'd like to use getAttribute() here, but that requires that we know
209  	 * the size of the attribute before hand...
210  	 */
211  	UInt32 tag = kExtendedAttrAttributeValueAttr;
212  	UInt32 format = 0;
213  	SecKeychainAttributeInfo attrInfo = {1, &tag, &format};
214  	SecKeychainAttributeList *attrList = NULL;
215  	foundItem->getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL);
216  	if((attrList == NULL) || (attrList->count != 1)) {
217  		/* should never happen... */
218  		MacOSError::throwMe(errSecNoSuchAttr);
219  	}
220  	*attrValue = CFDataCreate(NULL, (const UInt8 *)attrList->attr->data, 
221  		attrList->attr->length);
222  	ItemImpl::freeAttributesAndData(attrList, NULL);
223  	END_SECAPI
224  }
225  
226  OSStatus SecKeychainItemCopyAllExtendedAttributes(
227  	SecKeychainItemRef			itemRef,
228  	CFArrayRef					*attrNames,			/* RETURNED, each element is a CFStringRef */
229  	CFArrayRef					*attrValues)		/* optional, RETURNED, each element is a 
230  													 *   CFDataRef */
231  {
232      // <rdar://25635468>
233      //%%% This needs to detect SecCertificateRef items, and when it does, SecKeychainItemDelete must be updated
234  
235      BEGIN_SECAPI
236      os_activity_t activity = os_activity_create("SecKeychainItemCopyAllExtendedAttributes", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT);
237      os_activity_scope(activity);
238      os_release(activity);
239  	
240  	if((itemRef == NULL) || (attrNames == NULL)) {
241  		return errSecParam;
242  	}
243  
244  	isItemRefCapable(itemRef);
245  	
246  	/* 
247  	 * Get the info about the extended attribute to look up:
248  	 * -- RecordType
249  	 * -- ItemID (i.e., PrimaryKey blob)
250  	 */
251  
252  	Item inItem = ItemImpl::required(itemRef);
253  	const CssmData &itemID	= inItem->itemID();
254  	CSSM_DB_RECORDTYPE recType = inItem->recordType();
255  	if(!inItem->keychain()) {
256  		/* item must reside on a keychain */
257  		MacOSError::throwMe(errSecNoSuchAttr);
258  	}
259  	
260  	SecKeychainAttribute attrs[2];
261  	attrs[0].tag    = kExtendedAttrRecordTypeAttr;
262  	attrs[0].length = sizeof(UInt32);
263  	attrs[0].data   = (void *)&recType;
264  	attrs[1].tag    = kExtendedAttrItemIDAttr;
265  	attrs[1].length = (UInt32)itemID.Length;
266  	attrs[1].data   = itemID.Data;
267  	SecKeychainAttributeList attrList = {2, attrs};
268  	
269  	StorageManager::KeychainList kcList;
270  	kcList.push_back(inItem->keychain());
271  	
272  	CFMutableArrayRef outNames = NULL;
273  	CFMutableArrayRef outValues = NULL;
274  	OSStatus ourRtn = errSecSuccess;
275  	
276  	KCCursor cursor(kcList, (SecItemClass) CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE, &attrList);
277  	for(;;) {
278  		bool gotOne = false;
279  		Item foundItem;
280  		try {
281  			gotOne = cursor->next(foundItem);
282  		}
283  		catch(...) {
284  			break;
285  		} 
286  		if(!gotOne) {
287  			break;
288  		}
289  		
290  		/* 
291  		 * Found one - return its kExtendedAttrAttributeNameAttr and
292  		 * (optionally) kExtendedAttrAttributeValueAttr attribute values
293  		 * to caller.
294  		 */
295  		UInt32 tags[2] = { kExtendedAttrAttributeNameAttr, kExtendedAttrAttributeValueAttr };
296  		UInt32 formats[2] = {0};
297  		SecKeychainAttributeInfo attrInfo = {2, tags, formats};
298  		SecKeychainAttributeList *attrList = NULL;
299  		foundItem->getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL);
300  		if((attrList == NULL) || (attrList->count != 2)) {
301  			/* should never happen... */
302  			ourRtn = errSecNoSuchAttr;
303  			break;
304  		}
305  		if(outNames == NULL) {
306  			outNames = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
307  		}
308  		if((outValues == NULL) && (attrValues != NULL)) {
309  			/* this one's optional */
310  			outValues = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
311  		}
312  		
313  		/* 
314  		 * I don't see how we can assume that the order of the returned
315  		 * attributes is the same as the order of the tags we specified 
316  		 */
317  		for(unsigned dex=0; dex<2; dex++) {
318  			SecKeychainAttribute *attr = &attrList->attr[dex];
319  			CFDataRef cfd = NULL;
320  			CFStringRef cfs = NULL;
321  			switch(attr->tag) {
322  				case kExtendedAttrAttributeNameAttr:
323  					cfd = CFDataCreate(NULL, (const UInt8 *)attr->data, attr->length);
324  					
325  					/* We created this attribute's data via CFStringCreateExternalRepresentation, so
326  					 * this should always work... */
327  					cfs = CFStringCreateFromExternalRepresentation(NULL, cfd, kCFStringEncodingUTF8);
328  					CFArrayAppendValue(outNames, cfs);
329  					CFRelease(cfd);
330  					CFRelease(cfs);
331  					break;
332  				case kExtendedAttrAttributeValueAttr:
333  					if(outValues == NULL) {
334  						break;
335  					}
336  					cfd = CFDataCreate(NULL, (const UInt8 *)attr->data, attr->length);
337  					CFArrayAppendValue(outValues, cfd);
338  					CFRelease(cfd);
339  					break;
340  				default:
341  					/* should never happen, right? */
342  					MacOSError::throwMe(errSecInternalComponent);
343  			}
344  		}
345  		ItemImpl::freeAttributesAndData(attrList, NULL);
346  	}	/* main loop fetching matching Extended Attr records */
347  	
348  	if(ourRtn) {
349  		if(outNames) {
350  			CFRelease(outNames);
351  		}
352  		if(outValues) {
353  			CFRelease(outValues);
354  		}
355  		MacOSError::throwMe(ourRtn);
356  	}
357  	
358  	if(outNames == NULL) {
359  		/* no extended attributes found */
360  		return errSecNoSuchAttr;
361  	}
362  	*attrNames = outNames;
363  	if(outValues) {
364  		*attrValues = outValues;
365  	}
366  	
367  	END_SECAPI
368  }