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>
36 using namespace KeychainCore
;
37 using namespace CssmClient
;
43 KeychainSchemaImpl::KeychainSchemaImpl(const Db
&db
)
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
);
51 while (relations
->next(&relationRecord
, NULL
, outerUniqueId
))
53 DbUniqueRecord
uniqueId(db
);
55 uint32 relationID
= relationRecord
.at(0);
56 if (CSSM_DB_RECORDTYPE_SCHEMA_START
<= relationID
&& relationID
< CSSM_DB_RECORDTYPE_SCHEMA_END
)
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
);
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
);
71 RelationInfoMap
&rim
= mDatabaseInfoMap
[relationID
];
72 while (attributes
->next(&attributeRecord
, NULL
, uniqueId
))
74 if(CSSM_DB_ATTRIBUTE_FORMAT(attributeRecord
.at(2))==CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
)
75 rim
[attributeRecord
.at(1)] = attributeRecord
.at(0);
78 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records with RelationID == relationID
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
));
85 // Set up a record for retriving the SCHEMA_INDEXES
86 DbAttributes
indexRecord(db
, 1);
87 indexRecord
.add(Schema::AttributeID
);
89 CssmAutoDbRecordAttributeInfo
&infos
= *new CssmAutoDbRecordAttributeInfo();
90 mPrimaryKeyInfoMap
.insert(PrimaryKeyInfoMap::value_type(relationID
, &infos
));
91 infos
.DataRecordType
= relationID
;
92 while (indexes
->next(&indexRecord
, NULL
, uniqueId
))
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
102 KeychainSchemaImpl::~KeychainSchemaImpl()
104 for_each_map_delete(mPrimaryKeyInfoMap
.begin(), mPrimaryKeyInfoMap
.end());
107 CSSM_DB_ATTRIBUTE_FORMAT
108 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
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
);
122 KeychainSchemaImpl::attributeInfoForTag(UInt32 tag
)
124 CSSM_DB_ATTRIBUTE_INFO info
;
126 for(DatabaseInfoMap::const_iterator dit
= mDatabaseInfoMap
.begin(); dit
!= mDatabaseInfoMap
.end(); ++dit
)
128 for(RelationInfoMap::const_iterator rit
= dit
->second
.begin(); rit
!= dit
->second
.end(); ++rit
)
132 info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
;
133 info
.Label
.AttributeID
= rit
->first
;
134 info
.AttributeFormat
= rit
->second
;
143 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType
, SecKeychainAttributeInfo
**Info
)
145 DatabaseInfoMap::const_iterator dit
= mDatabaseInfoMap
.find(recordType
);
146 if (dit
== mDatabaseInfoMap
.end())
147 MacOSError::throwMe(errSecNoSuchClass
);
149 SecKeychainAttributeInfo
*theList
=reinterpret_cast<SecKeychainAttributeInfo
*>(malloc(sizeof(SecKeychainAttributeInfo
)));
152 UInt32
*tagBuf
=reinterpret_cast<UInt32
*>(malloc(capacity
*sizeof(UInt32
)));
153 UInt32
*formatBuf
=reinterpret_cast<UInt32
*>(malloc(capacity
*sizeof(UInt32
)));
156 for(RelationInfoMap::const_iterator rit
= dit
->second
.begin(); rit
!= dit
->second
.end(); ++rit
)
161 tagBuf
=reinterpret_cast<UInt32
*>(realloc(tagBuf
, (capacity
*sizeof(UInt32
))));
162 formatBuf
=reinterpret_cast<UInt32
*>(realloc(tagBuf
, (capacity
*sizeof(UInt32
))));
164 tagBuf
[i
]=rit
->first
;
165 formatBuf
[i
++]=rit
->second
;
170 theList
->format
=formatBuf
;
175 const CssmAutoDbRecordAttributeInfo
&
176 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
)
178 PrimaryKeyInfoMap::iterator it
;
179 it
= mPrimaryKeyInfoMap
.find(recordType
);
181 // if the primary key attributes have already been determined,
182 // return the cached results
184 if (it
== mPrimaryKeyInfoMap
.end())
185 MacOSError::throwMe(errSecNoSuchClass
); // @@@ Not really but whatever.
191 KeychainSchemaImpl::operator <(const KeychainSchemaImpl
&other
) const
193 return mDatabaseInfoMap
< other
.mDatabaseInfoMap
;
197 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl
&other
) const
199 return mDatabaseInfoMap
== other
.mDatabaseInfoMap
;
206 KeychainImpl::KeychainImpl(const Db
&db
)
211 KeychainImpl::~KeychainImpl()
216 KeychainImpl::createCursor(SecItemClass itemClass
, const SecKeychainAttributeList
*attrList
)
218 return KCCursor(DbCursor(mDb
), itemClass
, attrList
);
222 KeychainImpl::createCursor(const SecKeychainAttributeList
*attrList
)
224 return KCCursor(DbCursor(mDb
), attrList
);
228 KeychainImpl::create(UInt32 passwordLength
, const void *inPassword
)
236 CssmAllocator
&alloc
= CssmAllocator::standard();
237 // @@@ Share this instance
238 KeychainAclFactory
aclFactory(alloc
);
240 // @@@ This leaks the returned credentials
241 const CssmData
password(const_cast<void *>(inPassword
), passwordLength
);
242 const AccessCredentials
*cred
= aclFactory
.passwordChangeCredentials(password
);
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
;
252 const ResourceControlContext
rcc(protoType
, const_cast<AccessCredentials
*>(cred
));
256 void KeychainImpl::create(ConstStringPtr inPassword
)
259 create(static_cast<UInt32
>(inPassword
[0]), &inPassword
[1]);
265 KeychainImpl::create()
267 CssmAllocator
&alloc
= CssmAllocator::standard();
268 // @@@ Share this instance
269 KeychainAclFactory
aclFactory(alloc
);
271 const AccessCredentials
*cred
= aclFactory
.keychainPromptUnlockCredentials();
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
;
281 const ResourceControlContext
rcc(protoType
, const_cast<AccessCredentials
*>(cred
));
286 KeychainImpl::create(const ResourceControlContext
*rcc
)
288 mDb
->dbInfo(&Schema::DBInfo
); // Set the schema (to force a create)
289 mDb
->resourceControlContext(rcc
);
296 mDb
->resourceControlContext(NULL
);
297 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
300 mDb
->resourceControlContext(NULL
);
301 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
302 globals().storageManager
.created(Keychain(this));
318 KeychainImpl::unlock()
324 KeychainImpl::unlock(const CssmData
&password
)
326 mDb
->unlock(password
);
330 KeychainImpl::unlock(ConstStringPtr password
)
334 const CssmData
data(const_cast<unsigned char *>(&password
[1]), password
[0]);
342 KeychainImpl::getSettings(uint32
&outIdleTimeOut
, bool &outLockOnSleep
)
344 mDb
->getSettings(outIdleTimeOut
, outLockOnSleep
);
348 KeychainImpl::setSettings(uint32 inIdleTimeOut
, bool inLockOnSleep
)
350 mDb
->setSettings(inIdleTimeOut
, inLockOnSleep
);
353 KeychainImpl::changePassphrase(UInt32 oldPasswordLength
, const void *oldPassword
,
354 UInt32 newPasswordLength
, const void *newPassword
)
356 // @@@ When AutoCredentials is actually finished we should no logner use a tracking allocator.
357 TrackingAllocator
allocator(CssmAllocator::standard());
358 AutoCredentials cred
= AutoCredentials(allocator
);
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
));
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
));
377 mDb
->changePassphrase(&cred
);
381 KeychainImpl::changePassphrase(ConstStringPtr oldPassword
, ConstStringPtr newPassword
)
383 const void *oldPtr
, *newPtr
;
384 UInt32 oldLen
, newLen
;
387 oldLen
= oldPassword
[0];
388 oldPtr
= oldPassword
+ 1;
398 newLen
= newPassword
[0];
399 newPtr
= newPassword
+ 1;
407 changePassphrase(oldLen
, oldPtr
, newLen
, newPtr
);
411 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS
*cred
)
413 // @@@ This should do an authenticate which is not the same as unlock.
415 MacOSError::throwMe(errSecNoSuchKeychain
);
417 MacOSError::throwMe(unimpErr
);
421 KeychainImpl::status() const
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
;
429 KeychainImpl::exists()
435 // Ok to leave the mDb open since it will get closed when it goes away.
437 catch (const CssmError
&e
)
439 if (e
.cssmError() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST
)
448 KeychainImpl::isActive() const
450 return mDb
->isActive();
454 KeychainImpl::add(Item
&inItem
)
456 PrimaryKey primaryKey
= inItem
->add(this);
458 StLock
<Mutex
> _(mDbItemMapLock
);
459 // Use &* to get the item's Impl.
460 mDbItemMap
[primaryKey
] = &*inItem
;
463 KCEventNotifier::PostKeychainEvent(kSecAddEvent
, this, inItem
);
467 KeychainImpl::didUpdate(ItemImpl
*inItemImpl
, PrimaryKey
&oldPK
,
470 // Make sure we only hold mDbItemMapLock as long as we need to.
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
;
479 KCEventNotifier::PostKeychainEvent( kSecUpdateEvent
, this, inItemImpl
);
483 KeychainImpl::deleteItem(Item
&inoutItem
)
485 // item must be persistant.
486 if (!inoutItem
->isPersistant())
487 MacOSError::throwMe(errSecInvalidItemRef
);
489 DbUniqueRecord uniqueId
= inoutItem
->dbUniqueRecord();
490 PrimaryKey primaryKey
= inoutItem
->primaryKey();
491 uniqueId
->deleteRecord();
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();
498 // Post the notification for the item deletion with
499 // the primaryKey obtained when the item still existed
500 KCEventNotifier::PostKeychainEvent(kSecDeleteEvent
, dLDbIdentifier(), primaryKey
);
504 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
506 DbAttributes
primaryKeyAttrs(uniqueId
->database());
507 primaryKeyAttrs
.recordType(recordType
);
508 gatherPrimaryKeyAttributes(primaryKeyAttrs
);
509 uniqueId
->get(&primaryKeyAttrs
, NULL
);
510 return PrimaryKey(primaryKeyAttrs
);
513 const CssmAutoDbRecordAttributeInfo
&
514 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
)
516 return keychainSchema()->primaryKeyInfosFor(recordType
);
519 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes
& primaryKeyAttrs
)
521 const CssmAutoDbRecordAttributeInfo
&infos
=
522 primaryKeyInfosFor(primaryKeyAttrs
.recordType());
524 // @@@ fix this to not copy info.
525 for (uint32 i
= 0; i
< infos
.size(); i
++)
526 primaryKeyAttrs
.add(infos
.at(i
));
530 KeychainImpl::item(const PrimaryKey
& primaryKey
)
533 StLock
<Mutex
> _(mDbItemMapLock
);
534 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
535 if (it
!= mDbItemMap
.end())
537 return Item(it
->second
);
541 // Create an item with just a primary key
542 return Item(this, primaryKey
);
546 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
548 PrimaryKey primaryKey
= makePrimaryKey(recordType
, uniqueId
);
550 StLock
<Mutex
> _(mDbItemMapLock
);
551 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
552 if (it
!= mDbItemMap
.end())
554 return Item(it
->second
);
559 return Item(this, primaryKey
, uniqueId
);
563 KeychainImpl::keychainSchema()
565 if (!mKeychainSchema
)
567 // @@@ Use cache in storageManager
568 mKeychainSchema
= KeychainSchema(mDb
);
571 return mKeychainSchema
;
574 // Called from DbItemImpl's constructor (so it is only paritally constructed), add it to the map.
576 KeychainImpl::addItem(const PrimaryKey
&primaryKey
, ItemImpl
*dbItemImpl
)
578 StLock
<Mutex
> _(mDbItemMapLock
);
579 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
580 if (it
!= mDbItemMap
.end())
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
585 throw errSecDuplicateItem
;
586 //mDbItemMap.erase(it);
587 // @@@ What to do here?
590 mDbItemMap
.insert(DbItemMap::value_type(primaryKey
, dbItemImpl
));
594 KeychainImpl::removeItem(const PrimaryKey
&primaryKey
, const ItemImpl
*inItemImpl
)
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
);
604 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID
, SecKeychainAttributeInfo
**Info
)
606 keychainSchema()->getAttributeInfoForRecordType(itemID
, Info
);
610 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo
*Info
)
618 KeychainImpl::attributeInfoForTag(UInt32 tag
)
620 return keychainSchema()->attributeInfoForTag(tag
);
625 Keychain::optional(SecKeychainRef handle
)
628 return KeychainRef::required(handle
);
630 return globals().defaultKeychain
;