2 * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
29 #include "KCEventNotifier.h"
30 #include "Keychains.h"
35 #include <security_cdsa_utilities/Schema.h>
36 #include <security_cdsa_client/keychainacl.h>
37 #include <security_cdsa_utilities/cssmacl.h>
38 #include <security_cdsa_utilities/cssmdb.h>
39 #include <security_utilities/trackingallocator.h>
40 #include <security_utilities/FileLockTransaction.h>
41 #include <security_keychain/SecCFTypes.h>
42 #include <securityd_client/ssblob.h>
43 #include <Security/TrustSettingsSchema.h>
45 #include "SecKeychainPriv.h"
47 #include <Security/SecKeychainItemPriv.h>
48 #include <CoreFoundation/CoreFoundation.h>
49 #include "DLDbListCFPref.h"
52 #include <sys/param.h>
55 #include <sys/socket.h>
57 #include <sys/types.h>
60 static dispatch_once_t SecKeychainSystemKeychainChecked
;
62 OSStatus
SecKeychainSystemKeychainCheckWouldDeadlock()
64 dispatch_once(&SecKeychainSystemKeychainChecked
, ^{});
68 using namespace KeychainCore
;
69 using namespace CssmClient
;
72 typedef struct EventItem
74 SecKeychainEvent kcEvent
;
78 typedef std::list
<EventItem
> EventBufferSuper
;
79 class EventBuffer
: public EventBufferSuper
83 virtual ~EventBuffer ();
87 EventBuffer::~EventBuffer ()
96 KeychainSchemaImpl::KeychainSchemaImpl(const Db
&db
) : mMutex(Mutex::recursive
)
98 DbCursor
relations(db
);
99 relations
->recordType(CSSM_DL_DB_SCHEMA_INFO
);
100 DbAttributes
relationRecord(db
, 1);
101 relationRecord
.add(Schema::RelationID
);
102 DbUniqueRecord
outerUniqueId(db
);
104 while (relations
->next(&relationRecord
, NULL
, outerUniqueId
))
106 DbUniqueRecord
uniqueId(db
);
108 uint32 relationID
= relationRecord
.at(0);
109 if (CSSM_DB_RECORDTYPE_SCHEMA_START
<= relationID
110 && relationID
< CSSM_DB_RECORDTYPE_SCHEMA_END
)
113 // Create a cursor on the SCHEMA_ATTRIBUTES table for records with
114 // RelationID == relationID
115 DbCursor
attributes(db
);
116 attributes
->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES
);
117 attributes
->add(CSSM_DB_EQUAL
, Schema::RelationID
, relationID
);
119 // Set up a record for retriving the SCHEMA_ATTRIBUTES
120 DbAttributes
attributeRecord(db
, 2);
121 attributeRecord
.add(Schema::AttributeFormat
);
122 attributeRecord
.add(Schema::AttributeID
);
124 RelationInfoMap
&rim
= mDatabaseInfoMap
[relationID
];
125 while (attributes
->next(&attributeRecord
, NULL
, uniqueId
))
126 rim
[attributeRecord
.at(1)] = attributeRecord
.at(0);
128 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records
129 // with RelationID == relationID
130 DbCursor
indexes(db
);
131 indexes
->recordType(CSSM_DL_DB_SCHEMA_INDEXES
);
132 indexes
->conjunctive(CSSM_DB_AND
);
133 indexes
->add(CSSM_DB_EQUAL
, Schema::RelationID
, relationID
);
134 indexes
->add(CSSM_DB_EQUAL
, Schema::IndexType
,
135 uint32(CSSM_DB_INDEX_UNIQUE
));
137 // Set up a record for retriving the SCHEMA_INDEXES
138 DbAttributes
indexRecord(db
, 1);
139 indexRecord
.add(Schema::AttributeID
);
141 CssmAutoDbRecordAttributeInfo
&infos
=
142 *new CssmAutoDbRecordAttributeInfo();
144 insert(PrimaryKeyInfoMap::value_type(relationID
, &infos
));
145 infos
.DataRecordType
= relationID
;
146 while (indexes
->next(&indexRecord
, NULL
, uniqueId
))
148 CssmDbAttributeInfo
&info
= infos
.add();
149 info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
;
150 info
.Label
.AttributeID
= indexRecord
.at(0);
151 // @@@ Might insert bogus value if DB is corrupt
152 info
.AttributeFormat
= rim
[info
.Label
.AttributeID
];
157 KeychainSchemaImpl::~KeychainSchemaImpl()
161 map
<CSSM_DB_RECORDTYPE
, CssmAutoDbRecordAttributeInfo
*>::iterator it
= mPrimaryKeyInfoMap
.begin();
162 while (it
!= mPrimaryKeyInfoMap
.end())
167 // for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end());
174 const KeychainSchemaImpl::RelationInfoMap
&
175 KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType
) const
177 DatabaseInfoMap::const_iterator dit
= mDatabaseInfoMap
.find(recordType
);
178 if (dit
== mDatabaseInfoMap
.end())
179 MacOSError::throwMe(errSecNoSuchClass
);
183 bool KeychainSchemaImpl::hasRecordType (CSSM_DB_RECORDTYPE recordType
) const
185 DatabaseInfoMap::const_iterator it
= mDatabaseInfoMap
.find(recordType
);
186 return it
!= mDatabaseInfoMap
.end();
190 KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
194 const RelationInfoMap
&rmap
= relationInfoMapFor(recordType
);
195 RelationInfoMap::const_iterator rit
= rmap
.find(attributeId
);
196 return rit
!= rmap
.end();
198 catch (MacOSError result
)
200 if (result
.osStatus () == errSecNoSuchClass
)
211 CSSM_DB_ATTRIBUTE_FORMAT
212 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
214 const RelationInfoMap
&rmap
= relationInfoMapFor(recordType
);
215 RelationInfoMap::const_iterator rit
= rmap
.find(attributeId
);
216 if (rit
== rmap
.end())
217 MacOSError::throwMe(errSecNoSuchAttr
);
223 KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType
, uint32 attributeId
) const
225 CSSM_DB_ATTRIBUTE_INFO info
;
226 info
.AttributeFormat
= attributeFormatFor(recordType
, attributeId
);
227 info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
;
228 info
.Label
.AttributeID
= attributeId
;
234 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType
, SecKeychainAttributeInfo
**Info
) const
236 const RelationInfoMap
&rmap
= relationInfoMapFor(recordType
);
238 SecKeychainAttributeInfo
*theList
=reinterpret_cast<SecKeychainAttributeInfo
*>(malloc(sizeof(SecKeychainAttributeInfo
)));
240 size_t capacity
=rmap
.size();
241 UInt32
*tagBuf
=reinterpret_cast<UInt32
*>(malloc(capacity
*sizeof(UInt32
)));
242 UInt32
*formatBuf
=reinterpret_cast<UInt32
*>(malloc(capacity
*sizeof(UInt32
)));
246 for (RelationInfoMap::const_iterator rit
= rmap
.begin(); rit
!= rmap
.end(); ++rit
)
251 if (capacity
<= i
) capacity
= i
+ 1;
252 tagBuf
=reinterpret_cast<UInt32
*>(realloc(tagBuf
, (capacity
*sizeof(UInt32
))));
253 formatBuf
=reinterpret_cast<UInt32
*>(realloc(tagBuf
, (capacity
*sizeof(UInt32
))));
255 tagBuf
[i
]=rit
->first
;
256 formatBuf
[i
++]=rit
->second
;
261 theList
->format
=formatBuf
;
266 const CssmAutoDbRecordAttributeInfo
&
267 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
) const
269 PrimaryKeyInfoMap::const_iterator it
;
270 it
= mPrimaryKeyInfoMap
.find(recordType
);
272 if (it
== mPrimaryKeyInfoMap
.end())
273 MacOSError::throwMe(errSecNoSuchClass
); // @@@ Not really but whatever.
279 KeychainSchemaImpl::operator <(const KeychainSchemaImpl
&other
) const
281 return mDatabaseInfoMap
< other
.mDatabaseInfoMap
;
285 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl
&other
) const
287 return mDatabaseInfoMap
== other
.mDatabaseInfoMap
;
291 KeychainSchemaImpl::didCreateRelation(CSSM_DB_RECORDTYPE relationID
,
292 const char *inRelationName
,
293 uint32 inNumberOfAttributes
,
294 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO
*pAttributeInfo
,
295 uint32 inNumberOfIndexes
,
296 const CSSM_DB_SCHEMA_INDEX_INFO
*pIndexInfo
)
298 StLock
<Mutex
>_(mMutex
);
300 if (CSSM_DB_RECORDTYPE_SCHEMA_START
<= relationID
301 && relationID
< CSSM_DB_RECORDTYPE_SCHEMA_END
)
304 // if our schema is already in the map, return
305 if (mPrimaryKeyInfoMap
.find(relationID
) != mPrimaryKeyInfoMap
.end())
310 RelationInfoMap
&rim
= mDatabaseInfoMap
[relationID
];
311 for (uint32 ix
= 0; ix
< inNumberOfAttributes
; ++ix
)
312 rim
[pAttributeInfo
[ix
].AttributeId
] = pAttributeInfo
[ix
].DataType
;
314 CssmAutoDbRecordAttributeInfo
*infos
= new CssmAutoDbRecordAttributeInfo();
317 insert(PrimaryKeyInfoMap::value_type(relationID
, infos
));
318 infos
->DataRecordType
= relationID
;
319 for (uint32 ix
= 0; ix
< inNumberOfIndexes
; ++ix
)
320 if (pIndexInfo
[ix
].IndexType
== CSSM_DB_INDEX_UNIQUE
)
322 CssmDbAttributeInfo
&info
= infos
->add();
323 info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
;
324 info
.Label
.AttributeID
= pIndexInfo
[ix
].AttributeId
;
325 info
.AttributeFormat
= rim
[info
.Label
.AttributeID
];
331 KeychainSchema::~KeychainSchema()
340 SecKeychainEvent eventCode
;
341 PrimaryKey primaryKey
;
343 typedef std::list
<Event
> EventList
;
345 #define SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME "/var/run/systemkeychaincheck"
346 #define SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".socket")
347 #define SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".done")
349 static void check_system_keychain()
351 // sadly we can't use XPC here, XPC_DOMAIN_TYPE_SYSTEM doesn't exist yet. Also xpc-helper uses the
352 // keychain API (I assume for checking codesign things). So we use Unix Domain Sockets.
354 // NOTE: if we hit a system error we attempt to log it, and then just don't check the system keychain.
355 // In theory a system might be able to recover from this state if we let it try to muddle along, and
356 // past behaviour didn't even try this hard to do the keychain check. In particular we could be in a
357 // sandbox'ed process. So we just do our best and let another process try again.
359 struct stat keycheck_file_info
;
360 if (stat(SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME
, &keycheck_file_info
) < 0) {
361 int server_fd
= socket(PF_UNIX
, SOCK_STREAM
, 0);
363 syslog(LOG_ERR
, "Can't get socket (%m) system keychain may be unchecked");
367 struct sockaddr_un keychain_check_server_address
;
368 keychain_check_server_address
.sun_family
= AF_UNIX
;
369 if (strlcpy(keychain_check_server_address
.sun_path
, SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME
, sizeof(keychain_check_server_address
.sun_path
)) > sizeof(keychain_check_server_address
.sun_path
)) {
370 // It would be nice if we could compile time assert this
371 syslog(LOG_ERR
, "Socket path too long, max length %lu, your length %lu", (unsigned long)sizeof(keychain_check_server_address
.sun_path
), (unsigned long)strlen(SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME
));
375 keychain_check_server_address
.sun_len
= SUN_LEN(&keychain_check_server_address
);
377 int rc
= connect(server_fd
, (struct sockaddr
*)&keychain_check_server_address
, keychain_check_server_address
.sun_len
);
379 syslog(LOG_ERR
, "Can not connect to %s: %m", SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME
);
384 // this read lets us block until the EOF comes, we don't ever get a byte (and if we do, we don't care about it)
386 ssize_t read_size
= read(server_fd
, &byte
, 1);
388 syslog(LOG_ERR
, "Error reading from system keychain checker: %m");
399 KeychainImpl::KeychainImpl(const Db
&db
)
400 : mCacheTimer(NULL
), mSuppressTickle(false), mAttemptedUpgrade(false), mDbItemMapMutex(Mutex::recursive
), mDbDeletedItemMapMutex(Mutex::recursive
),
401 mInCache(false), mDb(db
), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive
)
403 dispatch_once(&SecKeychainSystemKeychainChecked
, ^{
404 check_system_keychain();
406 mDb
->defaultCredentials(this); // install activation hook
407 mEventBuffer
= new EventBuffer
;
410 KeychainImpl::~KeychainImpl()
414 // Remove ourselves from the cache if we are in it.
415 // fprintf(stderr, "Removing %p from storage manager cache.\n", handle(false));
416 globals().storageManager
.removeKeychain(dlDbIdentifier(), this);
425 KeychainImpl::getMutexForObject() const
427 return globals().storageManager
.getStorageManagerMutex();
431 KeychainImpl::getKeychainMutex()
437 KeychainImpl::getKeychainReadWriteLock()
442 void KeychainImpl::aboutToDestruct()
444 // remove me from the global cache, we are done
445 // fprintf(stderr, "Destructing keychain object\n");
446 DLDbIdentifier identifier
= dlDbIdentifier();
447 globals().storageManager
.removeKeychain(identifier
, this);
451 KeychainImpl::operator ==(const KeychainImpl
&keychain
) const
453 return dlDbIdentifier() == keychain
.dlDbIdentifier();
457 KeychainImpl::createCursor(SecItemClass itemClass
, const SecKeychainAttributeList
*attrList
)
459 StLock
<Mutex
>_(mMutex
);
461 StorageManager::KeychainList keychains
;
462 keychains
.push_back(Keychain(this));
463 return KCCursor(keychains
, itemClass
, attrList
);
467 KeychainImpl::createCursor(const SecKeychainAttributeList
*attrList
)
469 StLock
<Mutex
>_(mMutex
);
471 StorageManager::KeychainList keychains
;
472 keychains
.push_back(Keychain(this));
473 return KCCursor(keychains
, attrList
);
477 KeychainImpl::create(UInt32 passwordLength
, const void *inPassword
)
479 StLock
<Mutex
>_(mMutex
);
487 Allocator
&alloc
= Allocator::standard();
489 // @@@ Share this instance
491 const CssmData
password(const_cast<void *>(inPassword
), passwordLength
);
492 AclFactory::PasswordChangeCredentials
pCreds (password
, alloc
);
493 AclFactory::AnyResourceContext
rcc(pCreds
);
496 // Now that we've created, trigger setting the defaultCredentials
500 void KeychainImpl::create(ConstStringPtr inPassword
)
502 StLock
<Mutex
>_(mMutex
);
505 create(static_cast<UInt32
>(inPassword
[0]), &inPassword
[1]);
511 KeychainImpl::create()
513 StLock
<Mutex
>_(mMutex
);
515 AclFactory aclFactory
;
516 AclFactory::AnyResourceContext
rcc(aclFactory
.unlockCred());
519 // Now that we've created, trigger setting the defaultCredentials
523 void KeychainImpl::createWithBlob(CssmData
&blob
)
525 StLock
<Mutex
>_(mMutex
);
527 mDb
->dbInfo(&Schema::DBInfo
);
528 AclFactory aclFactory
;
529 AclFactory::AnyResourceContext
rcc(aclFactory
.unlockCred());
530 mDb
->resourceControlContext (&rcc
);
533 mDb
->createWithBlob(blob
);
537 mDb
->resourceControlContext(NULL
);
541 mDb
->resourceControlContext(NULL
);
542 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
543 globals().storageManager
.created(Keychain(this));
545 KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent
, this, NULL
);
549 KeychainImpl::create(const ResourceControlContext
*rcc
)
551 StLock
<Mutex
>_(mMutex
);
553 mDb
->dbInfo(&Schema::DBInfo
); // Set the schema (to force a create)
554 mDb
->resourceControlContext(rcc
);
561 mDb
->resourceControlContext(NULL
);
562 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
565 mDb
->resourceControlContext(NULL
);
566 mDb
->dbInfo(NULL
); // Clear the schema (to not break an open call later)
567 globals().storageManager
.created(Keychain(this));
573 StLock
<Mutex
>_(mMutex
);
581 StLock
<Mutex
>_(mMutex
);
587 KeychainImpl::unlock()
589 StLock
<Mutex
>_(mMutex
);
595 KeychainImpl::unlock(const CssmData
&password
)
597 StLock
<Mutex
>_(mMutex
);
599 mDb
->unlock(password
);
603 KeychainImpl::unlock(ConstStringPtr password
)
605 StLock
<Mutex
>_(mMutex
);
609 const CssmData
data(const_cast<unsigned char *>(&password
[1]), password
[0]);
617 KeychainImpl::stash()
619 StLock
<Mutex
>_(mMutex
);
625 KeychainImpl::stashCheck()
627 StLock
<Mutex
>_(mMutex
);
633 KeychainImpl::getSettings(uint32
&outIdleTimeOut
, bool &outLockOnSleep
)
635 StLock
<Mutex
>_(mMutex
);
637 mDb
->getSettings(outIdleTimeOut
, outLockOnSleep
);
641 KeychainImpl::setSettings(uint32 inIdleTimeOut
, bool inLockOnSleep
)
643 StLock
<Mutex
>_(mMutex
);
645 // The .Mac syncing code only makes sense for the AppleFile CSP/DL,
646 // but other DLs such as the OCSP and LDAP DLs do not expose a way to
647 // change settings or the password. To make a minimal change that only affects
648 // the smartcard case, we only look for that CSP/DL
650 bool isSmartcard
= (mDb
->dl()->guid() == gGuidAppleSdCSPDL
);
652 // get the old keychain blob so that we can tell .Mac to resync it
653 CssmAutoData
oldBlob(mDb
->allocator());
655 mDb
->copyBlob(oldBlob
.get());
657 mDb
->setSettings(inIdleTimeOut
, inLockOnSleep
);
661 KeychainImpl::changePassphrase(UInt32 oldPasswordLength
, const void *oldPassword
,
662 UInt32 newPasswordLength
, const void *newPassword
)
664 StLock
<Mutex
>_(mMutex
);
666 bool isSmartcard
= (mDb
->dl()->guid() == gGuidAppleSdCSPDL
);
668 TrackingAllocator
allocator(Allocator::standard());
669 AutoCredentials cred
= AutoCredentials(allocator
);
672 const CssmData
&oldPass
= *new(allocator
) CssmData(const_cast<void *>(oldPassword
), oldPasswordLength
);
673 TypedList
&oldList
= *new(allocator
) TypedList(allocator
, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK
);
674 oldList
.append(new(allocator
) ListElement(CSSM_SAMPLE_TYPE_PASSWORD
));
675 oldList
.append(new(allocator
) ListElement(oldPass
));
681 const CssmData
&newPass
= *new(allocator
) CssmData(const_cast<void *>(newPassword
), newPasswordLength
);
682 TypedList
&newList
= *new(allocator
) TypedList(allocator
, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK
);
683 newList
.append(new(allocator
) ListElement(CSSM_SAMPLE_TYPE_PASSWORD
));
684 newList
.append(new(allocator
) ListElement(newPass
));
688 // get the old keychain blob so that we can tell .Mac to resync it
689 CssmAutoData
oldBlob(mDb
->allocator());
691 mDb
->copyBlob(oldBlob
.get());
693 mDb
->changePassphrase(&cred
);
697 KeychainImpl::changePassphrase(ConstStringPtr oldPassword
, ConstStringPtr newPassword
)
699 StLock
<Mutex
>_(mMutex
);
701 const void *oldPtr
, *newPtr
;
702 UInt32 oldLen
, newLen
;
705 oldLen
= oldPassword
[0];
706 oldPtr
= oldPassword
+ 1;
716 newLen
= newPassword
[0];
717 newPtr
= newPassword
+ 1;
725 changePassphrase(oldLen
, oldPtr
, newLen
, newPtr
);
729 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS
*cred
)
731 StLock
<Mutex
>_(mMutex
);
734 MacOSError::throwMe(errSecNoSuchKeychain
);
736 MacOSError::throwMe(errSecUnimplemented
);
740 KeychainImpl::status() const
742 StLock
<Mutex
>_(mMutex
);
744 // @@@ We should figure out the read/write status though a DL passthrough
745 // or some other way. Also should locked be unlocked read only or just
747 return (mDb
->isLocked() ? 0 : kSecUnlockStateStatus
| kSecWritePermStatus
)
748 | kSecReadPermStatus
;
752 KeychainImpl::exists()
754 StLock
<Mutex
>_(mMutex
);
760 // Ok to leave the mDb open since it will get closed when it goes away.
762 catch (const CssmError
&e
)
764 if (e
.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST
)
773 KeychainImpl::isActive() const
775 return mDb
->isActive();
778 void KeychainImpl::completeAdd(Item
&inItem
, PrimaryKey
&primaryKey
)
780 // The inItem shouldn't be in the cache yet
781 assert(!inItem
->inCache());
783 // Insert inItem into mDbItemMap with key primaryKey. p.second will be
784 // true if it got inserted. If not p.second will be false and p.first
785 // will point to the current entry with key primaryKey.
786 StLock
<Mutex
> _(mDbItemMapMutex
);
787 pair
<DbItemMap::iterator
, bool> p
=
788 mDbItemMap
.insert(DbItemMap::value_type(primaryKey
, inItem
.get()));
791 // There was already an ItemImpl * in mDbItemMap with key
792 // primaryKey. Remove it, and try the add again.
793 ItemImpl
*oldItem
= p
.first
->second
;
795 // @@@ If this happens we are breaking our API contract of
796 // uniquifying items. We really need to insert the item into the
797 // map before we start the add. And have the item be in an
798 // "is being added" state.
799 secnotice("keychain", "add of new item %p somehow replaced %p",
800 inItem
.get(), oldItem
);
802 mDbItemMap
.erase(p
.first
);
803 oldItem
->inCache(false);
804 forceRemoveFromCache(oldItem
);
805 mDbItemMap
.insert(DbItemMap::value_type(primaryKey
, inItem
.get()));
808 inItem
->inCache(true);
812 KeychainImpl::addCopy(Item
&inItem
)
814 StReadWriteLock
_(mRWLock
, StReadWriteLock::Write
);
816 Keychain
keychain(this);
817 PrimaryKey primaryKey
= inItem
->addWithCopyInfo(keychain
, true);
818 completeAdd(inItem
, primaryKey
);
819 postEvent(kSecAddEvent
, inItem
);
823 KeychainImpl::add(Item
&inItem
)
825 // Make sure we hold a write lock on ourselves when we do this
826 StReadWriteLock
_(mRWLock
, StReadWriteLock::Write
);
828 Keychain
keychain(this);
829 PrimaryKey primaryKey
= inItem
->add(keychain
);
830 completeAdd(inItem
, primaryKey
);
831 postEvent(kSecAddEvent
, inItem
);
835 KeychainImpl::didUpdate(const Item
&inItem
, PrimaryKey
&oldPK
,
838 // If the primary key hasn't changed we don't need to update mDbItemMap.
841 // If inItem isn't in the cache we don't need to update mDbItemMap.
842 assert(inItem
->inCache());
843 if (inItem
->inCache())
845 StLock
<Mutex
> _(mDbItemMapMutex
);
846 // First remove the entry for inItem in mDbItemMap with key oldPK.
847 DbItemMap::iterator it
= mDbItemMap
.find(oldPK
);
848 if (it
!= mDbItemMap
.end() && (ItemImpl
*) it
->second
== inItem
.get())
849 mDbItemMap
.erase(it
);
851 // Insert inItem into mDbItemMap with key newPK. p.second will be
852 // true if it got inserted. If not p.second will be false and
853 // p.first will point to the current entry with key newPK.
854 pair
<DbItemMap::iterator
, bool> p
=
855 mDbItemMap
.insert(DbItemMap::value_type(newPK
, inItem
.get()));
858 // There was already an ItemImpl * in mDbItemMap with key
859 // primaryKey. Remove it, and try the add again.
860 ItemImpl
*oldItem
= p
.first
->second
;
862 // @@@ If this happens we are breaking our API contract of
863 // uniquifying items. We really need to insert the item into
864 // the map with the new primary key before we start the update.
865 // And have the item be in an "is being updated" state.
866 secnotice("keychain", "update of item %p somehow replaced %p",
867 inItem
.get(), oldItem
);
869 mDbItemMap
.erase(p
.first
);
870 oldItem
->inCache(false);
871 forceRemoveFromCache(oldItem
);
872 mDbItemMap
.insert(DbItemMap::value_type(newPK
, inItem
.get()));
877 // Item updates now are technically a delete and re-add, so post these events instead of kSecUpdateEvent
878 postEvent(kSecDeleteEvent
, inItem
, oldPK
);
879 postEvent(kSecAddEvent
, inItem
);
883 KeychainImpl::deleteItem(Item
&inoutItem
)
885 StReadWriteLock
_(mRWLock
, StReadWriteLock::Write
);
888 // item must be persistent
889 if (!inoutItem
->isPersistent())
890 MacOSError::throwMe(errSecInvalidItemRef
);
892 secinfo("kcnotify", "starting deletion of item %p", inoutItem
.get());
894 DbUniqueRecord uniqueId
= inoutItem
->dbUniqueRecord();
895 PrimaryKey primaryKey
= inoutItem
->primaryKey();
896 uniqueId
->deleteRecord();
898 // Move the item from mDbItemMap to mDbDeletedItemMap. We need the item
899 // to give to the client process when we receive the kSecDeleteEvent
900 // notification, but if that notification never arrives, we don't want
901 // the item hanging around. When didDeleteItem is called by CCallbackMgr,
902 // we'll remove all traces of the item.
904 if (inoutItem
->inCache()) {
905 StLock
<Mutex
> _(mDbItemMapMutex
);
906 StLock
<Mutex
> __(mDbDeletedItemMapMutex
);
907 // Only look for it if it's in the cache
908 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
910 if (it
!= mDbItemMap
.end() && (ItemImpl
*) it
->second
== inoutItem
.get()) {
911 mDbDeletedItemMap
.insert(DbItemMap::value_type(primaryKey
, it
->second
));
912 mDbItemMap
.erase(it
);
916 // Post the notification for the item deletion with
917 // the primaryKey obtained when the item still existed
920 postEvent(kSecDeleteEvent
, inoutItem
);
923 void KeychainImpl::changeDatabase(CssmClient::Db db
)
925 StLock
<Mutex
>_(mDbMutex
);
927 mDb
->defaultCredentials(this);
934 StLock
<Mutex
>_(mMutex
);
936 if (!mDb
->dl()->subserviceMask() & CSSM_SERVICE_CSP
)
937 MacOSError::throwMe(errSecInvalidKeychain
);
939 // Try to cast first to a CSPDL to handle case where we don't have an SSDb
942 CssmClient::CSPDL
cspdl(dynamic_cast<CssmClient::CSPDLImpl
*>(&*mDb
->dl()));
947 SSDbImpl
* impl
= dynamic_cast<SSDbImpl
*>(&(*mDb
));
950 CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER
);
959 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
961 StLock
<Mutex
>_(mMutex
);
963 DbAttributes
primaryKeyAttrs(uniqueId
->database());
964 primaryKeyAttrs
.recordType(recordType
);
965 gatherPrimaryKeyAttributes(primaryKeyAttrs
);
966 uniqueId
->get(&primaryKeyAttrs
, NULL
);
967 return PrimaryKey(primaryKeyAttrs
);
971 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType
, DbAttributes
* currentAttributes
)
973 StLock
<Mutex
>_(mMutex
);
975 DbAttributes primaryKeyAttrs
;
976 primaryKeyAttrs
.recordType(recordType
);
977 gatherPrimaryKeyAttributes(primaryKeyAttrs
);
979 for(int i
= 0; i
< primaryKeyAttrs
.size(); i
++) {
980 CssmDbAttributeData
& attr
= primaryKeyAttrs
[i
];
982 CssmDbAttributeData
* actual
= currentAttributes
->find(attr
.info());
984 attr
.set(*actual
, Allocator::standard());
987 return PrimaryKey(primaryKeyAttrs
);
990 const CssmAutoDbRecordAttributeInfo
&
991 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType
)
993 StLock
<Mutex
>_(mMutex
);
997 return keychainSchema()->primaryKeyInfosFor(recordType
);
999 catch (const CommonError
&error
)
1001 switch (error
.osStatus())
1003 case errSecNoSuchClass
:
1004 case CSSMERR_DL_INVALID_RECORDTYPE
:
1006 return keychainSchema()->primaryKeyInfosFor(recordType
);
1013 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes
& primaryKeyAttrs
)
1015 StLock
<Mutex
> _(mMutex
);
1017 const CssmAutoDbRecordAttributeInfo
&infos
=
1018 primaryKeyInfosFor(primaryKeyAttrs
.recordType());
1020 // @@@ fix this to not copy info.
1021 for (uint32 i
= 0; i
< infos
.size(); i
++)
1022 primaryKeyAttrs
.add(infos
.at(i
));
1026 KeychainImpl::_lookupItem(const PrimaryKey
&primaryKey
)
1028 StLock
<Mutex
> _(mDbItemMapMutex
);
1029 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
1030 if (it
!= mDbItemMap
.end())
1039 KeychainImpl::_lookupDeletedItemOnly(const PrimaryKey
&primaryKey
)
1041 StLock
<Mutex
> _(mDbDeletedItemMapMutex
);
1042 DbItemMap::iterator it
= mDbDeletedItemMap
.find(primaryKey
);
1043 if (it
!= mDbDeletedItemMap
.end())
1052 KeychainImpl::item(const PrimaryKey
&primaryKey
)
1054 StLock
<Mutex
>_(mMutex
);
1056 // Lookup the item in the map while holding the apiLock.
1057 ItemImpl
*itemImpl
= _lookupItem(primaryKey
);
1059 return Item(itemImpl
);
1064 // We didn't find it so create a new item with just a keychain and
1065 // a primary key. Some other thread might have beaten
1066 // us to creating this item and adding it to the cache. If that
1067 // happens we retry the lookup.
1068 return Item(this, primaryKey
);
1070 catch (const MacOSError
&e
)
1072 // If the item creation failed because some other thread already
1073 // inserted this item into the cache we retry the lookup.
1074 if (e
.osStatus() == errSecDuplicateItem
)
1076 // Lookup the item in the map while holding the apiLock.
1077 ItemImpl
*itemImpl
= _lookupItem(primaryKey
);
1079 return Item(itemImpl
);
1084 // Check for an item that may have been deleted.
1086 KeychainImpl::itemdeleted(const PrimaryKey
& primaryKey
) {
1087 StLock
<Mutex
>_(mMutex
);
1089 Item i
= _lookupDeletedItemOnly(primaryKey
);
1093 return item(primaryKey
);
1099 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType
, DbUniqueRecord
&uniqueId
)
1101 StLock
<Mutex
>_(mMutex
);
1103 PrimaryKey primaryKey
= makePrimaryKey(recordType
, uniqueId
);
1105 // Lookup the item in the map while holding the apiLock.
1106 ItemImpl
*itemImpl
= _lookupItem(primaryKey
);
1110 return Item(itemImpl
);
1116 // We didn't find it so create a new item with a keychain, a primary key
1117 // and a DbUniqueRecord. However since we aren't holding
1118 // globals().apiLock anymore some other thread might have beaten
1119 // us to creating this item and adding it to the cache. If that
1120 // happens we retry the lookup.
1121 return Item(this, primaryKey
, uniqueId
);
1123 catch (const MacOSError
&e
)
1125 // If the item creation failed because some other thread already
1126 // inserted this item into the cache we retry the lookup.
1127 if (e
.osStatus() == errSecDuplicateItem
)
1129 // Lookup the item in the map while holding the apiLock.
1130 ItemImpl
*itemImpl
= _lookupItem(primaryKey
);
1132 return Item(itemImpl
);
1139 KeychainImpl::keychainSchema()
1141 StLock
<Mutex
>_(mMutex
);
1142 if (!mKeychainSchema
)
1143 mKeychainSchema
= KeychainSchema(mDb
);
1145 return mKeychainSchema
;
1148 void KeychainImpl::resetSchema()
1150 mKeychainSchema
= NULL
; // re-fetch it from db next time
1154 // Called from DbItemImpl's constructor (so it is only partially constructed),
1155 // add it to the map.
1157 KeychainImpl::addItem(const PrimaryKey
&primaryKey
, ItemImpl
*dbItemImpl
)
1159 StLock
<Mutex
>_(mMutex
);
1161 // The dbItemImpl shouldn't be in the cache yet
1162 assert(!dbItemImpl
->inCache());
1164 // Insert dbItemImpl into mDbItemMap with key primaryKey. p.second will
1165 // be true if it got inserted. If not p.second will be false and p.first
1166 // will point to the current entry with key primaryKey.
1167 StLock
<Mutex
> __(mDbItemMapMutex
);
1168 pair
<DbItemMap::iterator
, bool> p
=
1169 mDbItemMap
.insert(DbItemMap::value_type(primaryKey
, dbItemImpl
));
1173 // There was already an ItemImpl * in mDbItemMap with key primaryKey.
1174 // There is a race condition here when being called in multiple threads
1175 // We might have added an item using add and received a notification at
1177 MacOSError::throwMe(errSecDuplicateItem
);
1180 dbItemImpl
->inCache(true);
1184 KeychainImpl::didDeleteItem(ItemImpl
*inItemImpl
)
1186 StLock
<Mutex
>_(mMutex
);
1188 // Called by CCallbackMgr
1189 secinfo("kcnotify", "%p notified that item %p was deleted", this, inItemImpl
);
1190 removeItem(inItemImpl
->primaryKey(), inItemImpl
);
1194 KeychainImpl::removeItem(const PrimaryKey
&primaryKey
, ItemImpl
*inItemImpl
)
1196 StLock
<Mutex
>_(mMutex
);
1198 // If inItemImpl isn't in the cache to begin with we are done.
1199 if (!inItemImpl
->inCache())
1203 StLock
<Mutex
> _(mDbItemMapMutex
);
1204 DbItemMap::iterator it
= mDbItemMap
.find(primaryKey
);
1205 if (it
!= mDbItemMap
.end() && (ItemImpl
*) it
->second
== inItemImpl
) {
1206 mDbItemMap
.erase(it
);
1208 } // drop mDbItemMapMutex
1211 StLock
<Mutex
> _(mDbDeletedItemMapMutex
);
1212 DbItemMap::iterator it
= mDbDeletedItemMap
.find(primaryKey
);
1213 if (it
!= mDbDeletedItemMap
.end() && (ItemImpl
*) it
->second
== inItemImpl
) {
1214 mDbDeletedItemMap
.erase(it
);
1216 } // drop mDbDeletedItemMapMutex
1218 inItemImpl
->inCache(false);
1222 KeychainImpl::forceRemoveFromCache(ItemImpl
* inItemImpl
) {
1224 // Wrap all this in a try-block and ignore all errors - we're trying to clean up these maps
1226 StLock
<Mutex
> _(mDbItemMapMutex
);
1227 for(DbItemMap::iterator it
= mDbItemMap
.begin(); it
!= mDbItemMap
.end(); ) {
1228 if(it
->second
== inItemImpl
) {
1229 // Increment the iterator, but use its pre-increment value for the erase
1230 it
->second
->inCache(false);
1231 mDbItemMap
.erase(it
++);
1236 } // drop mDbItemMapMutex
1239 StLock
<Mutex
> _(mDbDeletedItemMapMutex
);
1240 for(DbItemMap::iterator it
= mDbDeletedItemMap
.begin(); it
!= mDbDeletedItemMap
.end(); ) {
1241 if(it
->second
== inItemImpl
) {
1242 // Increment the iterator, but use its pre-increment value for the erase
1243 it
->second
->inCache(false);
1244 mDbDeletedItemMap
.erase(it
++);
1249 } // drop mDbDeletedItemMapMutex
1250 } catch(UnixError ue
) {
1251 secnotice("keychain", "caught UnixError: %d %s", ue
.unixError(), ue
.what());
1252 } catch (CssmError cssme
) {
1253 const char* errStr
= cssmErrorString(cssme
.error
);
1254 secnotice("keychain", "caught CssmError: %d %s", (int) cssme
.error
, errStr
);
1255 } catch (MacOSError mose
) {
1256 secnotice("keychain", "MacOSError: %d", (int)mose
.osStatus());
1258 secnotice("keychain", "Unknown error");
1263 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID
,
1264 SecKeychainAttributeInfo
**Info
)
1266 StLock
<Mutex
>_(mMutex
);
1270 keychainSchema()->getAttributeInfoForRecordType(itemID
, Info
);
1272 catch (const CommonError
&error
)
1274 switch (error
.osStatus())
1276 case errSecNoSuchClass
:
1277 case CSSMERR_DL_INVALID_RECORDTYPE
:
1279 keychainSchema()->getAttributeInfoForRecordType(itemID
, Info
);
1287 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo
*Info
)
1295 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType
, UInt32 tag
)
1297 StLock
<Mutex
>_(mMutex
);
1301 return keychainSchema()->attributeInfoFor(recordType
, tag
);
1303 catch (const CommonError
&error
)
1305 switch (error
.osStatus())
1307 case errSecNoSuchClass
:
1308 case CSSMERR_DL_INVALID_RECORDTYPE
:
1310 return keychainSchema()->attributeInfoFor(recordType
, tag
);
1318 KeychainImpl::recode(const CssmData
&data
, const CssmData
&extraData
)
1320 StLock
<Mutex
>_(mMutex
);
1322 mDb
->recode(data
, extraData
);
1326 KeychainImpl::copyBlob(CssmData
&data
)
1328 StLock
<Mutex
>_(mMutex
);
1330 mDb
->copyBlob(data
);
1334 KeychainImpl::setBatchMode(Boolean mode
, Boolean rollback
)
1336 StLock
<Mutex
>_(mMutex
);
1338 mDb
->setBatchMode(mode
, rollback
);
1339 mIsInBatchMode
= mode
;
1342 if (!rollback
) // was batch mode being turned off without an abort?
1345 EventBuffer::iterator it
= mEventBuffer
->begin();
1346 while (it
!= mEventBuffer
->end())
1348 PrimaryKey primaryKey
;
1351 primaryKey
= it
->item
->primaryKey();
1354 KCEventNotifier::PostKeychainEvent(it
->kcEvent
, mDb
->dlDbIdentifier(), primaryKey
);
1361 // notify that a keychain has changed in too many ways to count
1362 KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent
);
1363 mEventBuffer
->clear();
1367 KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent
);
1371 KeychainImpl::postEvent(SecKeychainEvent kcEvent
, ItemImpl
* item
)
1373 postEvent(kcEvent
, item
, NULL
);
1377 KeychainImpl::postEvent(SecKeychainEvent kcEvent
, ItemImpl
* item
, PrimaryKey pk
)
1379 PrimaryKey primaryKey
;
1384 StLock
<Mutex
>_(mMutex
);
1388 primaryKey
= item
->primaryKey();
1392 if (!mIsInBatchMode
)
1394 KCEventNotifier::PostKeychainEvent(kcEvent
, mDb
->dlDbIdentifier(), primaryKey
);
1398 StLock
<Mutex
>_(mMutex
);
1401 it
.kcEvent
= kcEvent
;
1407 mEventBuffer
->push_back (it
);
1411 void KeychainImpl::tickle() {
1412 if(!mSuppressTickle
) {
1413 globals().storageManager
.tickleKeychain(this);
1418 bool KeychainImpl::performKeychainUpgradeIfNeeded() {
1419 // Grab this keychain's mutex. This might not be sufficient, since the
1420 // keychain might have outstanding cursors. We'll grab the RWLock later if needed.
1421 StLock
<Mutex
>_(mMutex
);
1423 if(!globals().integrityProtection()) {
1424 secnotice("integrity", "skipping upgrade for %s due to global integrity protection being disabled", mDb
->name());
1428 // We need a CSP database for 'upgrade' to be meaningful
1429 if((mDb
->dl()->subserviceMask() & CSSM_SERVICE_CSP
) == 0) {
1433 // We only want to upgrade file-based Apple keychains. Check the GUID.
1434 if(mDb
->dl()->guid() != gGuidAppleCSPDL
) {
1435 secinfo("integrity", "skipping upgrade for %s due to guid mismatch\n", mDb
->name());
1439 // If we've already attempted an upgrade on this keychain, don't bother again
1440 if(mAttemptedUpgrade
) {
1444 // Don't upgrade the System root certificate keychain (to make old tp code happy)
1445 if(strncmp(mDb
->name(), SYSTEM_ROOT_STORE_PATH
, strlen(SYSTEM_ROOT_STORE_PATH
)) == 0) {
1446 secinfo("integrity", "skipping upgrade for %s\n", mDb
->name());
1450 uint32 dbBlobVersion
= SecurityServer::DbBlob::version_MacOS_10_0
;
1453 dbBlobVersion
= mDb
->dbBlobVersion();
1454 } catch (CssmError cssme
) {
1455 if(cssme
.error
== CSSMERR_DL_DATASTORE_DOESNOT_EXIST
) {
1456 // oh well! We tried to get the blob version of a database
1457 // that doesn't exist. It doesn't need migration, so do nothing.
1458 secnotice("integrity", "dbBlobVersion() failed for a non-existent database");
1461 // Some other error occurred. We can't upgrade this keychain, so fail.
1462 const char* errStr
= cssmErrorString(cssme
.error
);
1463 secnotice("integrity", "dbBlobVersion() failed for a CssmError: %d %s", (int) cssme
.error
, errStr
);
1467 secnotice("integrity", "dbBlobVersion() failed for an unknown reason");
1473 // Check the location of this keychain
1474 string path
= mDb
->name();
1475 string keychainDbPath
= StorageManager::makeKeychainDbFilename(path
);
1477 bool inHomeLibraryKeychains
= StorageManager::pathInHomeLibraryKeychains(path
);
1479 string keychainDbSuffix
= "-db";
1480 bool endsWithKeychainDb
= (path
.size() > keychainDbSuffix
.size() && (0 == path
.compare(path
.size() - keychainDbSuffix
.size(), keychainDbSuffix
.size(), keychainDbSuffix
)));
1482 bool isSystemKeychain
= (0 == path
.compare("/Library/Keychains/System.keychain"));
1484 bool result
= false;
1486 if(inHomeLibraryKeychains
&& endsWithKeychainDb
&& dbBlobVersion
== SecurityServer::DbBlob::version_MacOS_10_0
) {
1487 // something has gone horribly wrong: an old-versioned keychain has a .keychain-db name. Rename it.
1488 string basePath
= path
;
1489 basePath
.erase(basePath
.end()-3, basePath
.end());
1491 attemptKeychainRename(path
, basePath
, dbBlobVersion
);
1493 // If we moved to a good path, we might still want to perform the upgrade. Update our variables.
1497 dbBlobVersion
= mDb
->dbBlobVersion();
1498 } catch (CssmError cssme
) {
1499 const char* errStr
= cssmErrorString(cssme
.error
);
1500 secnotice("integrity", "dbBlobVersion() after a rename failed for a CssmError: %d %s", (int) cssme
.error
, errStr
);
1503 secnotice("integrity", "dbBlobVersion() failed for an unknown reason after a rename");
1507 endsWithKeychainDb
= (path
.size() > keychainDbSuffix
.size() && (0 == path
.compare(path
.size() - keychainDbSuffix
.size(), keychainDbSuffix
.size(), keychainDbSuffix
)));
1508 keychainDbPath
= StorageManager::makeKeychainDbFilename(path
);
1509 secnotice("integrity", "after rename, our database thinks that it is %s", path
.c_str());
1512 // Migrate an old keychain in ~/Library/Keychains
1513 if(inHomeLibraryKeychains
&& dbBlobVersion
!= SecurityServer::DbBlob::version_partition
&& !endsWithKeychainDb
) {
1514 // We can only attempt to migrate an unlocked keychain.
1515 if(mDb
->isLocked()) {
1516 // However, it's possible that while we weren't doing any keychain operations, someone upgraded the keychain,
1517 // and then locked it. No way around hitting the filesystem here: check for the existence of a new file and,
1518 // if no new file exists, quit.
1519 DLDbIdentifier mungedDLDbIdentifier
= StorageManager::mungeDLDbIdentifier(mDb
->dlDbIdentifier(), false);
1520 string
mungedPath(mungedDLDbIdentifier
.dbName());
1522 // If this matches the file we already have, skip the upgrade. Otherwise, continue.
1523 if(mungedPath
== path
) {
1524 secnotice("integrity", "skipping upgrade for locked keychain %s\n", mDb
->name());
1529 result
= keychainMigration(path
, dbBlobVersion
, keychainDbPath
, SecurityServer::DbBlob::version_partition
);
1530 } else if(inHomeLibraryKeychains
&& dbBlobVersion
== SecurityServer::DbBlob::version_partition
&& !endsWithKeychainDb
) {
1531 // This is a new-style keychain with the wrong name, try to rename it
1532 attemptKeychainRename(path
, keychainDbPath
, dbBlobVersion
);
1534 } else if(isSystemKeychain
&& dbBlobVersion
== SecurityServer::DbBlob::version_partition
) {
1535 // Try to "unupgrade" the system keychain, to clean up our old issues
1536 secnotice("integrity", "attempting downgrade for %s version %d (%d %d %d)", path
.c_str(), dbBlobVersion
, inHomeLibraryKeychains
, endsWithKeychainDb
, isSystemKeychain
);
1538 // First step: acquire the credentials to allow for ACL modification
1539 SecurityServer::SystemKeychainKey
skk(kSystemUnlockFile
);
1541 // We've managed to read the key; now, create credentials using it
1542 CssmClient::Key
systemKeychainMasterKey(csp(), skk
.key(), true);
1543 CssmClient::AclFactory::MasterKeyUnlockCredentials
creds(systemKeychainMasterKey
, Allocator::standard(Allocator::sensitive
));
1545 // Attempt the downgrade, using our master key as the ACL override
1546 result
= keychainMigration(path
, dbBlobVersion
, path
, SecurityServer::DbBlob::version_MacOS_10_0
, creds
.getAccessCredentials());
1548 secnotice("integrity", "Couldn't read System.keychain key, skipping update");
1551 secinfo("integrity", "not attempting migration for %s version %d (%d %d %d)", path
.c_str(), dbBlobVersion
, inHomeLibraryKeychains
, endsWithKeychainDb
, isSystemKeychain
);
1553 // Since we don't believe any migration needs to be done here, mark the
1554 // migration as "attempted" to short-circuit future checks.
1555 mAttemptedUpgrade
= true;
1558 // We might have changed our location on disk. Let StorageManager know.
1559 globals().storageManager
.registerKeychainImpl(this);
1561 // if we attempted a migration, try to clean up leftover files from <rdar://problem/23950408> XARA backup have provided me with 12GB of login keychain copies
1563 string pattern
= path
+ "_*_backup";
1565 secnotice("integrity", "globbing for %s", pattern
.c_str());
1566 int globresult
= glob(pattern
.c_str(), GLOB_MARK
, NULL
, &pglob
);
1567 if(globresult
== 0) {
1568 secnotice("integrity", "glob: %lu results", pglob
.gl_pathc
);
1569 if(pglob
.gl_pathc
> 10) {
1570 // There are more than 10 backup files, indicating a problem.
1571 // Delete all but one of them. Under rdar://23950408, they should all be identical.
1572 secnotice("integrity", "saving backup file: %s", pglob
.gl_pathv
[0]);
1573 for(int i
= 1; i
< pglob
.gl_pathc
; i
++) {
1574 secnotice("integrity", "cleaning up backup file: %s", pglob
.gl_pathv
[i
]);
1575 // ignore return code; this is a best-effort cleanup
1576 unlink(pglob
.gl_pathv
[i
]);
1581 bool pathExists
= (::stat(path
.c_str(), &st
) == 0);
1582 bool keychainDbPathExists
= (::stat(keychainDbPath
.c_str(), &st
) == 0);
1584 if(!pathExists
&& keychainDbPathExists
&& pglob
.gl_pathc
>= 1) {
1585 // We have a file at keychainDbPath, no file at path, and at least one backup keychain file.
1587 // Move the backup file to path, to simulate the current "split-world" view,
1588 // which copies from path to keychainDbPath, then modifies keychainDbPath.
1589 secnotice("integrity", "moving backup file %s to %s", pglob
.gl_pathv
[0], path
.c_str());
1590 ::rename(pglob
.gl_pathv
[0], path
.c_str());
1600 bool KeychainImpl::keychainMigration(const string oldPath
, const uint32 dbBlobVersion
, const string newPath
, const uint32 newBlobVersion
, const AccessCredentials
*cred
) {
1601 secnotice("integrity", "going to migrate %s at version %d to", oldPath
.c_str(), dbBlobVersion
);
1602 secnotice("integrity", " %s at version %d", newPath
.c_str(), newBlobVersion
);
1604 // We need to opportunistically perform the upgrade/reload dance.
1606 // If the keychain is unlocked, try to upgrade it.
1607 // In either case, reload the database from disk.
1608 // We need this keychain's read/write lock.
1610 // Try to grab the keychain write lock.
1611 StReadWriteLock
lock(mRWLock
, StReadWriteLock::TryWrite
);
1613 // If we didn't manage to grab the lock, there's readers out there
1614 // currently reading this keychain. Abort the upgrade.
1615 if(!lock
.isLocked()) {
1616 secnotice("integrity", "couldn't get read-write lock, aborting upgrade");
1620 // Take the file lock on the existing database. We don't need to commit this txion, because we're not planning to
1621 // change the original keychain.
1622 FileLockTransaction
fileLockmDb(mDb
);
1624 // Let's reload this keychain to see if someone changed it on disk
1625 globals().storageManager
.reloadKeychain(this);
1627 bool result
= false;
1630 // We can only attempt an upgrade if the keychain is currently unlocked
1631 // There's a TOCTTOU issue here, but it's going to be rare in practice, and the upgrade will simply fail.
1632 if(!mDb
->isLocked()) {
1633 secnotice("integrity", "have a plan to migrate database %s", mDb
->name());
1634 // Database blob is out of date. Attempt a migration.
1635 uint32 convertedVersion
= attemptKeychainMigration(oldPath
, dbBlobVersion
, newPath
, newBlobVersion
, cred
);
1636 if(convertedVersion
== newBlobVersion
) {
1637 secnotice("integrity", "conversion succeeded");
1640 secnotice("integrity", "conversion failed, keychain is still %d", convertedVersion
);
1643 secnotice("integrity", "keychain is locked, can't upgrade");
1645 } catch (CssmError cssme
) {
1646 const char* errStr
= cssmErrorString(cssme
.error
);
1647 secnotice("integrity", "caught CssmError: %d %s", (int) cssme
.error
, errStr
);
1649 // Something went wrong, but don't worry about it.
1650 secnotice("integrity", "caught unknown error");
1653 // No matter if the migrator succeeded, we need to reload this keychain from disk.
1654 secnotice("integrity", "reloading keychain after migration");
1655 globals().storageManager
.reloadKeychain(this);
1656 secnotice("integrity", "database %s is now version %d", mDb
->name(), mDb
->dbBlobVersion());
1661 // Make sure you have this keychain's mutex and write lock when you call this function!
1662 uint32
KeychainImpl::attemptKeychainMigration(const string oldPath
, const uint32 oldBlobVersion
, const string newPath
, const uint32 newBlobVersion
, const AccessCredentials
* cred
) {
1663 if(mDb
->dbBlobVersion() == newBlobVersion
) {
1664 // Someone else upgraded this, hurray!
1665 secnotice("integrity", "reloaded keychain version %d, quitting", mDb
->dbBlobVersion());
1666 return newBlobVersion
;
1669 mAttemptedUpgrade
= true;
1670 uint32 newDbVersion
= oldBlobVersion
;
1672 if( (oldBlobVersion
== SecurityServer::DbBlob::version_MacOS_10_0
&& newBlobVersion
== SecurityServer::DbBlob::version_partition
) ||
1673 (oldBlobVersion
== SecurityServer::DbBlob::version_partition
&& newBlobVersion
== SecurityServer::DbBlob::version_MacOS_10_0
&& cred
!= NULL
)) {
1674 // Here's the upgrade outline:
1676 // 1. Make a copy of the keychain with the new file path
1677 // 2. Open that keychain database.
1678 // 3. Recode it to use the new version.
1679 // 4. Notify the StorageManager that the DLDB identifier for this keychain has changed.
1681 // If we're creating a new keychain file, on failure, try to delete the new file. Otherwise,
1682 // everyone will try to use it.
1684 secnotice("integrity", "attempting migration from version %d to %d", oldBlobVersion
, newBlobVersion
);
1687 bool newFile
= (oldPath
!= newPath
);
1690 DLDbIdentifier
dldbi(dlDbIdentifier().ssuid(), newPath
.c_str(), dlDbIdentifier().dbLocation());
1692 secnotice("integrity", "creating a new keychain at %s", newPath
.c_str());
1693 db
= mDb
->cloneTo(dldbi
);
1695 secnotice("integrity", "using old keychain at %s", newPath
.c_str());
1698 FileLockTransaction
fileLockDb(db
);
1701 // since we're creating a completely new file, if this migration fails, delete the new file
1702 fileLockDb
.setDeleteOnFailure();
1705 // Let the upgrade begin.
1706 newDbVersion
= db
->recodeDbToVersion(newBlobVersion
);
1707 if(newDbVersion
!= newBlobVersion
) {
1708 // Recoding failed. Don't proceed.
1709 secnotice("integrity", "recodeDbToVersion failed, version is still %d", newDbVersion
);
1710 return newDbVersion
;
1713 secnotice("integrity", "recoded db successfully, adding extra integrity");
1715 Keychain
keychain(db
);
1717 // Breaking abstraction, but what're you going to do?
1718 // Don't upgrade this keychain, since we just upgraded the DB
1719 // But the DB won't return any new data until the txion commits
1720 keychain
->mAttemptedUpgrade
= true;
1721 keychain
->mSuppressTickle
= true;
1723 SecItemClass classes
[] = {kSecGenericPasswordItemClass
,
1724 kSecInternetPasswordItemClass
,
1725 kSecPublicKeyItemClass
,
1726 kSecPrivateKeyItemClass
,
1727 kSecSymmetricKeyItemClass
};
1729 for(int i
= 0; i
< sizeof(classes
) / sizeof(classes
[0]); i
++) {
1731 KCCursor kcc
= keychain
->createCursor(classes
[i
], NULL
);
1733 // During recoding, we might have deleted some corrupt keys.
1734 // Because of this, we might have zombie SSGroup records left in
1735 // the database that have no matching key. Tell the KCCursor to
1736 // delete these if found.
1737 // This will also try to suppress any other invalid items.
1738 kcc
->setDeleteInvalidRecords(true);
1740 while(kcc
->next(item
)) {
1742 if(newBlobVersion
== SecurityServer::DbBlob::version_partition
) {
1743 // Force the item to set integrity. The keychain is confused about its version because it hasn't written to disk yet,
1744 // but if we've reached this point, the keychain supports integrity.
1745 item
->setIntegrity(true);
1746 } else if(newBlobVersion
== SecurityServer::DbBlob::version_MacOS_10_0
) {
1747 // We're downgrading this keychain. Pass in whatever credentials our caller thinks will allow this ACL modification.
1748 item
->removeIntegrity(cred
);
1750 } catch(CssmError cssme
) {
1751 // During recoding, we might have deleted some corrupt keys. Because of this, we might have zombie SSGroup records left in
1752 // the database that have no matching key. If we get a DL_RECORD_NOT_FOUND error, delete the matching item record.
1753 if (cssme
.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND
) {
1754 secnotice("integrity", "deleting corrupt (Not Found) record");
1755 keychain
->deleteItem(item
);
1756 } else if(cssme
.osStatus() == CSSMERR_CSP_INVALID_KEY
) {
1757 secnotice("integrity", "deleting corrupt key record");
1758 keychain
->deleteItem(item
);
1766 // Tell securityd we're done with the upgrade, to re-enable all protections
1767 db
->recodeFinished();
1769 // If we reach here, tell the file locks to commit the transaction and return the new blob version
1770 fileLockDb
.success();
1772 secnotice("integrity", "success, returning version %d", newDbVersion
);
1773 return newDbVersion
;
1774 } catch(UnixError ue
) {
1775 secnotice("integrity", "caught UnixError: %d %s", ue
.unixError(), ue
.what());
1776 } catch (CssmError cssme
) {
1777 const char* errStr
= cssmErrorString(cssme
.error
);
1778 secnotice("integrity", "caught CssmError: %d %s", (int) cssme
.error
, errStr
);
1779 } catch (MacOSError mose
) {
1780 secnotice("integrity", "MacOSError: %d", (int)mose
.osStatus());
1781 } catch (const std::bad_cast
& e
) {
1782 secnotice("integrity", "***** bad cast: %s", e
.what());
1784 // We failed to migrate. We won't commit the transaction, so the blob on-disk stays the same.
1785 secnotice("integrity", "***** unknown error");
1788 secnotice("integrity", "no migration path for %s at version %d to", oldPath
.c_str(), oldBlobVersion
);
1789 secnotice("integrity", " %s at version %d", newPath
.c_str(), newBlobVersion
);
1790 return oldBlobVersion
;
1793 // If we reached here, the migration failed. Return the old version.
1794 return oldBlobVersion
;
1797 void KeychainImpl::attemptKeychainRename(const string oldPath
, const string newPath
, uint32 blobVersion
) {
1798 secnotice("integrity", "attempting to rename keychain (%d) from %s to %s", blobVersion
, oldPath
.c_str(), newPath
.c_str());
1800 // Take the file lock on this database, so other people won't try to move it before we do
1801 // NOTE: during a migration from a v256 to a v512 keychain, the db is first copied from the .keychain to the
1802 // .keychain-db path. Other non-migrating processes, if they open the keychain, enter this function to
1803 // try to move it back. These will attempt to take the .keychain-db file lock, but they will not succeed
1804 // until the migration is finished. Once they acquire that, they might try to take the .keychain file lock.
1805 // This is technically lock inversion, but deadlocks will not happen since the migrating process creates the
1806 // .keychain-db file lock before creating the .keychain-db file, so other processes will not try to grab the
1807 // .keychain-db lock in this function before the migrating process already has it.
1808 FileLockTransaction
fileLockmDb(mDb
);
1810 // first, check if someone renamed this keychain while we were grabbing the file lock
1811 globals().storageManager
.reloadKeychain(this);
1813 uint32 dbBlobVersion
= SecurityServer::DbBlob::version_MacOS_10_0
;
1816 dbBlobVersion
= mDb
->dbBlobVersion();
1818 secnotice("integrity", "dbBlobVersion() failed for an unknown reason while renaming, aborting rename");
1822 if(dbBlobVersion
!= blobVersion
) {
1823 secnotice("integrity", "database version changed while we were grabbing the file lock; aborting rename");
1827 if(oldPath
!= mDb
->name()) {
1828 secnotice("integrity", "database location changed while we were grabbing the file lock; aborting rename");
1832 // we're still at the original location and version; go ahead and do the move
1833 globals().storageManager
.rename(this, newPath
.c_str());
1836 Keychain::Keychain()
1838 dispatch_once(&SecKeychainSystemKeychainChecked
, ^{
1839 check_system_keychain();
1843 Keychain::~Keychain()
1850 Keychain::optional(SecKeychainRef handle
)
1853 return KeychainImpl::required(handle
);
1855 return globals().storageManager
.defaultKeychain();
1859 CFIndex
KeychainCore::GetKeychainRetainCount(Keychain
& kc
)
1861 CFTypeRef ref
= kc
->handle(false);
1862 return CFGetRetainCount(ref
);
1867 // Create default credentials for this keychain.
1868 // This is triggered upon default open (i.e. a Db::activate() with no set credentials).
1870 // This function embodies the "default credentials" logic for Keychain-layer databases.
1872 const AccessCredentials
*
1873 KeychainImpl::makeCredentials()
1875 return defaultCredentials();
1879 const AccessCredentials
*
1880 KeychainImpl::defaultCredentials()
1882 StLock
<Mutex
>_(mMutex
);
1884 // Use custom unlock credentials for file keychains which have a referral
1885 // record and the standard credentials for all others.
1887 if (mDb
->dl()->guid() == gGuidAppleCSPDL
&& mCustomUnlockCreds(mDb
))
1888 return &mCustomUnlockCreds
;
1890 if (mDb
->dl()->guid() == gGuidAppleSdCSPDL
)
1891 return globals().smartcardCredentials();
1893 return globals().keychainCredentials();
1898 bool KeychainImpl::mayDelete()
1903 bool KeychainImpl::hasIntegrityProtection() {
1904 StLock
<Mutex
>_(mMutex
);
1906 // This keychain only supports integrity if there's a database attached, that database is an Apple CSPDL, and the blob version is high enough
1907 if(mDb
&& (mDb
->dl()->guid() == gGuidAppleCSPDL
)) {
1908 if(mDb
->dbBlobVersion() >= SecurityServer::DbBlob::version_partition
) {
1911 secnotice("integrity", "keychain blob version does not support integrity");
1915 secnotice("integrity", "keychain guid does not support integrity");