#include <security_cdsa_utilities/cssmacl.h>
#include <security_cdsa_utilities/cssmdb.h>
#include <security_utilities/trackingallocator.h>
+#include <security_utilities/FileLockTransaction.h>
#include <security_keychain/SecCFTypes.h>
#include <securityd_client/ssblob.h>
#include <Security/TrustSettingsSchema.h>
#include <Security/SecKeychainItemPriv.h>
#include <CoreFoundation/CoreFoundation.h>
-#include "DLDbListCFPref.h"
+#include "DLDBListCFPref.h"
#include <fcntl.h>
+#include <glob.h>
#include <sys/param.h>
#include <syslog.h>
#include <sys/stat.h>
capacity *= 2;
if (capacity <= i) capacity = i + 1;
tagBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
- formatBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
+ formatBuf=reinterpret_cast<UInt32 *>(realloc(formatBuf, (capacity*sizeof(UInt32))));
}
tagBuf[i]=rit->first;
formatBuf[i++]=rit->second;
// KeychainImpl
//
KeychainImpl::KeychainImpl(const Db &db)
- : mAttemptedUpgrade(false), mDbItemMapMutex(Mutex::recursive), mDbDeletedItemMapMutex(Mutex::recursive),
+: mCacheTimer(NULL), mSuppressTickle(false), mAttemptedUpgrade(false), mDbItemMapMutex(Mutex::recursive), mDbDeletedItemMapMutex(Mutex::recursive),
mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive)
{
dispatch_once(&SecKeychainSystemKeychainChecked, ^{
}
Mutex*
-KeychainImpl::getMutexForObject()
+KeychainImpl::getMutexForObject() const
{
return globals().storageManager.getStorageManagerMutex();
}
return &mMutex;
}
-ReadWriteLock*
-KeychainImpl::getKeychainReadWriteLock()
-{
- return &mRWLock;
-}
-
void KeychainImpl::aboutToDestruct()
{
// remove me from the global cache, we are done
AclFactory::PasswordChangeCredentials pCreds (password, alloc);
AclFactory::AnyResourceContext rcc(pCreds);
create(&rcc);
+
+ // Now that we've created, trigger setting the defaultCredentials
+ mDb->open();
}
void KeychainImpl::create(ConstStringPtr inPassword)
AclFactory aclFactory;
AclFactory::AnyResourceContext rcc(aclFactory.unlockCred());
create(&rcc);
+
+ // Now that we've created, trigger setting the defaultCredentials
+ mDb->open();
}
void KeychainImpl::createWithBlob(CssmData &blob)
UInt32 newPasswordLength, const void *newPassword)
{
StLock<Mutex>_(mMutex);
-
+
+ secnotice("KCspi", "Attempting to change passphrase for %s", mDb->name());
+
bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL);
TrackingAllocator allocator(Allocator::standard());
AutoCredentials cred = AutoCredentials(allocator);
+
if (oldPassword)
{
const CssmData &oldPass = *new(allocator) CssmData(const_cast<void *>(oldPassword), oldPasswordLength);
UInt32
KeychainImpl::status() const
{
+ StLock<Mutex>_(mMutex);
+
// @@@ We should figure out the read/write status though a DL passthrough
// or some other way. Also should locked be unlocked read only or just
// read-only?
void KeychainImpl::completeAdd(Item &inItem, PrimaryKey &primaryKey)
{
-
-
// The inItem shouldn't be in the cache yet
assert(!inItem->inCache());
// uniquifying items. We really need to insert the item into the
// map before we start the add. And have the item be in an
// "is being added" state.
- secdebug("keychain", "add of new item %p somehow replaced %p",
+ secnotice("keychain", "add of new item %p somehow replaced %p",
inItem.get(), oldItem);
mDbItemMap.erase(p.first);
void
KeychainImpl::addCopy(Item &inItem)
{
+ StLock<Mutex>_(mMutex);
+
Keychain keychain(this);
PrimaryKey primaryKey = inItem->addWithCopyInfo(keychain, true);
completeAdd(inItem, primaryKey);
void
KeychainImpl::add(Item &inItem)
{
+ StLock<Mutex>_(mMutex);
+
Keychain keychain(this);
PrimaryKey primaryKey = inItem->add(keychain);
completeAdd(inItem, primaryKey);
// uniquifying items. We really need to insert the item into
// the map with the new primary key before we start the update.
// And have the item be in an "is being updated" state.
- secdebug("keychain", "update of item %p somehow replaced %p",
+ secnotice("keychain", "update of item %p somehow replaced %p",
inItem.get(), oldItem);
mDbItemMap.erase(p.first);
}
}
- // Item updates now are technically a delete and re-add, so post these events instead of kSecUpdateEvent
- postEvent(kSecDeleteEvent, inItem);
- postEvent(kSecAddEvent, inItem);
+ // Item updates now are technically a delete and re-add, so post these events instead of kSecUpdateEvent
+ postEvent(kSecDeleteEvent, inItem, oldPK);
+ postEvent(kSecAddEvent, inItem);
}
void
KeychainImpl::deleteItem(Item &inoutItem)
{
+ StLock<Mutex>_(mMutex);
+
{
- // We don't need to hold the DO mutex through event posting, and, in fact, doing so causes deadlock.
- // Hold it only as long as needed, instead.
-
-
// item must be persistent
if (!inoutItem->isPersistent())
MacOSError::throwMe(errSecInvalidItemRef);
- secdebug("kcnotify", "starting deletion of item %p", inoutItem.get());
+ secinfo("kcnotify", "starting deletion of item %p", inoutItem.get());
DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord();
PrimaryKey primaryKey = inoutItem->primaryKey();
postEvent(kSecDeleteEvent, inoutItem);
}
+void KeychainImpl::changeDatabase(CssmClient::Db db)
+{
+ StLock<Mutex>_(mDbMutex);
+ mDb = db;
+ mDb->defaultCredentials(this);
+}
+
CssmClient::CSP
KeychainImpl::csp()
{
StLock<Mutex>_(mMutex);
- if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP)
+ if (!(mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP))
MacOSError::throwMe(errSecInvalidKeychain);
// Try to cast first to a CSPDL to handle case where we don't have an SSDb
ItemImpl *
KeychainImpl::_lookupDeletedItemOnly(const PrimaryKey &primaryKey)
{
+ StLock<Mutex> _(mDbDeletedItemMapMutex);
DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey);
if (it != mDbDeletedItemMap.end())
{
try
{
// We didn't find it so create a new item with just a keychain and
- // a primary key. However since we aren't holding
- // globals().apiLock anymore some other thread might have beaten
+ // a primary key. Some other thread might have beaten
// us to creating this item and adding it to the cache. If that
// happens we retry the lookup.
return Item(this, primaryKey);
StLock<Mutex>_(mMutex);
// Called by CCallbackMgr
- secdebug("kcnotify", "%p notified that item %p was deleted", this, inItemImpl);
+ secinfo("kcnotify", "%p notified that item %p was deleted", this, inItemImpl);
removeItem(inItemImpl->primaryKey(), inItemImpl);
}
}
} // drop mDbDeletedItemMapMutex
} catch(UnixError ue) {
- secdebugfunc("keychain", "caught UnixError: %d %s", ue.unixError(), ue.what());
+ secnotice("keychain", "caught UnixError: %d %s", ue.unixError(), ue.what());
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
- secdebugfunc("keychain", "caught CssmError: %d %s", (int) cssme.error, errStr);
+ secnotice("keychain", "caught CssmError: %d %s", (int) cssme.error, errStr);
} catch (MacOSError mose) {
- secdebugfunc("keychain", "MacOSError: %d", (int)mose.osStatus());
+ secnotice("keychain", "MacOSError: %d", (int)mose.osStatus());
} catch(...) {
- secdebugfunc("keychain", "Unknown error");
+ secnotice("keychain", "Unknown error");
}
}
}
// notify that a keychain has changed in too many ways to count
- KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent);
+ KCEventNotifier::PostKeychainEvent((SecKeychainEvent) kSecKeychainLeftBatchModeEvent);
mEventBuffer->clear();
}
else
{
- KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent);
+ KCEventNotifier::PostKeychainEvent((SecKeychainEvent) kSecKeychainEnteredBatchModeEvent);
}
}
-
void
KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item)
+{
+ postEvent(kcEvent, item, NULL);
+}
+
+void
+KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item, PrimaryKey pk)
{
PrimaryKey primaryKey;
- {
+ if(pk.get()) {
+ primaryKey = pk;
+ } else {
StLock<Mutex>_(mMutex);
if (item != NULL)
}
}
+void KeychainImpl::tickle() {
+ if(!mSuppressTickle) {
+ globals().storageManager.tickleKeychain(this);
+ }
+}
+
bool KeychainImpl::performKeychainUpgradeIfNeeded() {
- // Grab this keychain's mutex. This might not be sufficient, since the
- // keychain might have outstanding cursors. We'll grab the RWLock later if needed.
+ // Grab this keychain's mutex.
StLock<Mutex>_(mMutex);
if(!globals().integrityProtection()) {
- secdebugfunc("integrity", "skipping upgrade for %s due to global integrity protection being diabled", mDb->name());
+ secnotice("integrity", "skipping upgrade for %s due to global integrity protection being disabled", mDb->name());
return false;
}
// We only want to upgrade file-based Apple keychains. Check the GUID.
if(mDb->dl()->guid() != gGuidAppleCSPDL) {
- secdebugfunc("integrity", "skipping upgrade for %s due to guid mismatch\n", mDb->name());
+ secinfo("integrity", "skipping upgrade for %s due to guid mismatch\n", mDb->name());
return false;
}
// Don't upgrade the System root certificate keychain (to make old tp code happy)
if(strncmp(mDb->name(), SYSTEM_ROOT_STORE_PATH, strlen(SYSTEM_ROOT_STORE_PATH)) == 0) {
- secdebugfunc("integrity", "skipping upgrade for %s\n", mDb->name());
+ secinfo("integrity", "skipping upgrade for %s\n", mDb->name());
return false;
}
if(cssme.error == CSSMERR_DL_DATASTORE_DOESNOT_EXIST) {
// oh well! We tried to get the blob version of a database
// that doesn't exist. It doesn't need migration, so do nothing.
- secdebugfunc("integrity", "dbBlobVersion() failed for a non-existent database");
+ secnotice("integrity", "dbBlobVersion() failed for a non-existent database");
return false;
} else {
// Some other error occurred. We can't upgrade this keychain, so fail.
const char* errStr = cssmErrorString(cssme.error);
- secdebugfunc("integrity", "dbBlobVersion() failed for a CssmError: %d %s", (int) cssme.error, errStr);
+ secnotice("integrity", "dbBlobVersion() failed for a CssmError: %d %s", (int) cssme.error, errStr);
return false;
}
} catch (...) {
- secdebugfunc("integrity", "dbBlobVersion() failed for an unknown reason");
+ secnotice("integrity", "dbBlobVersion() failed for an unknown reason");
return false;
}
- if(dbBlobVersion != SecurityServer::DbBlob::currentVersion) {
- secdebugfunc("integrity", "going to upgrade %s from version %d to %d!", mDb->name(), dbBlobVersion, SecurityServer::DbBlob::currentVersion);
- // We need to opportunistically perform the upgrade/reload dance.
- //
- // If the keychain is unlocked, try to upgrade it.
- // In either case, reload the database from disk.
- // We need this keychain's read/write lock.
- // Try to grab the keychain write lock.
- StReadWriteLock lock(mRWLock, StReadWriteLock::TryWrite);
+ // Check the location of this keychain
+ string path = mDb->name();
+ string keychainDbPath = StorageManager::makeKeychainDbFilename(path);
- // If we didn't manage to grab the lock, there's readers out there
- // currently reading this keychain. Abort the upgrade.
- if(!lock.isLocked()) {
- return false;
- }
+ bool inHomeLibraryKeychains = StorageManager::pathInHomeLibraryKeychains(path);
+
+ string keychainDbSuffix = "-db";
+ bool endsWithKeychainDb = (path.size() > keychainDbSuffix.size() && (0 == path.compare(path.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix)));
+
+ bool isSystemKeychain = (0 == path.compare("/Library/Keychains/System.keychain"));
+
+ bool result = false;
+
+ if(inHomeLibraryKeychains && endsWithKeychainDb && dbBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0) {
+ // something has gone horribly wrong: an old-versioned keychain has a .keychain-db name. Rename it.
+ string basePath = path;
+ basePath.erase(basePath.end()-3, basePath.end());
+
+ attemptKeychainRename(path, basePath, dbBlobVersion);
+
+ // If we moved to a good path, we might still want to perform the upgrade. Update our variables.
+ path = mDb->name();
try {
- // We can only attempt an upgrade if the keychain is currently unlocked
- // There's a TOCTTOU issue here, but it's going to be rare in practice, and the upgrade will simply fail.
- if(!mDb->isLocked()) {
- secdebugfunc("integrity", "attempting migration on database %s", mDb->name());
- // Database blob is out of date. Attempt a migration.
- uint32 convertedVersion = attemptKeychainMigration(dbBlobVersion, SecurityServer::DbBlob::currentVersion);
- if(convertedVersion == SecurityServer::DbBlob::currentVersion) {
- secdebugfunc("integrity", "conversion succeeded");
- } else {
- secdebugfunc("integrity", "conversion failed, keychain is still %d", convertedVersion);
- }
- }
+ dbBlobVersion = mDb->dbBlobVersion();
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
- secdebugfunc("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
+ secnotice("integrity", "dbBlobVersion() after a rename failed for a CssmError: %d %s", (int) cssme.error, errStr);
+ return false;
} catch (...) {
- // Something went wrong, but don't worry about it.
+ secnotice("integrity", "dbBlobVersion() failed for an unknown reason after a rename");
+ return false;
+ }
+
+ endsWithKeychainDb = (path.size() > keychainDbSuffix.size() && (0 == path.compare(path.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix)));
+ keychainDbPath = StorageManager::makeKeychainDbFilename(path);
+ secnotice("integrity", "after rename, our database thinks that it is %s", path.c_str());
+ }
+
+ // Migrate an old keychain in ~/Library/Keychains
+ if(inHomeLibraryKeychains && dbBlobVersion != SecurityServer::DbBlob::version_partition && !endsWithKeychainDb) {
+ // We can only attempt to migrate an unlocked keychain.
+ if(mDb->isLocked()) {
+ // However, it's possible that while we weren't doing any keychain operations, someone upgraded the keychain,
+ // and then locked it. No way around hitting the filesystem here: check for the existence of a new file and,
+ // if no new file exists, quit.
+ DLDbIdentifier mungedDLDbIdentifier = StorageManager::mungeDLDbIdentifier(mDb->dlDbIdentifier(), false);
+ string mungedPath(mungedDLDbIdentifier.dbName());
+
+ // If this matches the file we already have, skip the upgrade. Otherwise, continue.
+ if(mungedPath == path) {
+ secnotice("integrity", "skipping upgrade for locked keychain %s\n", mDb->name());
+ return false;
+ }
}
- // No matter if the migrator succeeded, we need to reload this keychain from disk.
- // Maybe someone else beat us to upgrading the keychain, but it's been locked since then.
- secdebugfunc("integrity", "reloading keychain");
- globals().storageManager.reloadKeychain(this);
- secdebugfunc("integrity", "database %s is version %d", mDb->name(), mDb->dbBlobVersion());
+ result = keychainMigration(path, dbBlobVersion, keychainDbPath, SecurityServer::DbBlob::version_partition);
+ } else if(inHomeLibraryKeychains && dbBlobVersion == SecurityServer::DbBlob::version_partition && !endsWithKeychainDb) {
+ // This is a new-style keychain with the wrong name, try to rename it
+ attemptKeychainRename(path, keychainDbPath, dbBlobVersion);
+ result = true;
+ } else if(isSystemKeychain && dbBlobVersion == SecurityServer::DbBlob::version_partition) {
+ // Try to "unupgrade" the system keychain, to clean up our old issues
+ secnotice("integrity", "attempting downgrade for %s version %d (%d %d %d)", path.c_str(), dbBlobVersion, inHomeLibraryKeychains, endsWithKeychainDb, isSystemKeychain);
+
+ // First step: acquire the credentials to allow for ACL modification
+ SecurityServer::SystemKeychainKey skk(kSystemUnlockFile);
+ if(skk.valid()) {
+ // We've managed to read the key; now, create credentials using it
+ CssmClient::Key systemKeychainMasterKey(csp(), skk.key(), true);
+ CssmClient::AclFactory::MasterKeyUnlockCredentials creds(systemKeychainMasterKey, Allocator::standard(Allocator::sensitive));
+
+ // Attempt the downgrade, using our master key as the ACL override
+ result = keychainMigration(path, dbBlobVersion, path, SecurityServer::DbBlob::version_MacOS_10_0, creds.getAccessCredentials());
+ } else {
+ secnotice("integrity", "Couldn't read System.keychain key, skipping update");
+ }
+ } else {
+ secinfo("integrity", "not attempting migration for %s version %d (%d %d %d)", path.c_str(), dbBlobVersion, inHomeLibraryKeychains, endsWithKeychainDb, isSystemKeychain);
+
+ // Since we don't believe any migration needs to be done here, mark the
+ // migration as "attempted" to short-circuit future checks.
+ mAttemptedUpgrade = true;
+ }
+
+ // We might have changed our location on disk. Let StorageManager know.
+ globals().storageManager.registerKeychainImpl(this);
+
+ // 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
+ if(result) {
+ string pattern = path + "_*_backup";
+ glob_t pglob = {};
+ secnotice("integrity", "globbing for %s", pattern.c_str());
+ int globresult = glob(pattern.c_str(), GLOB_MARK, NULL, &pglob);
+ if(globresult == 0) {
+ secnotice("integrity", "glob: %lu results", pglob.gl_pathc);
+ if(pglob.gl_pathc > 10) {
+ // There are more than 10 backup files, indicating a problem.
+ // Delete all but one of them. Under rdar://23950408, they should all be identical.
+ secnotice("integrity", "saving backup file: %s", pglob.gl_pathv[0]);
+ for(int i = 1; i < pglob.gl_pathc; i++) {
+ secnotice("integrity", "cleaning up backup file: %s", pglob.gl_pathv[i]);
+ // ignore return code; this is a best-effort cleanup
+ unlink(pglob.gl_pathv[i]);
+ }
+ }
+
+ struct stat st;
+ bool pathExists = (::stat(path.c_str(), &st) == 0);
+ bool keychainDbPathExists = (::stat(keychainDbPath.c_str(), &st) == 0);
+
+ if(!pathExists && keychainDbPathExists && pglob.gl_pathc >= 1) {
+ // We have a file at keychainDbPath, no file at path, and at least one backup keychain file.
+ //
+ // Move the backup file to path, to simulate the current "split-world" view,
+ // which copies from path to keychainDbPath, then modifies keychainDbPath.
+ secnotice("integrity", "moving backup file %s to %s", pglob.gl_pathv[0], path.c_str());
+ ::rename(pglob.gl_pathv[0], path.c_str());
+ }
+ }
- return true;
+ globfree(&pglob);
}
- return false;
+ return result;
}
-// Make sure you have this keychain's mutex and write lock when you call this function!
-uint32 KeychainImpl::attemptKeychainMigration(uint32 oldBlobVersion, uint32 newBlobVersion) {
- Db db = mDb; // let's not muck up our db for now
- db->takeFileLock();
+bool KeychainImpl::keychainMigration(const string oldPath, const uint32 dbBlobVersion, const string newPath, const uint32 newBlobVersion, const AccessCredentials *cred) {
+ secnotice("integrity", "going to migrate %s at version %d to", oldPath.c_str(), dbBlobVersion);
+ secnotice("integrity", " %s at version %d", newPath.c_str(), newBlobVersion);
+
+ // We need to opportunistically perform the upgrade/reload dance.
+ //
+ // If the keychain is unlocked, try to upgrade it.
+ // In either case, reload the database from disk.
+
+ // Try to grab the keychain mutex (although we should already have it)
+ StLock<Mutex>_(mMutex);
+
+ // Take the file lock on the existing database. We don't need to commit this txion, because we're not planning to
+ // change the original keychain.
+ FileLockTransaction fileLockmDb(mDb);
// Let's reload this keychain to see if someone changed it on disk
globals().storageManager.reloadKeychain(this);
+ bool result = false;
+
+ try {
+ // We can only attempt an upgrade if the keychain is currently unlocked
+ // There's a TOCTTOU issue here, but it's going to be rare in practice, and the upgrade will simply fail.
+ if(!mDb->isLocked()) {
+ secnotice("integrity", "have a plan to migrate database %s", mDb->name());
+ // Database blob is out of date. Attempt a migration.
+ uint32 convertedVersion = attemptKeychainMigration(oldPath, dbBlobVersion, newPath, newBlobVersion, cred);
+ if(convertedVersion == newBlobVersion) {
+ secnotice("integrity", "conversion succeeded");
+ result = true;
+ } else {
+ secnotice("integrity", "conversion failed, keychain is still %d", convertedVersion);
+ }
+ } else {
+ secnotice("integrity", "keychain is locked, can't upgrade");
+ }
+ } catch (CssmError cssme) {
+ const char* errStr = cssmErrorString(cssme.error);
+ secnotice("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
+ } catch (...) {
+ // Something went wrong, but don't worry about it.
+ secnotice("integrity", "caught unknown error");
+ }
+
+ // No matter if the migrator succeeded, we need to reload this keychain from disk.
+ secnotice("integrity", "reloading keychain after migration");
+ globals().storageManager.reloadKeychain(this);
+ secnotice("integrity", "database %s is now version %d", mDb->name(), mDb->dbBlobVersion());
+
+ return result;
+}
+
+// Make sure you have this keychain's mutex and write lock when you call this function!
+uint32 KeychainImpl::attemptKeychainMigration(const string oldPath, const uint32 oldBlobVersion, const string newPath, const uint32 newBlobVersion, const AccessCredentials* cred) {
if(mDb->dbBlobVersion() == newBlobVersion) {
// Someone else upgraded this, hurray!
- secdebugfunc("integrity", "reloaded keychain version %d, quitting", mDb->dbBlobVersion());
- db->releaseFileLock(false);
+ secnotice("integrity", "reloaded keychain version %d, quitting", mDb->dbBlobVersion());
return newBlobVersion;
}
mAttemptedUpgrade = true;
uint32 newDbVersion = oldBlobVersion;
- try {
- secdebugfunc("integrity", "attempting migration from version %d to %d", oldBlobVersion, newBlobVersion);
+ if( (oldBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && newBlobVersion == SecurityServer::DbBlob::version_partition) ||
+ (oldBlobVersion == SecurityServer::DbBlob::version_partition && newBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && cred != NULL)) {
+ // Here's the upgrade outline:
+ //
+ // 1. Make a copy of the keychain with the new file path
+ // 2. Open that keychain database.
+ // 3. Recode it to use the new version.
+ // 4. Notify the StorageManager that the DLDB identifier for this keychain has changed.
+ //
+ // If we're creating a new keychain file, on failure, try to delete the new file. Otherwise,
+ // everyone will try to use it.
+
+ secnotice("integrity", "attempting migration from version %d to %d", oldBlobVersion, newBlobVersion);
+
+ Db db;
+ bool newFile = (oldPath != newPath);
+
+ try {
+ DLDbIdentifier dldbi(dlDbIdentifier().ssuid(), newPath.c_str(), dlDbIdentifier().dbLocation());
+ if(newFile) {
+ secnotice("integrity", "creating a new keychain at %s", newPath.c_str());
+ db = mDb->cloneTo(dldbi);
+ } else {
+ secnotice("integrity", "using old keychain at %s", newPath.c_str());
+ db = mDb;
+ }
+ FileLockTransaction fileLockDb(db);
- // First, make a backup of this database (so we never lose data if something goes wrong)
- mDb->makeBackup();
+ if(newFile) {
+ // since we're creating a completely new file, if this migration fails, delete the new file
+ fileLockDb.setDeleteOnFailure();
+ }
- if(oldBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && newBlobVersion == SecurityServer::DbBlob::version_partition) {
// Let the upgrade begin.
newDbVersion = db->recodeDbToVersion(newBlobVersion);
if(newDbVersion != newBlobVersion) {
// Recoding failed. Don't proceed.
- secdebugfunc("integrity", "recodeDbToVersion failed, version is still %d", newDbVersion);
- db->releaseFileLock(false);
+ secnotice("integrity", "recodeDbToVersion failed, version is still %d", newDbVersion);
return newDbVersion;
}
- secdebugfunc("integrity", "recoded db successfully, adding extra integrity");
+ secnotice("integrity", "recoded db successfully, adding extra integrity");
Keychain keychain(db);
// Don't upgrade this keychain, since we just upgraded the DB
// But the DB won't return any new data until the txion commits
keychain->mAttemptedUpgrade = true;
+ keychain->mSuppressTickle = true;
SecItemClass classes[] = {kSecGenericPasswordItemClass,
kSecInternetPasswordItemClass,
while(kcc->next(item)) {
try {
- // Force the item to set integrity. The keychain is confused about its version because it hasn't written to disk yet,
- // but if we've reached this point, the keychain supports integrity.
- item->setIntegrity(true);
+ if(newBlobVersion == SecurityServer::DbBlob::version_partition) {
+ // Force the item to set integrity. The keychain is confused about its version because it hasn't written to disk yet,
+ // but if we've reached this point, the keychain supports integrity.
+ item->setIntegrity(true);
+ } else if(newBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0) {
+ // We're downgrading this keychain. Pass in whatever credentials our caller thinks will allow this ACL modification.
+ item->removeIntegrity(cred);
+ }
} catch(CssmError cssme) {
// During recoding, we might have deleted some corrupt keys. Because of this, we might have zombie SSGroup records left in
// the database that have no matching key. If we get a DL_RECORD_NOT_FOUND error, delete the matching item record.
if (cssme.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND) {
- secdebugfunc("integrity", "deleting corrupt (Not Found) record");
+ secnotice("integrity", "deleting corrupt (Not Found) record");
+ keychain->deleteItem(item);
+ } else if(cssme.osStatus() == CSSMERR_CSP_INVALID_KEY) {
+ secnotice("integrity", "deleting corrupt key record");
keychain->deleteItem(item);
} else {
throw;
}
}
- // If we reach here, tell releaseFileLock() to commit the transaction and return the new blob version
- secdebugfunc("integrity", "releasing file lock");
- db->releaseFileLock(true);
+ // Tell securityd we're done with the upgrade, to re-enable all protections
+ db->recodeFinished();
+
+ // If we reach here, tell the file locks to commit the transaction and return the new blob version
+ fileLockDb.success();
- secdebugfunc("integrity", "success, returning version %d", newDbVersion);
+ secnotice("integrity", "success, returning version %d", newDbVersion);
return newDbVersion;
+ } catch(UnixError ue) {
+ secnotice("integrity", "caught UnixError: %d %s", ue.unixError(), ue.what());
+ } catch (CssmError cssme) {
+ const char* errStr = cssmErrorString(cssme.error);
+ secnotice("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
+ } catch (MacOSError mose) {
+ secnotice("integrity", "MacOSError: %d", (int)mose.osStatus());
+ } catch (const std::bad_cast & e) {
+ secnotice("integrity", "***** bad cast: %s", e.what());
+ } catch (...) {
+ // We failed to migrate. We won't commit the transaction, so the blob on-disk stays the same.
+ secnotice("integrity", "***** unknown error");
}
- } catch (CssmError cssme) {
- const char* errStr = cssmErrorString(cssme.error);
- secdebugfunc("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
- db->releaseFileLock(false);
- } catch (MacOSError mose) {
- secdebugfunc("integrity", "MacOSError: %d", (int)mose.osStatus());
- db->releaseFileLock(false);
- } catch (...) {
- // We failed to migrate. We won't commit the transaction, so the blob on-disk stays the same.
- secdebugfunc("integrity", "unknown error");
- db->releaseFileLock(false);
+ } else {
+ secnotice("integrity", "no migration path for %s at version %d to", oldPath.c_str(), oldBlobVersion);
+ secnotice("integrity", " %s at version %d", newPath.c_str(), newBlobVersion);
+ return oldBlobVersion;
}
- // If we reached here, we failed the migration. Return the old version.
+ // If we reached here, the migration failed. Return the old version.
return oldBlobVersion;
}
+void KeychainImpl::attemptKeychainRename(const string oldPath, const string newPath, uint32 blobVersion) {
+ secnotice("integrity", "attempting to rename keychain (%d) from %s to %s", blobVersion, oldPath.c_str(), newPath.c_str());
+
+ // Take the file lock on this database, so other people won't try to move it before we do
+ // NOTE: during a migration from a v256 to a v512 keychain, the db is first copied from the .keychain to the
+ // .keychain-db path. Other non-migrating processes, if they open the keychain, enter this function to
+ // try to move it back. These will attempt to take the .keychain-db file lock, but they will not succeed
+ // until the migration is finished. Once they acquire that, they might try to take the .keychain file lock.
+ // This is technically lock inversion, but deadlocks will not happen since the migrating process creates the
+ // .keychain-db file lock before creating the .keychain-db file, so other processes will not try to grab the
+ // .keychain-db lock in this function before the migrating process already has it.
+ FileLockTransaction fileLockmDb(mDb);
+
+ // first, check if someone renamed this keychain while we were grabbing the file lock
+ globals().storageManager.reloadKeychain(this);
+
+ uint32 dbBlobVersion = SecurityServer::DbBlob::version_MacOS_10_0;
+
+ try {
+ dbBlobVersion = mDb->dbBlobVersion();
+ } catch (...) {
+ secnotice("integrity", "dbBlobVersion() failed for an unknown reason while renaming, aborting rename");
+ return;
+ }
+
+ if(dbBlobVersion != blobVersion) {
+ secnotice("integrity", "database version changed while we were grabbing the file lock; aborting rename");
+ return;
+ }
+
+ if(oldPath != mDb->name()) {
+ secnotice("integrity", "database location changed while we were grabbing the file lock; aborting rename");
+ return;
+ }
+
+ // we're still at the original location and version; go ahead and do the move
+ globals().storageManager.rename(this, newPath.c_str());
+}
Keychain::Keychain()
{
}
bool KeychainImpl::hasIntegrityProtection() {
+ StLock<Mutex>_(mMutex);
+
// This keychain only supports integrity if there's a database attached, that database is an Apple CSPDL, and the blob version is high enough
if(mDb && (mDb->dl()->guid() == gGuidAppleCSPDL)) {
if(mDb->dbBlobVersion() >= SecurityServer::DbBlob::version_partition) {
return true;
} else {
- secdebugfunc("integrity", "keychain blob version does not support integrity");
+ secnotice("integrity", "keychain blob version does not support integrity");
return false;
}
} else {
- secdebugfunc("integrity", "keychain guid does not support integrity");
+ secnotice("integrity", "keychain guid does not support integrity");
return false;
}
- return false;
}