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()
219 KeychainImpl::operator ==(const KeychainImpl
&keychain
) const
221 return dLDbIdentifier() == keychain
.dLDbIdentifier();
225 KeychainImpl::createCursor(SecItemClass itemClass
, const SecKeychainAttributeList
*attrList
)
227 StorageManager::KeychainList keychains
;
228 keychains
.push_back(Keychain(this));
229 return KCCursor(keychains
, itemClass
, attrList
);
233 KeychainImpl::createCursor(const SecKeychainAttributeList
*attrList
)
235 StorageManager::KeychainList keychains
;
236 keychains
.push_back(Keychain(this));
237 return KCCursor(keychains
, attrList
);
241 KeychainImpl::create(UInt32 passwordLength
, const void *inPassword
)
249 CssmAllocator
&alloc
= CssmAllocator::standard();
251 // @@@ Share this instance
253 const CssmData
password(const_cast<void *>(inPassword
), passwordLength
);
254 AclFactory::PasswordChangeCredentials
pCreds (password
, alloc
);
255 const AccessCredentials
* aa
= pCreds
;
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
;
265 const ResourceControlContext
rcc(protoType
, const_cast<AccessCredentials
*>(aa
));
269 void KeychainImpl::create(ConstStringPtr inPassword
)
272 create(static_cast<UInt32
>(inPassword
[0]), &inPassword
[1]);
278 KeychainImpl::create()
280 CssmAllocator
&alloc
= CssmAllocator::standard();
281 // @@@ Share this instance
283 KeychainAclFactory
aclFactory(alloc
);
285 const AccessCredentials
*cred
= aclFactory
.keychainPromptUnlockCredentials();
287 AclFactory aclFactor
;
288 const AccessCredentials
*cred
= aclFactor
.unlockCred ();
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
;
298 const ResourceControlContext
rcc(protoType
, const_cast<AccessCredentials
*>(cred
));
303 KeychainImpl::create(const ResourceControlContext
*rcc
)
305 mDb
->dbInfo(&Schema::DBInfo
); // Set the schema (to force a create)
306 mDb
->resourceControlContext(rcc
);
313 mDb
->resourceControlContext(NULL
);
314 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
317 mDb
->resourceControlContext(NULL
);
318 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
319 globals().storageManager
.created(Keychain(this));
335 KeychainImpl::unlock()
341 KeychainImpl::unlock(const CssmData
&password
)
343 mDb
->unlock(password
);
347 KeychainImpl::unlock(ConstStringPtr password
)
351 const CssmData
data(const_cast<unsigned char *>(&password
[1]), password
[0]);
359 KeychainImpl::getSettings(uint32
&outIdleTimeOut
, bool &outLockOnSleep
)
361 mDb
->getSettings(outIdleTimeOut
, outLockOnSleep
);
365 KeychainImpl::setSettings(uint32 inIdleTimeOut
, bool inLockOnSleep
)
367 mDb
->setSettings(inIdleTimeOut
, inLockOnSleep
);
370 KeychainImpl::changePassphrase(UInt32 oldPasswordLength
, const void *oldPassword
,
371 UInt32 newPasswordLength
, const void *newPassword
)
373 // @@@ When AutoCredentials is actually finished we should no logner use a tracking allocator.
374 TrackingAllocator
allocator(CssmAllocator::standard());
375 AutoCredentials cred
= AutoCredentials(allocator
);
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
));
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
));
394 mDb
->changePassphrase(&cred
);
398 KeychainImpl::changePassphrase(ConstStringPtr oldPassword
, ConstStringPtr newPassword
)
400 const void *oldPtr
, *newPtr
;
401 UInt32 oldLen
, newLen
;
404 oldLen
= oldPassword
[0];
405 oldPtr
= oldPassword
+ 1;
415 newLen
= newPassword
[0];
416 newPtr
= newPassword
+ 1;
424 changePassphrase(oldLen
, oldPtr
, newLen
, newPtr
);
428 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS
*cred
)
430 // @@@ This should do an authenticate which is not the same as unlock.
432 MacOSError::throwMe(errSecNoSuchKeychain
);
434 MacOSError::throwMe(unimpErr
);
438 KeychainImpl::status() const
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
;
446 KeychainImpl::exists()
452 // Ok to leave the mDb open since it will get closed when it goes away.
454 catch (const CssmError
&e
)
456 if (e
.cssmError() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST
)
465 KeychainImpl::isActive() const
467 return mDb
->isActive();
471 KeychainImpl::add(Item
&inItem
)
473 Keychain
keychain(this);
474 PrimaryKey primaryKey
= inItem
->add(keychain
);
476 StLock
<Mutex
> _(mDbItemMapLock
);
477 mDbItemMap
[primaryKey
] = inItem
.get();
480 KCEventNotifier::PostKeychainEvent(kSecAddEvent
, this, inItem
);
484 KeychainImpl::didUpdate(ItemImpl
*inItemImpl
, PrimaryKey
&oldPK
,
487 // Make sure we only hold mDbItemMapLock as long as we need to.
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
;
496 KCEventNotifier::PostKeychainEvent( kSecUpdateEvent
, this, inItemImpl
);
500 KeychainImpl::deleteItem(Item
&inoutItem
)
502 // item must be persistant.
503 if (!inoutItem
->isPersistant())
504 MacOSError::throwMe(errSecInvalidItemRef
);
506 DbUniqueRecord uniqueId
= inoutItem
->dbUniqueRecord();
507 PrimaryKey primaryKey
= inoutItem
->primaryKey();
508 uniqueId
->deleteRecord();
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();
515 // Post the notification for the item deletion with
516 // the primaryKey obtained when the item still existed
517 KCEventNotifier::PostKeychainEvent(kSecDeleteEvent
, dLDbIdentifier(), primaryKey
);
524 if (!mDb
->dl()->subserviceMask() & CSSM_SERVICE_CSP
)
525 MacOSError::throwMe(errSecInvalidKeychain
);
527 SSDb
ssDb(safe_cast
<SSDbImpl
*>(&(*mDb
)));
532 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
534 DbAttributes
primaryKeyAttrs(uniqueId
->database());
535 primaryKeyAttrs
.recordType(recordType
);
536 gatherPrimaryKeyAttributes(primaryKeyAttrs
);
537 uniqueId
->get(&primaryKeyAttrs
, NULL
);
538 return PrimaryKey(primaryKeyAttrs
);
541 const CssmAutoDbRecordAttributeInfo
&
542 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
)
544 return keychainSchema()->primaryKeyInfosFor(recordType
);
547 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes
& primaryKeyAttrs
)
549 const CssmAutoDbRecordAttributeInfo
&infos
=
550 primaryKeyInfosFor(primaryKeyAttrs
.recordType());
552 // @@@ fix this to not copy info.
553 for (uint32 i
= 0; i
< infos
.size(); i
++)
554 primaryKeyAttrs
.add(infos
.at(i
));
558 KeychainImpl::item(const PrimaryKey
& primaryKey
)
561 StLock
<Mutex
> _(mDbItemMapLock
);
562 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
563 if (it
!= mDbItemMap
.end())
565 return Item(it
->second
);
569 // Create an item with just a primary key
570 return Item(this, primaryKey
);
574 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
576 PrimaryKey primaryKey
= makePrimaryKey(recordType
, uniqueId
);
578 StLock
<Mutex
> _(mDbItemMapLock
);
579 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
580 if (it
!= mDbItemMap
.end())
582 return Item(it
->second
);
587 return Item(this, primaryKey
, uniqueId
);
591 KeychainImpl::keychainSchema()
593 if (!mKeychainSchema
)
595 // @@@ Use cache in storageManager
596 mKeychainSchema
= KeychainSchema(mDb
);
599 return mKeychainSchema
;
602 // Called from DbItemImpl's constructor (so it is only paritally constructed), add it to the map.
604 KeychainImpl::addItem(const PrimaryKey
&primaryKey
, ItemImpl
*dbItemImpl
)
606 StLock
<Mutex
> _(mDbItemMapLock
);
607 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
608 if (it
!= mDbItemMap
.end())
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
613 throw errSecDuplicateItem
;
614 //mDbItemMap.erase(it);
615 // @@@ What to do here?
618 mDbItemMap
.insert(DbItemMap::value_type(primaryKey
, dbItemImpl
));
622 KeychainImpl::removeItem(const PrimaryKey
&primaryKey
, const ItemImpl
*inItemImpl
)
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
);
632 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID
, SecKeychainAttributeInfo
**Info
)
634 keychainSchema()->getAttributeInfoForRecordType(itemID
, Info
);
638 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo
*Info
)
646 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType
, UInt32 tag
)
648 return keychainSchema()->attributeInfoFor(recordType
, tag
);
653 Keychain::optional(SecKeychainRef handle
)
656 return gTypes().keychain
.required(handle
);
658 return globals().defaultKeychain
;