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 }