2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
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
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.
23 #include "Keychains.h"
24 #include "KCEventNotifier.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>
37 using namespace KeychainCore
;
38 using namespace CssmClient
;
44 KeychainSchemaImpl::KeychainSchemaImpl(const Db
&db
)
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
);
52 while (relations
->next(&relationRecord
, NULL
, outerUniqueId
))
54 DbUniqueRecord
uniqueId(db
);
56 uint32 relationID
= relationRecord
.at(0);
57 if (CSSM_DB_RECORDTYPE_SCHEMA_START
<= relationID
&& relationID
< CSSM_DB_RECORDTYPE_SCHEMA_END
)
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
);
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
);
72 RelationInfoMap
&rim
= mDatabaseInfoMap
[relationID
];
73 while (attributes
->next(&attributeRecord
, NULL
, uniqueId
))
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);
80 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records with RelationID == relationID
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
));
87 // Set up a record for retriving the SCHEMA_INDEXES
88 DbAttributes
indexRecord(db
, 1);
89 indexRecord
.add(Schema::AttributeID
);
91 CssmAutoDbRecordAttributeInfo
&infos
= *new CssmAutoDbRecordAttributeInfo();
92 mPrimaryKeyInfoMap
.insert(PrimaryKeyInfoMap::value_type(relationID
, &infos
));
93 infos
.DataRecordType
= relationID
;
94 while (indexes
->next(&indexRecord
, NULL
, uniqueId
))
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
104 KeychainSchemaImpl::~KeychainSchemaImpl()
106 for_each_map_delete(mPrimaryKeyInfoMap
.begin(), mPrimaryKeyInfoMap
.end());
109 const KeychainSchemaImpl::RelationInfoMap
&
110 KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType
) const
112 DatabaseInfoMap::const_iterator dit
= mDatabaseInfoMap
.find(recordType
);
113 if (dit
== mDatabaseInfoMap
.end())
114 MacOSError::throwMe(errSecNoSuchClass
);
119 KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
121 const RelationInfoMap
&rmap
= relationInfoMapFor(recordType
);
122 RelationInfoMap::const_iterator rit
= rmap
.find(attributeId
);
123 return rit
!= rmap
.end();
126 CSSM_DB_ATTRIBUTE_FORMAT
127 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
129 const RelationInfoMap
&rmap
= relationInfoMapFor(recordType
);
130 RelationInfoMap::const_iterator rit
= rmap
.find(attributeId
);
131 if (rit
== rmap
.end())
132 MacOSError::throwMe(errSecNoSuchAttr
);
138 KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
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
;
149 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType
, SecKeychainAttributeInfo
**Info
) const
151 const RelationInfoMap
&rmap
= relationInfoMapFor(recordType
);
153 SecKeychainAttributeInfo
*theList
=reinterpret_cast<SecKeychainAttributeInfo
*>(malloc(sizeof(SecKeychainAttributeInfo
)));
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
)));
161 for (RelationInfoMap::const_iterator rit
= rmap
.begin(); rit
!= rmap
.end(); ++rit
)
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
))));
170 tagBuf
[i
]=rit
->first
;
171 formatBuf
[i
++]=rit
->second
;
176 theList
->format
=formatBuf
;
181 const CssmAutoDbRecordAttributeInfo
&
182 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
) const
184 PrimaryKeyInfoMap::const_iterator it
;
185 it
= mPrimaryKeyInfoMap
.find(recordType
);
187 if (it
== mPrimaryKeyInfoMap
.end())
188 MacOSError::throwMe(errSecNoSuchClass
); // @@@ Not really but whatever.
194 KeychainSchemaImpl::operator <(const KeychainSchemaImpl
&other
) const
196 return mDatabaseInfoMap
< other
.mDatabaseInfoMap
;
200 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl
&other
) const
202 return mDatabaseInfoMap
== other
.mDatabaseInfoMap
;
209 KeychainImpl::KeychainImpl(const Db
&db
)
214 KeychainImpl::~KeychainImpl() throw()
216 globals().storageManager
.removeKeychain(dLDbIdentifier(), this);
220 KeychainImpl::operator ==(const KeychainImpl
&keychain
) const
222 return dLDbIdentifier() == keychain
.dLDbIdentifier();
226 KeychainImpl::createCursor(SecItemClass itemClass
, const SecKeychainAttributeList
*attrList
)
228 StorageManager::KeychainList keychains
;
229 keychains
.push_back(Keychain(this));
230 return KCCursor(keychains
, itemClass
, attrList
);
234 KeychainImpl::createCursor(const SecKeychainAttributeList
*attrList
)
236 StorageManager::KeychainList keychains
;
237 keychains
.push_back(Keychain(this));
238 return KCCursor(keychains
, attrList
);
242 KeychainImpl::create(UInt32 passwordLength
, const void *inPassword
)
250 CssmAllocator
&alloc
= CssmAllocator::standard();
252 // @@@ Share this instance
254 const CssmData
password(const_cast<void *>(inPassword
), passwordLength
);
255 AclFactory::PasswordChangeCredentials
pCreds (password
, alloc
);
256 AclFactory::AnyResourceContext
rcc(pCreds
);
260 void KeychainImpl::create(ConstStringPtr inPassword
)
263 create(static_cast<UInt32
>(inPassword
[0]), &inPassword
[1]);
269 KeychainImpl::create()
271 AclFactory aclFactory
;
272 AclFactory::AnyResourceContext
rcc(aclFactory
.unlockCred());
277 KeychainImpl::create(const ResourceControlContext
*rcc
)
279 mDb
->dbInfo(&Schema::DBInfo
); // Set the schema (to force a create)
280 mDb
->resourceControlContext(rcc
);
287 mDb
->resourceControlContext(NULL
);
288 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
291 mDb
->resourceControlContext(NULL
);
292 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
293 globals().storageManager
.created(Keychain(this));
295 KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent
, this, NULL
);
311 KeychainImpl::unlock()
317 KeychainImpl::unlock(const CssmData
&password
)
319 mDb
->unlock(password
);
323 KeychainImpl::unlock(ConstStringPtr password
)
327 const CssmData
data(const_cast<unsigned char *>(&password
[1]), password
[0]);
335 KeychainImpl::getSettings(uint32
&outIdleTimeOut
, bool &outLockOnSleep
)
337 mDb
->getSettings(outIdleTimeOut
, outLockOnSleep
);
341 KeychainImpl::setSettings(uint32 inIdleTimeOut
, bool inLockOnSleep
)
343 mDb
->setSettings(inIdleTimeOut
, inLockOnSleep
);
346 KeychainImpl::changePassphrase(UInt32 oldPasswordLength
, const void *oldPassword
,
347 UInt32 newPasswordLength
, const void *newPassword
)
349 // @@@ When AutoCredentials is actually finished we should no logner use a tracking allocator.
350 TrackingAllocator
allocator(CssmAllocator::standard());
351 AutoCredentials cred
= AutoCredentials(allocator
);
354 const CssmData
&oldPass
= *new(allocator
) CssmData(const_cast<void *>(oldPassword
), oldPasswordLength
);
355 TypedList
&oldList
= *new(allocator
) TypedList(allocator
, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK
);
356 oldList
.append(new(allocator
) ListElement(CSSM_SAMPLE_TYPE_PASSWORD
));
357 oldList
.append(new(allocator
) ListElement(oldPass
));
363 const CssmData
&newPass
= *new(allocator
) CssmData(const_cast<void *>(newPassword
), newPasswordLength
);
364 TypedList
&newList
= *new(allocator
) TypedList(allocator
, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK
);
365 newList
.append(new(allocator
) ListElement(CSSM_SAMPLE_TYPE_PASSWORD
));
366 newList
.append(new(allocator
) ListElement(newPass
));
370 mDb
->changePassphrase(&cred
);
374 KeychainImpl::changePassphrase(ConstStringPtr oldPassword
, ConstStringPtr newPassword
)
376 const void *oldPtr
, *newPtr
;
377 UInt32 oldLen
, newLen
;
380 oldLen
= oldPassword
[0];
381 oldPtr
= oldPassword
+ 1;
391 newLen
= newPassword
[0];
392 newPtr
= newPassword
+ 1;
400 changePassphrase(oldLen
, oldPtr
, newLen
, newPtr
);
404 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS
*cred
)
406 // @@@ This should do an authenticate which is not the same as unlock.
408 MacOSError::throwMe(errSecNoSuchKeychain
);
410 MacOSError::throwMe(unimpErr
);
414 KeychainImpl::status() const
416 // @@@ We should figure out the read/write status though a DL passthrough or some other way.
417 // @@@ Also should locked be unlocked read only or just read-only?
418 return (mDb
->isLocked() ? 0 : kSecUnlockStateStatus
| kSecWritePermStatus
) | kSecReadPermStatus
;
422 KeychainImpl::exists()
428 // Ok to leave the mDb open since it will get closed when it goes away.
430 catch (const CssmError
&e
)
432 if (e
.cssmError() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST
)
441 KeychainImpl::isActive() const
443 return mDb
->isActive();
447 KeychainImpl::add(Item
&inItem
)
449 Keychain
keychain(this);
450 PrimaryKey primaryKey
= inItem
->add(keychain
);
452 StLock
<Mutex
> _(mDbItemMapLock
);
453 mDbItemMap
[primaryKey
] = inItem
.get();
456 KCEventNotifier::PostKeychainEvent(kSecAddEvent
, this, inItem
);
460 KeychainImpl::didUpdate(ItemImpl
*inItemImpl
, PrimaryKey
&oldPK
,
463 // Make sure we only hold mDbItemMapLock as long as we need to.
465 StLock
<Mutex
> _(mDbItemMapLock
);
466 DbItemMap::iterator it
= mDbItemMap
.find(oldPK
);
467 if (it
!= mDbItemMap
.end() && it
->second
== inItemImpl
)
468 mDbItemMap
.erase(it
);
469 mDbItemMap
[newPK
] = inItemImpl
;
472 KCEventNotifier::PostKeychainEvent( kSecUpdateEvent
, this, inItemImpl
);
476 KeychainImpl::deleteItem(Item
&inoutItem
)
478 // item must be persistant.
479 if (!inoutItem
->isPersistant())
480 MacOSError::throwMe(errSecInvalidItemRef
);
482 DbUniqueRecord uniqueId
= inoutItem
->dbUniqueRecord();
483 PrimaryKey primaryKey
= inoutItem
->primaryKey();
484 uniqueId
->deleteRecord();
486 // Don't kill the ref or clear the Item() since this potentially
487 // messes up things for the receiver of the kSecDeleteEvent notification.
488 //inoutItem->killRef();
489 //inoutItem = Item();
491 // Post the notification for the item deletion with
492 // the primaryKey obtained when the item still existed
493 KCEventNotifier::PostKeychainEvent(kSecDeleteEvent
, dLDbIdentifier(), primaryKey
);
500 if (!mDb
->dl()->subserviceMask() & CSSM_SERVICE_CSP
)
501 MacOSError::throwMe(errSecInvalidKeychain
);
503 SSDb
ssDb(safe_cast
<SSDbImpl
*>(&(*mDb
)));
508 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
510 DbAttributes
primaryKeyAttrs(uniqueId
->database());
511 primaryKeyAttrs
.recordType(recordType
);
512 gatherPrimaryKeyAttributes(primaryKeyAttrs
);
513 uniqueId
->get(&primaryKeyAttrs
, NULL
);
514 return PrimaryKey(primaryKeyAttrs
);
517 const CssmAutoDbRecordAttributeInfo
&
518 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
)
521 return keychainSchema()->primaryKeyInfosFor(recordType
);
522 } catch (const CssmCommonError
&error
) {
523 switch (error
.cssmError()) {
524 case errSecNoSuchClass
:
525 case CSSMERR_DL_INVALID_RECORDTYPE
:
527 return keychainSchema()->primaryKeyInfosFor(recordType
);
534 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes
& primaryKeyAttrs
)
536 const CssmAutoDbRecordAttributeInfo
&infos
=
537 primaryKeyInfosFor(primaryKeyAttrs
.recordType());
539 // @@@ fix this to not copy info.
540 for (uint32 i
= 0; i
< infos
.size(); i
++)
541 primaryKeyAttrs
.add(infos
.at(i
));
545 KeychainImpl::item(const PrimaryKey
& primaryKey
)
547 // @@@ This retry code isn't really the right way to do this,
548 // we need to redo the locking structure here in the future.
553 StLock
<Mutex
> _(mDbItemMapLock
);
554 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
555 if (it
!= mDbItemMap
.end())
557 return Item(it
->second
);
563 // Create an item with just a primary key
564 return Item(this, primaryKey
);
566 catch (const MacOSError
&e
)
568 if (tried
|| e
.osStatus() != errSecDuplicateItem
)
576 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
578 PrimaryKey primaryKey
= makePrimaryKey(recordType
, uniqueId
);
580 StLock
<Mutex
> _(mDbItemMapLock
);
581 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
582 if (it
!= mDbItemMap
.end())
584 return Item(it
->second
);
589 return Item(this, primaryKey
, uniqueId
);
593 KeychainImpl::keychainSchema()
595 if (!mKeychainSchema
)
597 // @@@ Use cache in storageManager
598 mKeychainSchema
= KeychainSchema(mDb
);
601 return mKeychainSchema
;
604 void KeychainImpl::resetSchema()
606 mKeychainSchema
= NULL
; // re-fetch it from db next time
610 // Called from DbItemImpl's constructor (so it is only paritally constructed), add it to the map.
612 KeychainImpl::addItem(const PrimaryKey
&primaryKey
, ItemImpl
*dbItemImpl
)
614 StLock
<Mutex
> _(mDbItemMapLock
);
615 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
616 if (it
!= mDbItemMap
.end())
618 // @@@ There is a race condition here when being called in multiple threads
619 // We might have added an item using add and received a notification at the same time
621 MacOSError::throwMe(errSecDuplicateItem
);
622 //mDbItemMap.erase(it);
623 // @@@ What to do here?
626 mDbItemMap
.insert(DbItemMap::value_type(primaryKey
, dbItemImpl
));
630 KeychainImpl::didDeleteItem(const ItemImpl
*inItemImpl
)
632 // Sent sent by CCallbackMgr.
633 secdebug("kcnotify", "%p notified that item %p was deleted", this, inItemImpl
);
634 PrimaryKey primaryKey
= inItemImpl
->primaryKey();
635 StLock
<Mutex
> _(mDbItemMapLock
);
636 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
637 if (it
!= mDbItemMap
.end())
638 mDbItemMap
.erase(it
);
642 KeychainImpl::removeItem(const PrimaryKey
&primaryKey
, const ItemImpl
*inItemImpl
)
644 // Sent from DbItemImpl's destructor, remove it from the map.
645 StLock
<Mutex
> _(mDbItemMapLock
);
646 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
647 if (it
!= mDbItemMap
.end() && it
->second
== inItemImpl
)
648 mDbItemMap
.erase(it
);
652 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID
, SecKeychainAttributeInfo
**Info
)
655 keychainSchema()->getAttributeInfoForRecordType(itemID
, Info
);
656 } catch (const CssmCommonError
&error
) {
657 switch (error
.cssmError()) {
658 case errSecNoSuchClass
:
659 case CSSMERR_DL_INVALID_RECORDTYPE
:
661 keychainSchema()->getAttributeInfoForRecordType(itemID
, Info
);
669 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo
*Info
)
677 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType
, UInt32 tag
)
680 return keychainSchema()->attributeInfoFor(recordType
, tag
);
681 } catch (const CssmCommonError
&error
) {
682 switch (error
.cssmError()) {
683 case errSecNoSuchClass
:
684 case CSSMERR_DL_INVALID_RECORDTYPE
:
686 return keychainSchema()->attributeInfoFor(recordType
, tag
);
695 Keychain::optional(SecKeychainRef handle
)
698 return KeychainImpl::required(handle
);
700 return globals().storageManager
.defaultKeychain();