SecKeychainItemExtendedAttributes.cpp   [plain text]


/*
 * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include "SecKeychainItemExtendedAttributes.h"
#include "SecKeychainItemPriv.h"
#include "ExtendedAttribute.h"
#include "SecBridge.h"
#include "StorageManager.h"
#include "KCCursor.h"

/* I'm not sure we need this */
#if 0
CFTypeID SecKeychainItemExtendedAttributesGetTypeID(void);

static CFTypeID SecKeychainItemExtendedAttributesGetTypeID(void)
{
	BEGIN_SECAPI

	return gTypes().ExtendedAttribute.typeID;

	END_SECAPI1(_kCFRuntimeNotATypeID)
}
#endif

/*
 * Determine if incoming itemRef can be considered for 
 * this mechanism; throw if not.
 */
static void isItemRefCapable(
	SecKeychainItemRef			itemRef)
{
	CFTypeID id = CFGetTypeID(itemRef);
	if((id == gTypes().ItemImpl.typeID) ||
	   (id == gTypes().Certificate.typeID) || 
	   (id == gTypes().KeyItem.typeID)) {
		return;
	}
	else {
		MacOSError::throwMe(errSecNoSuchAttr);
	}   
}	

static void cfStringToData(
	CFStringRef cfStr,
	CssmOwnedData &dst)
{
	CFDataRef cfData = CFStringCreateExternalRepresentation(NULL, cfStr,
		kCFStringEncodingUTF8, 0);
	if(cfData == NULL) {
		/* can't convert to UTF8!? */
		MacOSError::throwMe(paramErr);
	}
	dst.copy(CFDataGetBytePtr(cfData), CFDataGetLength(cfData)); 
	CFRelease(cfData);
}

/*
 * Look up an ExtendedAttribute item associated with specified item.
 * Returns true if found, false if not. 
 * Throws errSecNoSuchAttr if item does not reside on a keychain.
 */
static bool lookupExtendedAttr(
	SecKeychainItemRef			itemRef,
	CFStringRef					attrName,
	Item						&foundItem)
{
	isItemRefCapable(itemRef);
	
	/* 
	 * Get the info about the extended attribute to look up:
	 * -- RecordType
	 * -- ItemID (i.e., PrimaryKey blob)
	 * -- AttributeName
	 */

	Item inItem = ItemImpl::required(itemRef);
	const CssmData &itemID	= inItem->itemID();
	CSSM_DB_RECORDTYPE recType = inItem->recordType();
	if(!inItem->keychain()) {
		/* item must reside on a keychain */
		MacOSError::throwMe(errSecNoSuchAttr);
	}
	
	CssmAutoData nameData(Allocator::standard());
	cfStringToData(attrName, nameData);
	CssmData nameCData = nameData;
	
	SecKeychainAttribute attrs[3];
	attrs[0].tag    = kExtendedAttrRecordTypeAttr;
	attrs[0].length = sizeof(UInt32);
	attrs[0].data   = (void *)&recType;
	attrs[1].tag    = kExtendedAttrItemIDAttr;
	attrs[1].length = itemID.Length;
	attrs[1].data   = itemID.Data;
	attrs[2].tag    = kExtendedAttrAttributeNameAttr;
	attrs[2].length = nameCData.Length;
	attrs[2].data   = nameCData.Data;
	SecKeychainAttributeList attrList = {3, attrs};
	
	StorageManager::KeychainList kcList;
	kcList.push_back(inItem->keychain());
	
	KCCursor cursor(kcList, CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE, &attrList);
	try {
		return cursor->next(foundItem);
	}
	catch(const CssmError &err) {
		if(err.error == CSSMERR_DL_INVALID_RECORDTYPE) {
			/* this keychain not set up for extended attributes yet */
			return false;
		}
		else {
			throw;
		}
	} 
}

OSStatus SecKeychainItemSetExtendedAttribute(
	SecKeychainItemRef			itemRef,
	CFStringRef					attrName,
	CFDataRef					attrValue)			/* NULL means delete the attribute */
{
    BEGIN_SECAPI
	
	if((itemRef == NULL) || (attrName == NULL)) {
		return paramErr;
	}
	
	/* is there already a matching ExtendedAttribute item? */
	Item foundItem;
	bool haveMatch = lookupExtendedAttr(itemRef, attrName, foundItem);
	if(attrValue == NULL) {
		/* caller asking us to delete existing record */
		if(!foundItem) {
			return errSecNoSuchAttr;
		}
		foundItem->keychain()->deleteItem(foundItem);
		return noErr;
	}

	CSSM_DATA attrCValue = {CFDataGetLength(attrValue), (uint8 *)CFDataGetBytePtr(attrValue)};
	
	if(haveMatch) {
		/* update existing extended attribute record */
		CssmDbAttributeInfo attrInfo(kExtendedAttrAttributeValueAttr, CSSM_DB_ATTRIBUTE_FORMAT_BLOB);
		foundItem->setAttribute(attrInfo, attrCValue);
		foundItem->update();
	}
	else {
		/* create a new one, add it to the same keychain as itemRef */
		Item inItem = ItemImpl::required(itemRef);
		CssmAutoData nameData(Allocator::standard());
		cfStringToData(attrName, nameData);
		CssmData nameCData = nameData;
		SecPointer<ExtendedAttribute> extAttr(new ExtendedAttribute(
				inItem->recordType(), inItem->itemID(), nameCData,
				CssmData::overlay(attrCValue)));
		Item outItem(extAttr);
		inItem->keychain()->add(outItem);
	}
	
	END_SECAPI
}

OSStatus SecKeychainItemCopyExtendedAttribute(
	SecKeychainItemRef			itemRef,
	CFStringRef					attrName,
	CFDataRef					*attrValue)		/* RETURNED */
{
    BEGIN_SECAPI
	
	if((itemRef == NULL) || (attrName == NULL) || (attrValue == NULL)) {
		return paramErr;
	}
	
	Item foundItem;
	if(!lookupExtendedAttr(itemRef, attrName, foundItem)) {
		return errSecNoSuchAttr;
	}
	
	/* 
	 * Found it - its kExtendedAttrAttributeValueAttr value is what the 
	 * caller is looking for.
	 * We'd like to use getAttribute() here, but that requires that we know
	 * the size of the attribute before hand...
	 */
	UInt32 tag = kExtendedAttrAttributeValueAttr;
	UInt32 format = 0;
	SecKeychainAttributeInfo attrInfo = {1, &tag, &format};
	SecKeychainAttributeList *attrList = NULL;
	foundItem->getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL);
	if((attrList == NULL) || (attrList->count != 1)) {
		/* should never happen... */
		MacOSError::throwMe(errSecNoSuchAttr);
	}
	*attrValue = CFDataCreate(NULL, (const UInt8 *)attrList->attr->data, 
		attrList->attr->length);
	ItemImpl::freeAttributesAndData(attrList, NULL);
	END_SECAPI
}

OSStatus SecKeychainItemCopyAllExtendedAttributes(
	SecKeychainItemRef			itemRef,
	CFArrayRef					*attrNames,			/* RETURNED, each element is a CFStringRef */
	CFArrayRef					*attrValues)		/* optional, RETURNED, each element is a 
													 *   CFDataRef */
{
    BEGIN_SECAPI
	
	if((itemRef == NULL) || (attrNames == NULL)) {
		return paramErr;
	}

	isItemRefCapable(itemRef);
	
	/* 
	 * Get the info about the extended attribute to look up:
	 * -- RecordType
	 * -- ItemID (i.e., PrimaryKey blob)
	 */

	Item inItem = ItemImpl::required(itemRef);
	const CssmData &itemID	= inItem->itemID();
	CSSM_DB_RECORDTYPE recType = inItem->recordType();
	if(!inItem->keychain()) {
		/* item must reside on a keychain */
		MacOSError::throwMe(errSecNoSuchAttr);
	}
	
	SecKeychainAttribute attrs[2];
	attrs[0].tag    = kExtendedAttrRecordTypeAttr;
	attrs[0].length = sizeof(UInt32);
	attrs[0].data   = (void *)&recType;
	attrs[1].tag    = kExtendedAttrItemIDAttr;
	attrs[1].length = itemID.Length;
	attrs[1].data   = itemID.Data;
	SecKeychainAttributeList attrList = {2, attrs};
	
	StorageManager::KeychainList kcList;
	kcList.push_back(inItem->keychain());
	
	CFMutableArrayRef outNames = NULL;
	CFMutableArrayRef outValues = NULL;
	OSStatus ourRtn = noErr;
	
	KCCursor cursor(kcList, CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE, &attrList);
	for(;;) {
		bool gotOne = false;
		Item foundItem;
		try {
			gotOne = cursor->next(foundItem);
		}
		catch(...) {
			break;
		} 
		if(!gotOne) {
			break;
		}
		
		/* 
		 * Found one - return its kExtendedAttrAttributeNameAttr and
		 * (optionally) kExtendedAttrAttributeValueAttr attribute values
		 * to caller.
		 */
		UInt32 tags[2] = { kExtendedAttrAttributeNameAttr, kExtendedAttrAttributeValueAttr };
		UInt32 formats[2] = {0};
		SecKeychainAttributeInfo attrInfo = {2, tags, formats};
		SecKeychainAttributeList *attrList = NULL;
		foundItem->getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL);
		if((attrList == NULL) || (attrList->count != 2)) {
			/* should never happen... */
			ourRtn = errSecNoSuchAttr;
			break;
		}
		if(outNames == NULL) {
			outNames = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
		}
		if((outValues == NULL) && (attrValues != NULL)) {
			/* this one's optional */
			outValues = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
		}
		
		/* 
		 * I don't see how we can assume that the order of the returned
		 * attributes is the same as the order of the tags we specified 
		 */
		for(unsigned dex=0; dex<2; dex++) {
			SecKeychainAttribute *attr = &attrList->attr[dex];
			CFDataRef cfd = NULL;
			CFStringRef cfs = NULL;
			switch(attr->tag) {
				case kExtendedAttrAttributeNameAttr:
					cfd = CFDataCreate(NULL, (const UInt8 *)attr->data, attr->length);
					
					/* We created this attribute's data via CFStringCreateExternalRepresentation, so
					 * this should always work... */
					cfs = CFStringCreateFromExternalRepresentation(NULL, cfd, kCFStringEncodingUTF8);
					CFArrayAppendValue(outNames, cfs);
					CFRelease(cfd);
					CFRelease(cfs);
					break;
				case kExtendedAttrAttributeValueAttr:
					if(outValues == NULL) {
						break;
					}
					cfd = CFDataCreate(NULL, (const UInt8 *)attr->data, attr->length);
					CFArrayAppendValue(outValues, cfd);
					CFRelease(cfd);
					break;
				default:
					/* should never happen, right? */
					MacOSError::throwMe(internalComponentErr);
			}
		}
		ItemImpl::freeAttributesAndData(attrList, NULL);
	}	/* main loop fetching matching Extended Attr records */
	
	if(ourRtn) {
		if(outNames) {
			CFRelease(outNames);
		}
		if(outValues) {
			CFRelease(outValues);
		}
		MacOSError::throwMe(ourRtn);
	}
	
	if(outNames == NULL) {
		/* no extended attributes found */
		return errSecNoSuchAttr;
	}
	*attrNames = outNames;
	if(outValues) {
		*attrValues = outValues;
	}
	
	END_SECAPI
}