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