]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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 | ||
25 | // | |
26 | // KCCursor.cpp | |
27 | // | |
28 | ||
29 | #include "KCCursor.h" | |
30 | ||
31 | #include "Item.h" | |
32 | #include <security_cdsa_utilities/Schema.h> | |
33 | #include <security_cdsa_utilities/KeySchema.h> | |
34 | #include "cssmdatetime.h" | |
35 | #include "Globals.h" | |
36 | #include "StorageManager.h" | |
b1ab9ed8 | 37 | #include <Security/SecKeychainItemPriv.h> |
b54c578e | 38 | #include <Security/SecBase.h> |
e3d460c9 | 39 | #include <Security/SecBasePriv.h> |
6b200bc3 | 40 | #include <utilities/array_size.h> |
b1ab9ed8 A |
41 | |
42 | using namespace KeychainCore; | |
43 | using namespace CssmClient; | |
44 | using namespace CSSMDateTimeUtils; | |
45 | ||
46 | using namespace KeySchema; | |
47 | ||
48 | // define a table of our attributes for easy lookup | |
49 | static const CSSM_DB_ATTRIBUTE_INFO* gKeyAttributeLookupTable[] = | |
50 | { | |
51 | &KeyClass, &PrintName, &Alias, &Permanent, &Private, &Modifiable, &Label, &ApplicationTag, &KeyCreator, | |
52 | &KeyType, &KeySizeInBits, &EffectiveKeySize, &StartDate, &EndDate, &Sensitive, &AlwaysSensitive, &Extractable, | |
53 | &NeverExtractable, &Encrypt, &Decrypt, &Derive, &Sign, &Verify, &SignRecover, &VerifyRecover, &Wrap, &Unwrap | |
54 | }; | |
55 | ||
56 | // | |
57 | // KCCursorImpl | |
58 | // | |
59 | KCCursorImpl::KCCursorImpl(const StorageManager::KeychainList &searchList, SecItemClass itemClass, const SecKeychainAttributeList *attrList, CSSM_DB_CONJUNCTIVE dbConjunctive, CSSM_DB_OPERATOR dbOperator) : | |
60 | mSearchList(searchList), | |
61 | mCurrent(mSearchList.begin()), | |
62 | mAllFailed(true), | |
e3d460c9 | 63 | mDeleteInvalidRecords(false), |
fa7225c8 | 64 | mIsNewKeychain(true), |
b04fe171 | 65 | mMutex(Mutex::recursive) |
b1ab9ed8 A |
66 | { |
67 | recordType(Schema::recordTypeFor(itemClass)); | |
68 | ||
69 | if (!attrList) // No additional selectionPredicates: we are done | |
70 | return; | |
71 | ||
72 | conjunctive(dbConjunctive); | |
73 | const SecKeychainAttribute *end=&attrList->attr[attrList->count]; | |
74 | // Add all the attrs in attrs list to the cursor. | |
75 | for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr) | |
76 | { | |
77 | const CSSM_DB_ATTRIBUTE_INFO *temp; | |
6b200bc3 A |
78 | |
79 | // ok, is this a key schema? Handle differently, just because we can... | |
80 | if (attr->tag <' ' && attr->tag < array_size(gKeyAttributeLookupTable)) | |
b1ab9ed8 A |
81 | { |
82 | temp = gKeyAttributeLookupTable[attr->tag]; | |
83 | } | |
84 | else | |
85 | { | |
86 | temp = &Schema::attributeInfo(attr->tag); | |
87 | } | |
88 | const CssmDbAttributeInfo &info = *temp; | |
89 | void *buf = attr->data; | |
90 | UInt32 length = attr->length; | |
91 | uint8 timeString[16]; | |
92 | ||
93 | // XXX This code is duplicated in NewItemImpl::setAttribute() | |
94 | // Convert a 4 or 8 byte TIME_DATE to a CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE | |
95 | // style attribute value. | |
96 | if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE) | |
97 | { | |
98 | if (length == sizeof(UInt32)) | |
99 | { | |
100 | MacSecondsToTimeString(*reinterpret_cast<const UInt32 *>(buf), | |
101 | 16, &timeString); | |
102 | buf = &timeString; | |
103 | length = 16; | |
104 | } | |
105 | else if (length == sizeof(SInt64)) | |
106 | { | |
107 | MacLongDateTimeToTimeString(*reinterpret_cast<const SInt64 *>(buf), | |
108 | 16, &timeString); | |
109 | buf = &timeString; | |
110 | length = 16; | |
111 | } | |
112 | } | |
113 | add(dbOperator ,info, CssmData(buf,length)); | |
114 | } | |
115 | } | |
116 | ||
117 | KCCursorImpl::KCCursorImpl(const StorageManager::KeychainList &searchList, const SecKeychainAttributeList *attrList) : | |
118 | mSearchList(searchList), | |
119 | mCurrent(mSearchList.begin()), | |
120 | mAllFailed(true), | |
e3d460c9 | 121 | mDeleteInvalidRecords(false), |
fa7225c8 | 122 | mIsNewKeychain(true), |
b04fe171 | 123 | mMutex(Mutex::recursive) |
b1ab9ed8 A |
124 | { |
125 | if (!attrList) // No additional selectionPredicates: we are done | |
126 | return; | |
127 | ||
128 | conjunctive(CSSM_DB_AND); | |
129 | bool foundClassAttribute=false; | |
130 | const SecKeychainAttribute *end=&attrList->attr[attrList->count]; | |
131 | // Add all the attrs in attrs list to the cursor. | |
132 | for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr) | |
133 | { | |
134 | if (attr->tag!=kSecClassItemAttr) // a regular attribute | |
135 | { | |
136 | const CssmDbAttributeInfo &info = Schema::attributeInfo(attr->tag); | |
137 | void *buf = attr->data; | |
138 | UInt32 length = attr->length; | |
139 | uint8 timeString[16]; | |
140 | ||
141 | // XXX This code is duplicated in NewItemImpl::setAttribute() | |
142 | // Convert a 4 or 8 byte TIME_DATE to a CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE | |
143 | // style attribute value. | |
144 | if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE) | |
145 | { | |
146 | if (length == sizeof(UInt32)) | |
147 | { | |
148 | MacSecondsToTimeString(*reinterpret_cast<const UInt32 *>(buf), | |
149 | 16, &timeString); | |
150 | buf = &timeString; | |
151 | length = 16; | |
152 | } | |
153 | else if (length == sizeof(SInt64)) | |
154 | { | |
155 | MacLongDateTimeToTimeString(*reinterpret_cast<const SInt64 *>(buf), | |
156 | 16, &timeString); | |
157 | buf = &timeString; | |
158 | length = 16; | |
159 | } | |
160 | } | |
161 | add(CSSM_DB_EQUAL,info, CssmData(buf,length)); | |
162 | ||
163 | continue; | |
164 | } | |
165 | ||
166 | // the class attribute | |
167 | if (foundClassAttribute || attr->length != sizeof(SecItemClass)) | |
427c49bc | 168 | MacOSError::throwMe(errSecParam); // We have 2 different 'clas' attributes |
b1ab9ed8 A |
169 | |
170 | recordType(Schema::recordTypeFor(*reinterpret_cast<SecItemClass *>(attr->data))); | |
171 | foundClassAttribute=true; | |
172 | } | |
173 | } | |
174 | ||
d64be36e | 175 | KCCursorImpl::~KCCursorImpl() _NOEXCEPT |
b1ab9ed8 A |
176 | { |
177 | } | |
178 | ||
427c49bc | 179 | //static ModuleNexus<Mutex> gActivationMutex; |
b1ab9ed8 A |
180 | |
181 | bool | |
182 | KCCursorImpl::next(Item &item) | |
183 | { | |
184 | StLock<Mutex>_(mMutex); | |
185 | DbAttributes dbAttributes; | |
186 | DbUniqueRecord uniqueId; | |
187 | OSStatus status = 0; | |
188 | ||
189 | for (;;) | |
190 | { | |
e3d460c9 A |
191 | Item tempItem = NULL; |
192 | { | |
e3d460c9 A |
193 | while (!mDbCursor) |
194 | { | |
195 | // Do the newKeychain dance before we check our done status | |
196 | newKeychain(mCurrent); | |
b1ab9ed8 | 197 | |
e3d460c9 A |
198 | if (mCurrent == mSearchList.end()) |
199 | { | |
200 | // If we got always failed when calling mDbCursor->next return the error from | |
201 | // the last call to mDbCursor->next now | |
202 | if (mAllFailed && status) | |
203 | CssmError::throwMe(status); | |
b1ab9ed8 | 204 | |
e3d460c9 A |
205 | // No more keychains to search so we are done. |
206 | return false; | |
207 | } | |
208 | ||
209 | try | |
210 | { | |
211 | // StLock<Mutex> _(gActivationMutex()); // force serialization of cursor creation | |
212 | Keychain &kc = *mCurrent; | |
213 | Mutex* mutex = kc->getKeychainMutex(); | |
214 | StLock<Mutex> _(*mutex); | |
215 | (*mCurrent)->database()->activate(); | |
216 | mDbCursor = DbCursor((*mCurrent)->database(), *this); | |
217 | } | |
218 | catch(const CommonError &err) | |
219 | { | |
220 | ++mCurrent; | |
fa7225c8 | 221 | mIsNewKeychain = true; |
e3d460c9 A |
222 | } |
223 | } | |
224 | ||
225 | Keychain &kc = *mCurrent; | |
b04fe171 | 226 | |
e3d460c9 A |
227 | Mutex* mutex = kc->getKeychainMutex(); |
228 | StLock<Mutex> _(*mutex); | |
229 | ||
230 | bool gotRecord; | |
231 | try | |
232 | { | |
233 | // Clear out existing attributes first! | |
234 | // (the previous iteration may have left attributes from a different schema) | |
235 | dbAttributes.clear(); | |
236 | ||
237 | gotRecord = mDbCursor->next(&dbAttributes, NULL, uniqueId); | |
238 | mAllFailed = false; | |
239 | } | |
240 | catch(const CommonError &err) | |
241 | { | |
242 | // Catch the last error we get and move on to the next keychain | |
243 | // This error will be returned when we reach the end of our keychain list | |
244 | // iff all calls to KCCursorImpl::next failed | |
245 | status = err.osStatus(); | |
246 | gotRecord = false; | |
247 | dbAttributes.invalidate(); | |
248 | } | |
249 | catch(...) | |
250 | { | |
251 | // Catch all other errors | |
252 | status = errSecItemNotFound; | |
253 | gotRecord = false; | |
254 | } | |
b1ab9ed8 | 255 | |
e3d460c9 A |
256 | // If we did not get a record from the current keychain or the current |
257 | // keychain did not exist skip to the next keychain in the list. | |
258 | if (!gotRecord) | |
259 | { | |
260 | ++mCurrent; | |
261 | mDbCursor = DbCursor(); | |
262 | // we'd like to call newKeychain(mCurrent) here, but to avoid deadlock | |
263 | // we need to drop the current keychain's mutex first. Use this silly | |
264 | // hack to void the stack Mutex object. | |
fa7225c8 | 265 | mIsNewKeychain = true; |
b1ab9ed8 | 266 | continue; |
e3d460c9 A |
267 | } |
268 | ||
269 | // If doing a search for all records, skip the db blob added by the CSPDL | |
270 | if (dbAttributes.recordType() == CSSM_DL_DB_RECORD_METADATA && | |
271 | mDbCursor->recordType() == CSSM_DL_DB_RECORD_ANY) | |
272 | continue; | |
273 | ||
274 | // Filter out group keys at this layer | |
275 | if (dbAttributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) | |
276 | { | |
277 | bool groupKey = false; | |
278 | try | |
b1ab9ed8 | 279 | { |
e3d460c9 A |
280 | // fetch the key label attribute, if it exists |
281 | dbAttributes.add(KeySchema::Label); | |
282 | Db db((*mCurrent)->database()); | |
283 | CSSM_RETURN getattr_result = CSSM_DL_DataGetFromUniqueRecordId(db->handle(), uniqueId, &dbAttributes, NULL); | |
284 | if (getattr_result == CSSM_OK) | |
285 | { | |
286 | CssmDbAttributeData *label = dbAttributes.find(KeySchema::Label); | |
287 | CssmData attrData; | |
288 | if (label) | |
289 | attrData = *label; | |
290 | if (attrData.length() > 4 && !memcmp(attrData.data(), "ssgp", 4)) | |
291 | groupKey = true; | |
292 | } | |
293 | else | |
294 | { | |
295 | dbAttributes.invalidate(); | |
296 | } | |
b1ab9ed8 | 297 | } |
e3d460c9 | 298 | catch (...) {} |
b1ab9ed8 | 299 | |
e3d460c9 A |
300 | if (groupKey) |
301 | continue; | |
302 | } | |
303 | ||
304 | // Create the Item | |
305 | // Go though Keychain since item might already exist. | |
306 | // This might throw a CSSMERR_DL_RECORD_NOT_FOUND or be otherwise invalid. If we're supposed to delete these items, delete them... | |
307 | try { | |
308 | tempItem = (*mCurrent)->item(dbAttributes.recordType(), uniqueId); | |
309 | } catch(CssmError cssme) { | |
310 | if (mDeleteInvalidRecords) { | |
311 | // This is an invalid record for some reason; delete it and restart the loop | |
312 | const char* errStr = cssmErrorString(cssme.error); | |
fa7225c8 | 313 | secnotice("integrity", "deleting corrupt record because: %d %s", (int) cssme.error, errStr); |
e3d460c9 A |
314 | |
315 | deleteInvalidRecord(uniqueId); | |
316 | // if deleteInvalidRecord doesn't throw, we want to restart the loop | |
317 | continue; | |
318 | } else { | |
319 | throw; | |
320 | } | |
321 | } | |
b1ab9ed8 | 322 | } |
e3d460c9 | 323 | |
e3d460c9 | 324 | item = tempItem; |
b1ab9ed8 A |
325 | |
326 | break; | |
327 | } | |
328 | ||
e3d460c9 | 329 | // If we reach here, we've found an item |
b1ab9ed8 A |
330 | return true; |
331 | } | |
427c49bc | 332 | |
e3d460c9 A |
333 | void KCCursorImpl::deleteInvalidRecord(DbUniqueRecord& uniqueId) { |
334 | // This might throw a RECORD_NOT_FOUND. Handle that, because we don't care if we're trying to delete the item. | |
335 | try { | |
336 | uniqueId->deleteRecord(); | |
337 | } catch(CssmError delcssme) { | |
338 | if (delcssme.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND) { | |
fa7225c8 | 339 | secnotice("integrity", "couldn't delete nonexistent record (this is okay)"); |
e3d460c9 A |
340 | } else { |
341 | throw; | |
342 | } | |
343 | } | |
344 | } | |
345 | ||
427c49bc A |
346 | |
347 | ||
348 | bool KCCursorImpl::mayDelete() | |
349 | { | |
350 | if (mDbCursor.get() != NULL) | |
351 | { | |
352 | return mDbCursor->isIdle(); | |
353 | } | |
354 | else | |
355 | { | |
356 | return true; | |
357 | } | |
358 | } | |
e3d460c9 A |
359 | |
360 | void KCCursorImpl::setDeleteInvalidRecords(bool deleteRecord) { | |
361 | mDeleteInvalidRecords = deleteRecord; | |
362 | } | |
363 | ||
364 | void KCCursorImpl::newKeychain(StorageManager::KeychainList::iterator kcIter) { | |
fa7225c8 A |
365 | if(!mIsNewKeychain) { |
366 | // We've already been called on this keychain, don't bother. | |
367 | return; | |
368 | } | |
369 | ||
e3d460c9 A |
370 | if(kcIter != mSearchList.end()) { |
371 | (*kcIter)->performKeychainUpgradeIfNeeded(); | |
fa7225c8 | 372 | (*kcIter)->tickle(); |
e3d460c9 | 373 | } |
fa7225c8 A |
374 | |
375 | // Mark down that this function has been called | |
376 | mIsNewKeychain = false; | |
e3d460c9 | 377 | } |