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();