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
;