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