]> git.saurik.com Git - apple/security.git/blob - Keychain/Keychains.cpp
Security-54.tar.gz
[apple/security.git] / Keychain / Keychains.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 //
20 // Keychains.cpp
21 //
22
23 #include "Keychains.h"
24 #include "KCEventNotifier.h"
25
26 #include "Item.h"
27 #include "KCCursor.h"
28 #include "Globals.h"
29 #include "Schema.h"
30 #include <Security/keychainacl.h>
31 #include <Security/cssmacl.h>
32 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
33 #include <Security/cssmdb.h>
34 #include <Security/trackingallocator.h>
35 #include <Security/SecCFTypes.h>
36
37 using namespace KeychainCore;
38 using namespace CssmClient;
39
40
41 //
42 // KeychainSchemaImpl
43 //
44 KeychainSchemaImpl::KeychainSchemaImpl(const Db &db)
45 {
46 DbCursor relations(db);
47 relations->recordType(CSSM_DL_DB_SCHEMA_INFO);
48 DbAttributes relationRecord(db, 1);
49 relationRecord.add(Schema::RelationID);
50 DbUniqueRecord outerUniqueId(db);
51
52 while (relations->next(&relationRecord, NULL, outerUniqueId))
53 {
54 DbUniqueRecord uniqueId(db);
55
56 uint32 relationID = relationRecord.at(0);
57 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END)
58 continue;
59
60 // Create a cursor on the SCHEMA_ATTRIBUTES table for records with RelationID == relationID
61 DbCursor attributes(db);
62 attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES);
63 attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
64
65 // Set up a record for retriving the SCHEMA_ATTRIBUTES
66 DbAttributes attributeRecord(db, 2);
67 attributeRecord.add(Schema::AttributeFormat);
68 attributeRecord.add(Schema::AttributeID);
69 attributeRecord.add(Schema::AttributeNameFormat);
70
71
72 RelationInfoMap &rim = mDatabaseInfoMap[relationID];
73 while (attributes->next(&attributeRecord, NULL, uniqueId))
74 {
75 // @@@ this if statement was blocking tags of different naming conventions
76 //if(CSSM_DB_ATTRIBUTE_FORMAT(attributeRecord.at(2))==CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER)
77 rim[attributeRecord.at(1)] = attributeRecord.at(0);
78 }
79
80 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records with RelationID == relationID
81 DbCursor indexes(db);
82 indexes->recordType(CSSM_DL_DB_SCHEMA_INDEXES);
83 indexes->conjunctive(CSSM_DB_AND);
84 indexes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
85 indexes->add(CSSM_DB_EQUAL, Schema::IndexType, uint32(CSSM_DB_INDEX_UNIQUE));
86
87 // Set up a record for retriving the SCHEMA_INDEXES
88 DbAttributes indexRecord(db, 1);
89 indexRecord.add(Schema::AttributeID);
90
91 CssmAutoDbRecordAttributeInfo &infos = *new CssmAutoDbRecordAttributeInfo();
92 mPrimaryKeyInfoMap.insert(PrimaryKeyInfoMap::value_type(relationID, &infos));
93 infos.DataRecordType = relationID;
94 while (indexes->next(&indexRecord, NULL, uniqueId))
95 {
96 CssmDbAttributeInfo &info = infos.add();
97 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
98 info.Label.AttributeID = indexRecord.at(0);
99 info.AttributeFormat = rim[info.Label.AttributeID]; // @@@ Might insert bogus value if DB is corrupt
100 }
101 }
102 }
103
104 KeychainSchemaImpl::~KeychainSchemaImpl()
105 {
106 for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end());
107 }
108
109 const KeychainSchemaImpl::RelationInfoMap &
110 KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType) const
111 {
112 DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType);
113 if (dit == mDatabaseInfoMap.end())
114 MacOSError::throwMe(errSecNoSuchClass);
115 return dit->second;
116 }
117
118 bool
119 KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
120 {
121 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
122 RelationInfoMap::const_iterator rit = rmap.find(attributeId);
123 return rit != rmap.end();
124 }
125
126 CSSM_DB_ATTRIBUTE_FORMAT
127 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
128 {
129 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
130 RelationInfoMap::const_iterator rit = rmap.find(attributeId);
131 if (rit == rmap.end())
132 MacOSError::throwMe(errSecNoSuchAttr);
133
134 return rit->second;
135 }
136
137 CssmDbAttributeInfo
138 KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
139 {
140 CSSM_DB_ATTRIBUTE_INFO info;
141 info.AttributeFormat = attributeFormatFor(recordType, attributeId);
142 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
143 info.Label.AttributeID = attributeId;
144
145 return info;
146 }
147
148 void
149 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) const
150 {
151 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
152
153 SecKeychainAttributeInfo *theList=reinterpret_cast<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo)));
154
155 UInt32 capacity=rmap.size();
156 UInt32 *tagBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
157 UInt32 *formatBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
158 UInt32 i=0;
159
160
161 for (RelationInfoMap::const_iterator rit = rmap.begin(); rit != rmap.end(); ++rit)
162 {
163 if (i>=capacity)
164 {
165 capacity *= 2;
166 if (capacity <= i) capacity = i + 1;
167 tagBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
168 formatBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
169 }
170 tagBuf[i]=rit->first;
171 formatBuf[i++]=rit->second;
172 }
173
174 theList->count=i;
175 theList->tag=tagBuf;
176 theList->format=formatBuf;
177 *Info=theList;
178 }
179
180
181 const CssmAutoDbRecordAttributeInfo &
182 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) const
183 {
184 PrimaryKeyInfoMap::const_iterator it;
185 it = mPrimaryKeyInfoMap.find(recordType);
186
187 if (it == mPrimaryKeyInfoMap.end())
188 MacOSError::throwMe(errSecNoSuchClass); // @@@ Not really but whatever.
189
190 return *it->second;
191 }
192
193 bool
194 KeychainSchemaImpl::operator <(const KeychainSchemaImpl &other) const
195 {
196 return mDatabaseInfoMap < other.mDatabaseInfoMap;
197 }
198
199 bool
200 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl &other) const
201 {
202 return mDatabaseInfoMap == other.mDatabaseInfoMap;
203 }
204
205
206 //
207 // KeychainImpl
208 //
209 KeychainImpl::KeychainImpl(const Db &db)
210 : mDb(db)
211 {
212 }
213
214 KeychainImpl::~KeychainImpl()
215 {
216 }
217
218 bool
219 KeychainImpl::operator ==(const KeychainImpl &keychain) const
220 {
221 return dLDbIdentifier() == keychain.dLDbIdentifier();
222 }
223
224 KCCursor
225 KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList)
226 {
227 StorageManager::KeychainList keychains;
228 keychains.push_back(Keychain(this));
229 return KCCursor(keychains, itemClass, attrList);
230 }
231
232 KCCursor
233 KeychainImpl::createCursor(const SecKeychainAttributeList *attrList)
234 {
235 StorageManager::KeychainList keychains;
236 keychains.push_back(Keychain(this));
237 return KCCursor(keychains, attrList);
238 }
239
240 void
241 KeychainImpl::create(UInt32 passwordLength, const void *inPassword)
242 {
243 if (!inPassword)
244 {
245 create();
246 return;
247 }
248
249 CssmAllocator &alloc = CssmAllocator::standard();
250
251 // @@@ Share this instance
252
253 const CssmData password(const_cast<void *>(inPassword), passwordLength);
254 AclFactory::PasswordChangeCredentials pCreds (password, alloc);
255 const AccessCredentials* aa = pCreds;
256
257 // @@@ Create a nice wrapper for building the default AclEntryPrototype.
258 TypedList subject(alloc, CSSM_ACL_SUBJECT_TYPE_ANY);
259 AclEntryPrototype protoType(subject);
260 AuthorizationGroup &authGroup = protoType.authorization();
261 CSSM_ACL_AUTHORIZATION_TAG tag = CSSM_ACL_AUTHORIZATION_ANY;
262 authGroup.NumberOfAuthTags = 1;
263 authGroup.AuthTags = &tag;
264
265 const ResourceControlContext rcc(protoType, const_cast<AccessCredentials *>(aa));
266 create(&rcc);
267 }
268
269 void KeychainImpl::create(ConstStringPtr inPassword)
270 {
271 if ( inPassword )
272 create(static_cast<UInt32>(inPassword[0]), &inPassword[1]);
273 else
274 create();
275 }
276
277 void
278 KeychainImpl::create()
279 {
280 CssmAllocator &alloc = CssmAllocator::standard();
281 // @@@ Share this instance
282 #ifdef OBSOLETE
283 KeychainAclFactory aclFactory(alloc);
284
285 const AccessCredentials *cred = aclFactory.keychainPromptUnlockCredentials();
286 #endif
287 AclFactory aclFactor;
288 const AccessCredentials *cred = aclFactor.unlockCred ();
289
290 // @@@ Create a nice wrapper for building the default AclEntryPrototype.
291 TypedList subject(alloc, CSSM_ACL_SUBJECT_TYPE_ANY);
292 AclEntryPrototype protoType(subject);
293 AuthorizationGroup &authGroup = protoType.authorization();
294 CSSM_ACL_AUTHORIZATION_TAG tag = CSSM_ACL_AUTHORIZATION_ANY;
295 authGroup.NumberOfAuthTags = 1;
296 authGroup.AuthTags = &tag;
297
298 const ResourceControlContext rcc(protoType, const_cast<AccessCredentials *>(cred));
299 create(&rcc);
300 }
301
302 void
303 KeychainImpl::create(const ResourceControlContext *rcc)
304 {
305 mDb->dbInfo(&Schema::DBInfo); // Set the schema (to force a create)
306 mDb->resourceControlContext(rcc);
307 try
308 {
309 mDb->create();
310 }
311 catch (...)
312 {
313 mDb->resourceControlContext(NULL);
314 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
315 throw;
316 }
317 mDb->resourceControlContext(NULL);
318 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
319 globals().storageManager.created(Keychain(this));
320 }
321
322 void
323 KeychainImpl::open()
324 {
325 mDb->open();
326 }
327
328 void
329 KeychainImpl::lock()
330 {
331 mDb->lock();
332 }
333
334 void
335 KeychainImpl::unlock()
336 {
337 mDb->unlock();
338 }
339
340 void
341 KeychainImpl::unlock(const CssmData &password)
342 {
343 mDb->unlock(password);
344 }
345
346 void
347 KeychainImpl::unlock(ConstStringPtr password)
348 {
349 if (password)
350 {
351 const CssmData data(const_cast<unsigned char *>(&password[1]), password[0]);
352 unlock(data);
353 }
354 else
355 unlock();
356 }
357
358 void
359 KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep)
360 {
361 mDb->getSettings(outIdleTimeOut, outLockOnSleep);
362 }
363
364 void
365 KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep)
366 {
367 mDb->setSettings(inIdleTimeOut, inLockOnSleep);
368 }
369 void
370 KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword,
371 UInt32 newPasswordLength, const void *newPassword)
372 {
373 // @@@ When AutoCredentials is actually finished we should no logner use a tracking allocator.
374 TrackingAllocator allocator(CssmAllocator::standard());
375 AutoCredentials cred = AutoCredentials(allocator);
376 if (oldPassword)
377 {
378 const CssmData &oldPass = *new(allocator) CssmData(const_cast<void *>(oldPassword), oldPasswordLength);
379 TypedList &oldList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK);
380 oldList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD));
381 oldList.append(new(allocator) ListElement(oldPass));
382 cred += oldList;
383 }
384
385 if (newPassword)
386 {
387 const CssmData &newPass = *new(allocator) CssmData(const_cast<void *>(newPassword), newPasswordLength);
388 TypedList &newList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK);
389 newList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD));
390 newList.append(new(allocator) ListElement(newPass));
391 cred += newList;
392 }
393
394 mDb->changePassphrase(&cred);
395 }
396
397 void
398 KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword)
399 {
400 const void *oldPtr, *newPtr;
401 UInt32 oldLen, newLen;
402 if (oldPassword)
403 {
404 oldLen = oldPassword[0];
405 oldPtr = oldPassword + 1;
406 }
407 else
408 {
409 oldLen = 0;
410 oldPtr = NULL;
411 }
412
413 if (newPassword)
414 {
415 newLen = newPassword[0];
416 newPtr = newPassword + 1;
417 }
418 else
419 {
420 newLen = 0;
421 newPtr = NULL;
422 }
423
424 changePassphrase(oldLen, oldPtr, newLen, newPtr);
425 }
426
427 void
428 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS *cred)
429 {
430 // @@@ This should do an authenticate which is not the same as unlock.
431 if (!exists())
432 MacOSError::throwMe(errSecNoSuchKeychain);
433
434 MacOSError::throwMe(unimpErr);
435 }
436
437 UInt32
438 KeychainImpl::status() const
439 {
440 // @@@ We should figure out the read/write status though a DL passthrough or some other way.
441 // @@@ Also should locked be unlocked read only or just read-only?
442 return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus) | kSecReadPermStatus;
443 }
444
445 bool
446 KeychainImpl::exists()
447 {
448 bool exists = true;
449 try
450 {
451 open();
452 // Ok to leave the mDb open since it will get closed when it goes away.
453 }
454 catch (const CssmError &e)
455 {
456 if (e.cssmError() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST)
457 throw;
458 exists = false;
459 }
460
461 return exists;
462 }
463
464 bool
465 KeychainImpl::isActive() const
466 {
467 return mDb->isActive();
468 }
469
470 void
471 KeychainImpl::add(Item &inItem)
472 {
473 Keychain keychain(this);
474 PrimaryKey primaryKey = inItem->add(keychain);
475 {
476 StLock<Mutex> _(mDbItemMapLock);
477 mDbItemMap[primaryKey] = inItem.get();
478 }
479
480 KCEventNotifier::PostKeychainEvent(kSecAddEvent, this, inItem);
481 }
482
483 void
484 KeychainImpl::didUpdate(ItemImpl *inItemImpl, PrimaryKey &oldPK,
485 PrimaryKey &newPK)
486 {
487 // Make sure we only hold mDbItemMapLock as long as we need to.
488 {
489 StLock<Mutex> _(mDbItemMapLock);
490 DbItemMap::iterator it = mDbItemMap.find(oldPK);
491 if (it != mDbItemMap.end() && it->second == inItemImpl)
492 mDbItemMap.erase(it);
493 mDbItemMap[newPK] = inItemImpl;
494 }
495
496 KCEventNotifier::PostKeychainEvent( kSecUpdateEvent, this, inItemImpl );
497 }
498
499 void
500 KeychainImpl::deleteItem(Item &inoutItem)
501 {
502 // item must be persistant.
503 if (!inoutItem->isPersistant())
504 MacOSError::throwMe(errSecInvalidItemRef);
505
506 DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord();
507 PrimaryKey primaryKey = inoutItem->primaryKey();
508 uniqueId->deleteRecord();
509
510 // Don't kill the ref or clear the Item() since this potentially
511 // messes up things for the receiver of the kSecDeleteEvent notification.
512 //inoutItem->killRef();
513 //inoutItem = Item();
514
515 // Post the notification for the item deletion with
516 // the primaryKey obtained when the item still existed
517 KCEventNotifier::PostKeychainEvent(kSecDeleteEvent, dLDbIdentifier(), primaryKey);
518 }
519
520
521 CssmClient::CSP
522 KeychainImpl::csp()
523 {
524 if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP)
525 MacOSError::throwMe(errSecInvalidKeychain);
526
527 SSDb ssDb(safe_cast<SSDbImpl *>(&(*mDb)));
528 return ssDb->csp();
529 }
530
531 PrimaryKey
532 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
533 {
534 DbAttributes primaryKeyAttrs(uniqueId->database());
535 primaryKeyAttrs.recordType(recordType);
536 gatherPrimaryKeyAttributes(primaryKeyAttrs);
537 uniqueId->get(&primaryKeyAttrs, NULL);
538 return PrimaryKey(primaryKeyAttrs);
539 }
540
541 const CssmAutoDbRecordAttributeInfo &
542 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType)
543 {
544 return keychainSchema()->primaryKeyInfosFor(recordType);
545 }
546
547 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs)
548 {
549 const CssmAutoDbRecordAttributeInfo &infos =
550 primaryKeyInfosFor(primaryKeyAttrs.recordType());
551
552 // @@@ fix this to not copy info.
553 for (uint32 i = 0; i < infos.size(); i++)
554 primaryKeyAttrs.add(infos.at(i));
555 }
556
557 Item
558 KeychainImpl::item(const PrimaryKey& primaryKey)
559 {
560 {
561 StLock<Mutex> _(mDbItemMapLock);
562 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
563 if (it != mDbItemMap.end())
564 {
565 return Item(it->second);
566 }
567 }
568
569 // Create an item with just a primary key
570 return Item(this, primaryKey);
571 }
572
573 Item
574 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
575 {
576 PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId);
577 {
578 StLock<Mutex> _(mDbItemMapLock);
579 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
580 if (it != mDbItemMap.end())
581 {
582 return Item(it->second);
583 }
584 }
585
586 // Create a new item
587 return Item(this, primaryKey, uniqueId);
588 }
589
590 KeychainSchema
591 KeychainImpl::keychainSchema()
592 {
593 if (!mKeychainSchema)
594 {
595 // @@@ Use cache in storageManager
596 mKeychainSchema = KeychainSchema(mDb);
597 }
598
599 return mKeychainSchema;
600 }
601
602 // Called from DbItemImpl's constructor (so it is only paritally constructed), add it to the map.
603 void
604 KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl)
605 {
606 StLock<Mutex> _(mDbItemMapLock);
607 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
608 if (it != mDbItemMap.end())
609 {
610 // @@@ There is a race condition here when being called in multiple threads
611 // We might have added an item using add and received a notification at the same time
612 //assert(true);
613 throw errSecDuplicateItem;
614 //mDbItemMap.erase(it);
615 // @@@ What to do here?
616 }
617
618 mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl));
619 }
620
621 void
622 KeychainImpl::removeItem(const PrimaryKey &primaryKey, const ItemImpl *inItemImpl)
623 {
624 // Sent from DbItemImpl's destructor, remove it from the map.
625 StLock<Mutex> _(mDbItemMapLock);
626 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
627 if (it != mDbItemMap.end() && it->second == inItemImpl)
628 mDbItemMap.erase(it);
629 }
630
631 void
632 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID, SecKeychainAttributeInfo **Info)
633 {
634 keychainSchema()->getAttributeInfoForRecordType(itemID, Info);
635 }
636
637 void
638 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info)
639 {
640 free(Info->tag);
641 free(Info->format);
642 free(Info);
643 }
644
645 CssmDbAttributeInfo
646 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, UInt32 tag)
647 {
648 return keychainSchema()->attributeInfoFor(recordType, tag);
649
650 }
651
652 Keychain
653 Keychain::optional(SecKeychainRef handle)
654 {
655 if (handle)
656 return gTypes().keychain.required(handle);
657 else
658 return globals().defaultKeychain;
659 }
660