--- /dev/null
+/*
+ * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+
+/*
+ File: StorageManager.cpp
+
+ Contains: Working with multiple keychains
+
+*/
+
+#include "StorageManager.h"
+#include "KCEventNotifier.h"
+
+#include <Security/cssmapple.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <algorithm>
+#include <string>
+#include <stdio.h>
+//#include <Security/AuthorizationTags.h>
+//#include <Security/AuthSession.h>
+#include <security_utilities/debugging.h>
+#include <security_keychain/SecCFTypes.h>
+//#include <Security/SecurityAgentClient.h>
+#include <securityd_client/ssclient.h>
+#include <Security/AuthorizationTags.h>
+#include <Security/AuthorizationTagsPriv.h>
+#include <Security/SecTask.h>
+#include <security_keychain/SecCFTypes.h>
+#include "TrustSettingsSchema.h"
+#include <security_cdsa_client/wrapkey.h>
+
+//%%% add this to AuthorizationTagsPriv.h later
+#ifndef AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL
+#define AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL "loginKCCreate:suppressResetPanel"
+#endif
+
+#include "KCCursor.h"
+#include "Globals.h"
+
+
+using namespace CssmClient;
+using namespace KeychainCore;
+
+#define kLoginKeychainPathPrefix "~/Library/Keychains/"
+#define kUserLoginKeychainPath "~/Library/Keychains/login.keychain"
+#define kEmptyKeychainSizeInBytes 20460
+
+//-----------------------------------------------------------------------------------
+
+static SecPreferencesDomain defaultPreferenceDomain()
+{
+ SessionAttributeBits sessionAttrs;
+ if (gServerMode) {
+ secdebug("servermode", "StorageManager initialized in server mode");
+ sessionAttrs = sessionIsRoot;
+ } else {
+ MacOSError::check(SessionGetInfo(callerSecuritySession, NULL, &sessionAttrs));
+ }
+
+ // If this is the root session, use system preferences.
+ // (In SecurityServer debug mode, you'll get a (fake) root session
+ // that has graphics access. Ignore that to help testing.)
+ if ((sessionAttrs & sessionIsRoot)
+ IFDEBUG( && !(sessionAttrs & sessionHasGraphicAccess))) {
+ secdebug("storagemgr", "using system preferences");
+ return kSecPreferencesDomainSystem;
+ }
+
+ // otherwise, use normal (user) preferences
+ return kSecPreferencesDomainUser;
+}
+
+static bool isAppSandboxed()
+{
+ bool result = false;
+ SecTaskRef task = SecTaskCreateFromSelf(NULL);
+ if(task != NULL) {
+ CFTypeRef appSandboxValue = SecTaskCopyValueForEntitlement(task,
+ CFSTR("com.apple.security.app-sandbox"), NULL);
+ if(appSandboxValue != NULL) {
+ result = true;
+ CFRelease(appSandboxValue);
+ }
+ CFRelease(task);
+ }
+ return result;
+}
+
+static bool shouldAddToSearchList(const DLDbIdentifier &dLDbIdentifier)
+{
+ // Creation of a private keychain should not modify the search list: rdar://13529331
+ // However, we want to ensure the login and System keychains are in
+ // the search list if that is not the case when they are created.
+ // Note that App Sandbox apps may not modify the list in either case.
+
+ bool loginOrSystemKeychain = false;
+ const char *dbname = dLDbIdentifier.dbName();
+ if (dbname) {
+ if ((!strcmp(dbname, "/Library/Keychains/System.keychain")) ||
+ (strstr(dbname, "/login.keychain")) ) {
+ loginOrSystemKeychain = true;
+ }
+ }
+ return (loginOrSystemKeychain && !isAppSandboxed());
+}
+
+
+StorageManager::StorageManager() :
+ mSavedList(defaultPreferenceDomain()),
+ mCommonList(kSecPreferencesDomainCommon),
+ mDomain(kSecPreferencesDomainUser),
+ mMutex(Mutex::recursive)
+{
+}
+
+
+Mutex*
+StorageManager::getStorageManagerMutex()
+{
+ return &mKeychainMapMutex;
+}
+
+
+Keychain
+StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier)
+{
+ StLock<Mutex>_(mKeychainMapMutex);
+
+ if (!dLDbIdentifier)
+ return Keychain();
+
+ KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+ if (it != mKeychains.end())
+ {
+ if (it->second == NULL) // cleared by weak reference?
+ {
+ mKeychains.erase(it);
+ }
+ else
+ {
+ return it->second;
+ }
+ }
+
+ if (gServerMode) {
+ secdebug("servermode", "keychain reference in server mode");
+ return Keychain();
+ }
+
+ // The keychain is not in our cache. Create it.
+ Module module(dLDbIdentifier.ssuid().guid());
+ DL dl;
+ if (dLDbIdentifier.ssuid().subserviceType() & CSSM_SERVICE_CSP)
+ dl = SSCSPDL(module);
+ else
+ dl = DL(module);
+
+ dl->subserviceId(dLDbIdentifier.ssuid().subserviceId());
+ dl->version(dLDbIdentifier.ssuid().version());
+ Db db(dl, dLDbIdentifier.dbName());
+
+ Keychain keychain(db);
+ // Add the keychain to the cache.
+ mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, &*keychain));
+ keychain->inCache(true);
+
+ return keychain;
+}
+
+void
+StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier,
+ KeychainImpl *keychainImpl)
+{
+ // Lock the recursive mutex
+
+ StLock<Mutex>_(mKeychainMapMutex);
+
+ KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+ if (it != mKeychains.end() && (KeychainImpl*) it->second == keychainImpl)
+ mKeychains.erase(it);
+
+ keychainImpl->inCache(false);
+}
+
+void
+StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier)
+{
+ // Lock the recursive mutex
+
+ StLock<Mutex>_(mKeychainMapMutex);
+
+ KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+ if (it != mKeychains.end())
+ {
+ if (it->second != NULL) // did we get zapped by weak reference destruction
+ {
+ KeychainImpl *keychainImpl = it->second;
+ keychainImpl->inCache(false);
+ }
+
+ mKeychains.erase(it);
+ }
+}
+
+// Create keychain if it doesn't exist, and optionally add it to the search list.
+Keychain
+StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add)
+{
+ StLock<Mutex>_(mKeychainMapMutex);
+
+ Keychain theKeychain = keychain(dLDbIdentifier);
+ bool post = false;
+ bool updateList = (add && shouldAddToSearchList(dLDbIdentifier));
+
+ if (updateList)
+ {
+ mSavedList.revert(false);
+ DLDbList searchList = mSavedList.searchList();
+ if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
+ return theKeychain; // theKeychain is already in the searchList.
+
+ mCommonList.revert(false);
+ searchList = mCommonList.searchList();
+ if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
+ return theKeychain; // theKeychain is already in the commonList don't add it to the searchList.
+
+ // If theKeychain doesn't exist don't bother adding it to the search list yet.
+ if (!theKeychain->exists())
+ return theKeychain;
+
+ // theKeychain exists and is not in our search list, so add it to the
+ // search list.
+ mSavedList.revert(true);
+ mSavedList.add(dLDbIdentifier);
+ mSavedList.save();
+ post = true;
+ }
+
+ if (post)
+ {
+ // Make sure we are not holding mStorageManagerLock anymore when we
+ // post this event.
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+
+ return theKeychain;
+}
+
+// Be notified a Keychain just got created.
+void
+StorageManager::created(const Keychain &keychain)
+{
+ StLock<Mutex>_(mKeychainMapMutex);
+
+ DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
+ bool defaultChanged = false;
+ bool updateList = shouldAddToSearchList(dLDbIdentifier);
+
+ if (updateList)
+ {
+ mSavedList.revert(true);
+ // If we don't have a default Keychain yet. Make the newly created
+ // keychain the default.
+ if (!mSavedList.defaultDLDbIdentifier())
+ {
+ mSavedList.defaultDLDbIdentifier(dLDbIdentifier);
+ defaultChanged = true;
+ }
+
+ // Add the keychain to the search list prefs.
+ mSavedList.add(dLDbIdentifier);
+ mSavedList.save();
+
+ // Make sure we are not holding mLock when we post these events.
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+
+ if (defaultChanged)
+ {
+ KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, dLDbIdentifier);
+ }
+}
+
+KCCursor
+StorageManager::createCursor(SecItemClass itemClass,
+ const SecKeychainAttributeList *attrList)
+{
+ StLock<Mutex>_(mMutex);
+
+ KeychainList searchList;
+ getSearchList(searchList);
+ return KCCursor(searchList, itemClass, attrList);
+}
+
+KCCursor
+StorageManager::createCursor(const SecKeychainAttributeList *attrList)
+{
+ StLock<Mutex>_(mMutex);
+
+ KeychainList searchList;
+ getSearchList(searchList);
+ return KCCursor(searchList, attrList);
+}
+
+void
+StorageManager::lockAll()
+{
+ StLock<Mutex>_(mMutex);
+
+ SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard());
+ ss.lockAll (false);
+}
+
+Keychain
+StorageManager::defaultKeychain()
+{
+ StLock<Mutex>_(mMutex);
+
+ Keychain theKeychain;
+ CFTypeRef ref;
+
+ {
+ mSavedList.revert(false);
+ DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier());
+ if (defaultDLDbIdentifier)
+ {
+ theKeychain = keychain(defaultDLDbIdentifier);
+ ref = theKeychain->handle(false);
+ }
+ }
+
+ if (theKeychain /* && theKeychain->exists() */)
+ return theKeychain;
+
+ MacOSError::throwMe(errSecNoDefaultKeychain);
+}
+
+void
+StorageManager::defaultKeychain(const Keychain &keychain)
+{
+ StLock<Mutex>_(mMutex);
+
+ // Only set a keychain as the default if we own it and can read/write it,
+ // and our uid allows modifying the directory for that preference domain.
+ if (!keychainOwnerPermissionsValidForDomain(keychain->name(), mDomain))
+ MacOSError::throwMe(errSecWrPerm);
+
+ DLDbIdentifier oldDefaultId;
+ DLDbIdentifier newDefaultId(keychain->dlDbIdentifier());
+ {
+ oldDefaultId = mSavedList.defaultDLDbIdentifier();
+ mSavedList.revert(true);
+ mSavedList.defaultDLDbIdentifier(newDefaultId);
+ mSavedList.save();
+ }
+
+ if (!(oldDefaultId == newDefaultId))
+ {
+ // Make sure we are not holding mLock when we post this event.
+ KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId);
+ }
+}
+
+Keychain
+StorageManager::defaultKeychain(SecPreferencesDomain domain)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ if (domain == mDomain)
+ return defaultKeychain();
+ else
+ {
+ DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier());
+ if (defaultDLDbIdentifier)
+ return keychain(defaultDLDbIdentifier);
+
+ MacOSError::throwMe(errSecNoDefaultKeychain);
+ }
+}
+
+void
+StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ if (domain == mDomain)
+ defaultKeychain(keychain);
+ else
+ DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier());
+}
+
+Keychain
+StorageManager::loginKeychain()
+{
+ StLock<Mutex>_(mMutex);
+
+ Keychain theKeychain;
+ {
+ mSavedList.revert(false);
+ DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
+ if (loginDLDbIdentifier)
+ {
+ theKeychain = keychain(loginDLDbIdentifier);
+ }
+ }
+
+ if (theKeychain && theKeychain->exists())
+ return theKeychain;
+
+ MacOSError::throwMe(errSecNoSuchKeychain);
+}
+
+void
+StorageManager::loginKeychain(Keychain keychain)
+{
+ StLock<Mutex>_(mMutex);
+
+ mSavedList.revert(true);
+ mSavedList.loginDLDbIdentifier(keychain->dlDbIdentifier());
+ mSavedList.save();
+}
+
+size_t
+StorageManager::size()
+{
+ StLock<Mutex>_(mMutex);
+
+ mSavedList.revert(false);
+ mCommonList.revert(false);
+ return mSavedList.searchList().size() + mCommonList.searchList().size();
+}
+
+Keychain
+StorageManager::at(unsigned int ix)
+{
+ StLock<Mutex>_(mMutex);
+
+ mSavedList.revert(false);
+ DLDbList dLDbList = mSavedList.searchList();
+ if (ix < dLDbList.size())
+ {
+ return keychain(dLDbList[ix]);
+ }
+ else
+ {
+ ix -= dLDbList.size();
+ mCommonList.revert(false);
+ DLDbList commonList = mCommonList.searchList();
+ if (ix >= commonList.size())
+ MacOSError::throwMe(errSecInvalidKeychain);
+
+ return keychain(commonList[ix]);
+ }
+}
+
+Keychain
+StorageManager::operator[](unsigned int ix)
+{
+ StLock<Mutex>_(mMutex);
+
+ return at(ix);
+}
+
+void StorageManager::rename(Keychain keychain, const char* newName)
+{
+
+ StLock<Mutex>_(mKeychainMapMutex);
+
+ bool changedDefault = false;
+ DLDbIdentifier newDLDbIdentifier;
+ {
+ mSavedList.revert(true);
+ DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
+
+ // Find the keychain object for the given ref
+ DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
+
+ // Actually rename the database on disk.
+ keychain->database()->rename(newName);
+
+ if (dLDbIdentifier == defaultId)
+ changedDefault=true;
+
+ newDLDbIdentifier = keychain->dlDbIdentifier();
+ // Rename the keychain in the search list.
+ mSavedList.rename(dLDbIdentifier, newDLDbIdentifier);
+
+ // If this was the default keychain change it accordingly
+ if (changedDefault)
+ mSavedList.defaultDLDbIdentifier(newDLDbIdentifier);
+
+ mSavedList.save();
+
+ // we aren't worried about a weak reference here, because we have to
+ // hold a lock on an item in order to do the rename
+
+ // Now update the Keychain cache
+ if (keychain->inCache())
+ {
+ KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+ if (it != mKeychains.end() && (KeychainImpl*) it->second == keychain.get())
+ {
+ // Remove the keychain from the cache under its old
+ // dLDbIdentifier
+ mKeychains.erase(it);
+ }
+ }
+
+ // If we renamed this keychain on top of an existing one we should
+ // drop the old one from the cache.
+ KeychainMap::iterator it = mKeychains.find(newDLDbIdentifier);
+ if (it != mKeychains.end())
+ {
+ Keychain oldKeychain(it->second);
+ oldKeychain->inCache(false);
+ // @@@ Ideally we should invalidate or fault this keychain object.
+ }
+
+ if (keychain->inCache())
+ {
+ // If the keychain wasn't in the cache to being with let's not put
+ // it there now. There was probably a good reason it wasn't in it.
+ // If the keychain was in the cache, update it to use
+ // newDLDbIdentifier.
+ mKeychains.insert(KeychainMap::value_type(newDLDbIdentifier,
+ keychain));
+ }
+ }
+
+ // Make sure we are not holding mLock when we post these events.
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+
+ if (changedDefault)
+ KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent,
+ newDLDbIdentifier);
+}
+
+void StorageManager::renameUnique(Keychain keychain, CFStringRef newName)
+{
+ StLock<Mutex>_(mMutex);
+
+ bool doneCreating = false;
+ int index = 1;
+ do
+ {
+ char newNameCString[MAXPATHLEN];
+ if ( CFStringGetCString(newName, newNameCString, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
+ {
+ // Construct the new name...
+ //
+ CFMutableStringRef newNameCFStr = NULL;
+ newNameCFStr = CFStringCreateMutable(NULL, MAXPATHLEN);
+ if ( newNameCFStr )
+ {
+ CFStringAppendFormat(newNameCFStr, NULL, CFSTR("%s%d"), newNameCString, index);
+ CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix)); // add .keychain
+ char toUseBuff2[MAXPATHLEN];
+ if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
+ {
+ struct stat filebuf;
+ if ( lstat(toUseBuff2, &filebuf) )
+ {
+ rename(keychain, toUseBuff2);
+ KeychainList kcList;
+ kcList.push_back(keychain);
+ remove(kcList, false);
+ doneCreating = true;
+ }
+ else
+ index++;
+ }
+ else
+ doneCreating = true; // failure to get c string.
+ CFRelease(newNameCFStr);
+ }
+ else
+ doneCreating = false; // failure to create mutable string.
+ }
+ else
+ doneCreating = false; // failure to get the string (i.e. > MAXPATHLEN?)
+ }
+ while (!doneCreating && index != INT_MAX);
+}
+
+#define KEYCHAIN_SYNC_KEY CFSTR("KeychainSyncList")
+#define KEYCHAIN_SYNC_DOMAIN CFSTR("com.apple.keychainsync")
+
+static CFStringRef MakeExpandedPath (const char* path)
+{
+ std::string name = DLDbListCFPref::ExpandTildesInPath (std::string (path));
+ CFStringRef expanded = CFStringCreateWithCString (NULL, name.c_str (), 0);
+ return expanded;
+}
+
+void StorageManager::removeKeychainFromSyncList (const DLDbIdentifier &id)
+{
+ StLock<Mutex>_(mMutex);
+
+ // make a CFString of our identifier
+ const char* idname = id.dbName ();
+ if (idname == NULL)
+ {
+ return;
+ }
+
+ CFRef<CFStringRef> idString = MakeExpandedPath (idname);
+
+ // check and see if this keychain is in the keychain syncing list
+ CFArrayRef value =
+ (CFArrayRef) CFPreferencesCopyValue (KEYCHAIN_SYNC_KEY,
+ KEYCHAIN_SYNC_DOMAIN,
+ kCFPreferencesCurrentUser,
+ kCFPreferencesAnyHost);
+ if (value == NULL)
+ {
+ return;
+ }
+
+ // make a mutable copy of the dictionary
+ CFRef<CFMutableArrayRef> mtValue = CFArrayCreateMutableCopy (NULL, 0, value);
+ CFRelease (value);
+
+ // walk the array, looking for the value
+ CFIndex i;
+ CFIndex limit = CFArrayGetCount (mtValue.get());
+ bool found = false;
+
+ for (i = 0; i < limit; ++i)
+ {
+ CFDictionaryRef idx = (CFDictionaryRef) CFArrayGetValueAtIndex (mtValue.get(), i);
+ CFStringRef v = (CFStringRef) CFDictionaryGetValue (idx, CFSTR("DbName"));
+ if (v == NULL)
+ {
+ return; // something is really wrong if this is taken
+ }
+
+ char* stringBuffer = NULL;
+ const char* pathString = CFStringGetCStringPtr(v, 0);
+ if (pathString == 0)
+ {
+ CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(v), kCFStringEncodingUTF8) + 1;
+ stringBuffer = (char*) malloc(maxLen);
+ CFStringGetCString(v, stringBuffer, maxLen, kCFStringEncodingUTF8);
+ pathString = stringBuffer;
+ }
+
+ CFStringRef vExpanded = MakeExpandedPath(pathString);
+ CFComparisonResult result = CFStringCompare (vExpanded, idString.get(), 0);
+ if (stringBuffer != NULL)
+ {
+ free(stringBuffer);
+ }
+
+ CFRelease (vExpanded);
+
+ if (result == 0)
+ {
+ CFArrayRemoveValueAtIndex (mtValue.get(), i);
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ {
+#ifndef NDEBUG
+ CFShow (mtValue.get());
+#endif
+
+ CFPreferencesSetValue (KEYCHAIN_SYNC_KEY,
+ mtValue,
+ KEYCHAIN_SYNC_DOMAIN,
+ kCFPreferencesCurrentUser,
+ kCFPreferencesAnyHost);
+ CFPreferencesSynchronize (KEYCHAIN_SYNC_DOMAIN, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ }
+}
+
+void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb)
+{
+ StLock<Mutex>_(mMutex);
+
+ bool unsetDefault = false;
+ bool updateList = (!isAppSandboxed());
+
+ if (updateList)
+ {
+ mSavedList.revert(true);
+ DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
+ for (KeychainList::const_iterator ix = kcsToRemove.begin();
+ ix != kcsToRemove.end(); ++ix)
+ {
+ // Find the keychain object for the given ref
+ Keychain theKeychain = *ix;
+ DLDbIdentifier dLDbIdentifier = theKeychain->dlDbIdentifier();
+
+ // Remove it from the saved list
+ mSavedList.remove(dLDbIdentifier);
+ if (dLDbIdentifier == defaultId)
+ unsetDefault=true;
+
+ if (deleteDb)
+ {
+ removeKeychainFromSyncList (dLDbIdentifier);
+
+ // Now remove it from the cache
+ removeKeychain(dLDbIdentifier, theKeychain.get());
+ }
+ }
+
+ if (unsetDefault)
+ mSavedList.defaultDLDbIdentifier(DLDbIdentifier());
+
+ mSavedList.save();
+ }
+
+ if (deleteDb)
+ {
+ // Delete the actual databases without holding any locks.
+ for (KeychainList::const_iterator ix = kcsToRemove.begin();
+ ix != kcsToRemove.end(); ++ix)
+ {
+ (*ix)->database()->deleteDb();
+ }
+ }
+
+ if (updateList) {
+ // Make sure we are not holding mLock when we post these events.
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+
+ if (unsetDefault)
+ KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent);
+}
+
+void
+StorageManager::getSearchList(KeychainList &keychainList)
+{
+ // hold the global lock since we make keychain objects in this function
+
+ // to do: each of the items in this list must be retained, otherwise mayhem will occur
+ StLock<Mutex>_(mMutex);
+
+ if (gServerMode) {
+ keychainList.clear();
+ return;
+ }
+
+ mSavedList.revert(false);
+ mCommonList.revert(false);
+
+ // Merge mSavedList, mDynamicList and mCommonList
+ DLDbList dLDbList = mSavedList.searchList();
+ DLDbList dynamicList = mDynamicList.searchList();
+ DLDbList commonList = mCommonList.searchList();
+ KeychainList result;
+ result.reserve(dLDbList.size() + dynamicList.size() + commonList.size());
+
+ {
+ for (DLDbList::const_iterator it = dynamicList.begin();
+ it != dynamicList.end(); ++it)
+ {
+ Keychain k = keychain(*it);
+ result.push_back(k);
+ }
+
+ for (DLDbList::const_iterator it = dLDbList.begin();
+ it != dLDbList.end(); ++it)
+ {
+ Keychain k = keychain(*it);
+ result.push_back(k);
+ }
+
+ for (DLDbList::const_iterator it = commonList.begin();
+ it != commonList.end(); ++it)
+ {
+ Keychain k = keychain(*it);
+ result.push_back(k);
+ }
+ }
+
+ keychainList.swap(result);
+}
+
+void
+StorageManager::setSearchList(const KeychainList &keychainList)
+{
+ StLock<Mutex>_(mMutex);
+
+ DLDbList commonList = mCommonList.searchList();
+
+ // Strip out the common list part from the end of the search list.
+ KeychainList::const_iterator it_end = keychainList.end();
+ DLDbList::const_reverse_iterator end_common = commonList.rend();
+ for (DLDbList::const_reverse_iterator it_common = commonList.rbegin(); it_common != end_common; ++it_common)
+ {
+ // Eliminate common entries from the end of the passed in keychainList.
+ if (it_end == keychainList.begin())
+ break;
+
+ --it_end;
+ if (!((*it_end)->dlDbIdentifier() == *it_common))
+ {
+ ++it_end;
+ break;
+ }
+ }
+
+ /* it_end now points one past the last element in keychainList which is not in commonList. */
+ DLDbList searchList, oldSearchList(mSavedList.searchList());
+ for (KeychainList::const_iterator it = keychainList.begin(); it != it_end; ++it)
+ {
+ searchList.push_back((*it)->dlDbIdentifier());
+ }
+
+ {
+ // Set the current searchlist to be what was passed in, the old list will be freed
+ // upon exit of this stackframe.
+ mSavedList.revert(true);
+ mSavedList.searchList(searchList);
+ mSavedList.save();
+ }
+
+ if (!(oldSearchList == searchList))
+ {
+ // Make sure we are not holding mLock when we post this event.
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+}
+
+void
+StorageManager::getSearchList(SecPreferencesDomain domain, KeychainList &keychainList)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (gServerMode) {
+ keychainList.clear();
+ return;
+ }
+
+ if (domain == kSecPreferencesDomainDynamic)
+ {
+ convertList(keychainList, mDynamicList.searchList());
+ }
+ else if (domain == mDomain)
+ {
+ mSavedList.revert(false);
+ convertList(keychainList, mSavedList.searchList());
+ }
+ else
+ {
+ convertList(keychainList, DLDbListCFPref(domain).searchList());
+ }
+}
+
+void StorageManager::forceUserSearchListReread()
+{
+ mSavedList.forceUserSearchListReread();
+}
+
+void
+StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ DLDbList searchList;
+ convertList(searchList, keychainList);
+
+ if (domain == mDomain)
+ {
+ DLDbList oldSearchList(mSavedList.searchList());
+ {
+ // Set the current searchlist to be what was passed in, the old list will be freed
+ // upon exit of this stackframe.
+ mSavedList.revert(true);
+ mSavedList.searchList(searchList);
+ mSavedList.save();
+ }
+
+ if (!(oldSearchList == searchList))
+ {
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+ }
+ else
+ {
+ DLDbListCFPref(domain).searchList(searchList);
+ }
+}
+
+void
+StorageManager::domain(SecPreferencesDomain domain)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ if (domain == mDomain)
+ return; // no change
+
+#if !defined(NDEBUG)
+ switch (domain)
+ {
+ case kSecPreferencesDomainSystem:
+ secdebug("storagemgr", "switching to system domain"); break;
+ case kSecPreferencesDomainUser:
+ secdebug("storagemgr", "switching to user domain (uid %d)", getuid()); break;
+ default:
+ secdebug("storagemgr", "switching to weird prefs domain %d", domain); break;
+ }
+#endif
+
+ mDomain = domain;
+ mSavedList.set(domain);
+}
+
+void
+StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (!keychainOrArray)
+ getSearchList(keychainList);
+ else
+ {
+ CFTypeID typeID = CFGetTypeID(keychainOrArray);
+ if (typeID == CFArrayGetTypeID())
+ convertToKeychainList(CFArrayRef(keychainOrArray), keychainList);
+ else if (typeID == gTypes().KeychainImpl.typeID)
+ keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray)));
+ else
+ MacOSError::throwMe(errSecParam);
+ }
+}
+
+// static methods.
+void
+StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList)
+{
+ CFIndex count = CFArrayGetCount(keychainArray);
+ if (!(count > 0))
+ return;
+
+ KeychainList keychains(count);
+ for (CFIndex ix = 0; ix < count; ++ix)
+ {
+ keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix)));
+ }
+
+ keychainList.swap(keychains);
+}
+
+CFArrayRef
+StorageManager::convertFromKeychainList(const KeychainList &keychainList)
+{
+ CFRef<CFMutableArrayRef> keychainArray(CFArrayCreateMutable(NULL, keychainList.size(), &kCFTypeArrayCallBacks));
+
+ for (KeychainList::const_iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix)
+ {
+ SecKeychainRef keychainRef = (*ix)->handle();
+ CFArrayAppendValue(keychainArray, keychainRef);
+ CFRelease(keychainRef);
+ }
+
+ // Counter the CFRelease that CFRef<> is about to do when keychainArray goes out of scope.
+ CFRetain(keychainArray);
+ return keychainArray;
+}
+
+void StorageManager::convertList(DLDbList &ids, const KeychainList &kcs)
+{
+ DLDbList result;
+ result.reserve(kcs.size());
+ for (KeychainList::const_iterator ix = kcs.begin(); ix != kcs.end(); ++ix)
+ {
+ result.push_back((*ix)->dlDbIdentifier());
+ }
+ ids.swap(result);
+}
+
+void StorageManager::convertList(KeychainList &kcs, const DLDbList &ids)
+{
+ StLock<Mutex>_(mMutex);
+
+ KeychainList result;
+ result.reserve(ids.size());
+ {
+ for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix)
+ result.push_back(keychain(*ix));
+ }
+ kcs.swap(result);
+}
+
+#pragma mark ____ Login Functions ____
+
+void StorageManager::login(AuthorizationRef authRef, UInt32 nameLength, const char* name)
+{
+ StLock<Mutex>_(mMutex);
+
+ AuthorizationItemSet* info = NULL;
+ OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); // get the results of the copy rights call.
+ Boolean created = false;
+ if ( result == errSecSuccess && info->count )
+ {
+ // Grab the password from the auth context (info) and create the keychain...
+ //
+ AuthorizationItem* currItem = info->items;
+ for (UInt32 index = 1; index <= info->count; index++) //@@@plugin bug won't return a specific context.
+ {
+ if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
+ {
+ // creates the login keychain with the specified password
+ try
+ {
+ login(nameLength, name, (UInt32)currItem->valueLength, currItem->value);
+ created = true;
+ }
+ catch(...)
+ {
+ }
+ break;
+ }
+ currItem++;
+ }
+ }
+ if ( info )
+ AuthorizationFreeItemSet(info);
+
+ if ( !created )
+ MacOSError::throwMe(errAuthorizationInternal);
+}
+
+void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
+{
+ StLock<Mutex>_(mMutex);
+
+ if ( name == NULL || password == NULL )
+ MacOSError::throwMe(errSecParam);
+
+ login(name[0], name + 1, password[0], password + 1);
+}
+
+void StorageManager::login(UInt32 nameLength, const void *name,
+ UInt32 passwordLength, const void *password)
+{
+ if (passwordLength != 0 && password == NULL)
+ {
+ secdebug("KCLogin", "StorageManager::login: invalid argument (NULL password)");
+ MacOSError::throwMe(errSecParam);
+ }
+
+ DLDbIdentifier loginDLDbIdentifier;
+ {
+ mSavedList.revert(true);
+ loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
+ }
+
+ secdebug("KCLogin", "StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ if (!loginDLDbIdentifier)
+ MacOSError::throwMe(errSecNoSuchKeychain);
+
+
+ //***************************************************************
+ // gather keychain information
+ //***************************************************************
+
+ // user name
+ int uid = geteuid();
+ struct passwd *pw = getpwuid(uid);
+ if (pw == NULL) {
+ secdebug("KCLogin", "StorageManager::login: invalid argument (NULL uid)");
+ MacOSError::throwMe(errSecParam);
+ }
+ char *userName = pw->pw_name;
+
+ // make keychain path strings
+ std::string keychainPath = DLDbListCFPref::ExpandTildesInPath(kLoginKeychainPathPrefix);
+ std::string shortnameKeychain = keychainPath + userName;
+ std::string shortnameDotKeychain = shortnameKeychain + ".keychain";
+ std::string loginDotKeychain = keychainPath + "login.keychain";
+ std::string loginRenamed1Keychain = keychainPath + "login_renamed1.keychain";
+
+ // check for existence of keychain files
+ bool shortnameKeychainExists = false;
+ bool shortnameDotKeychainExists = false;
+ bool loginKeychainExists = false;
+ bool loginRenamed1KeychainExists = false;
+ {
+ struct stat st;
+ int stat_result;
+ stat_result = ::stat(shortnameKeychain.c_str(), &st);
+ shortnameKeychainExists = (stat_result == 0);
+ stat_result = ::stat(shortnameDotKeychain.c_str(), &st);
+ shortnameDotKeychainExists = (stat_result == 0);
+ stat_result = ::stat(loginDotKeychain.c_str(), &st);
+ loginKeychainExists = (stat_result == 0);
+ stat_result = ::stat(loginRenamed1Keychain.c_str(), &st);
+ loginRenamed1KeychainExists = (stat_result == 0);
+ }
+
+ bool loginUnlocked = false;
+
+ // make the keychain identifiers
+ CSSM_VERSION version = {0, 0};
+ DLDbIdentifier shortnameDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameKeychain.c_str(), NULL);
+ DLDbIdentifier shortnameDotDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameDotKeychain.c_str(), NULL);
+ DLDbIdentifier loginRenamed1DLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, loginRenamed1Keychain.c_str(), NULL);
+
+ //***************************************************************
+ // make file renaming changes first
+ //***************************************************************
+
+ // if "~/Library/Keychains/shortname" exists, we need to migrate it forward;
+ // either to login.keychain if there isn't already one, otherwise to shortname.keychain
+ if (shortnameKeychainExists) {
+ int rename_stat = 0;
+ if (loginKeychainExists) {
+ struct stat st;
+ int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
+ if (tmp_result == 0) {
+ if (st.st_size <= kEmptyKeychainSizeInBytes) {
+ tmp_result = ::unlink(loginDotKeychain.c_str());
+ rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
+ shortnameKeychainExists = (rename_stat != 0);
+ }
+ }
+ }
+ if (shortnameKeychainExists) {
+ if (loginKeychainExists && !shortnameDotKeychainExists) {
+ rename_stat = ::rename(shortnameKeychain.c_str(), shortnameDotKeychain.c_str());
+ shortnameDotKeychainExists = (rename_stat == 0);
+ } else if (!loginKeychainExists) {
+ rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
+ loginKeychainExists = (rename_stat == 0);
+ } else {
+ // we have all 3 keychains: login.keychain, shortname, and shortname.keychain.
+ // on Leopard we never want a shortname keychain, so we must move it aside.
+ char pathbuf[MAXPATHLEN];
+ std::string shortnameRenamedXXXKeychain = keychainPath;
+ shortnameRenamedXXXKeychain += userName;
+ shortnameRenamedXXXKeychain += "_renamed_XXX.keychain";
+ ::strlcpy(pathbuf, shortnameRenamedXXXKeychain.c_str(), sizeof(pathbuf));
+ ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
+ rename_stat = ::rename(shortnameKeychain.c_str(), pathbuf);
+ shortnameKeychainExists = (rename_stat != 0);
+ }
+ }
+ if (rename_stat != 0) {
+ MacOSError::throwMe(errno);
+ }
+ }
+
+ //***************************************************************
+ // handle special case where user previously reset the keychain
+ //***************************************************************
+ // Since 9A581, we have changed the definition of kKeychainRenamedSuffix from "_renamed" to "_renamed_".
+ // Therefore, if "login_renamed1.keychain" exists and there is no plist, the user may have run into a
+ // prior upgrade issue and clicked Reset. If we can successfully unlock login_renamed1.keychain with the
+ // supplied password, then we will attempt to rename it to login.keychain if that file is empty, or with
+ // "shortname.keychain" if it is not.
+
+ if (loginRenamed1KeychainExists && (!loginKeychainExists ||
+ (mSavedList.searchList().size() == 1 && mSavedList.member(loginDLDbIdentifier)) )) {
+ try
+ {
+ Keychain loginRenamed1KC(keychain(loginRenamed1DLDbIdentifier));
+ secdebug("KCLogin", "Attempting to unlock %s with %d-character password",
+ (loginRenamed1KC) ? loginRenamed1KC->name() : "<NULL>", (unsigned int)passwordLength);
+ loginRenamed1KC->unlock(CssmData(const_cast<void *>(password), passwordLength));
+ // if we get here, we unlocked it
+ if (loginKeychainExists) {
+ struct stat st;
+ int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
+ if (tmp_result == 0) {
+ if (st.st_size <= kEmptyKeychainSizeInBytes) {
+ tmp_result = ::unlink(loginDotKeychain.c_str());
+ tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
+ } else if (!shortnameDotKeychainExists) {
+ tmp_result = ::rename(loginRenamed1Keychain.c_str(), shortnameDotKeychain.c_str());
+ shortnameDotKeychainExists = (tmp_result == 0);
+ } else {
+ throw 1; // can't do anything with it except move it out of the way
+ }
+ }
+ } else {
+ int tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
+ loginKeychainExists = (tmp_result == 0);
+ }
+ }
+ catch(...)
+ {
+ // we failed to unlock the login_renamed1.keychain file with the login password.
+ // move it aside so we don't try to deal with it again.
+ char pathbuf[MAXPATHLEN];
+ std::string loginRenamedXXXKeychain = keychainPath;
+ loginRenamedXXXKeychain += "login_renamed_XXX.keychain";
+ ::strlcpy(pathbuf, loginRenamedXXXKeychain.c_str(), sizeof(pathbuf));
+ ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
+ ::rename(loginRenamed1Keychain.c_str(), pathbuf);
+ }
+ }
+
+ // if login.keychain does not exist at this point, create it
+ if (!loginKeychainExists) {
+ Keychain theKeychain(keychain(loginDLDbIdentifier));
+ secdebug("KCLogin", "Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ theKeychain->create(passwordLength, password);
+ secdebug("KCLogin", "Login keychain created successfully");
+ loginKeychainExists = true;
+ // Set the prefs for this new login keychain.
+ loginKeychain(theKeychain);
+ // Login Keychain does not lock on sleep nor lock after timeout by default.
+ theKeychain->setSettings(INT_MAX, false);
+ loginUnlocked = true;
+ mSavedList.revert(true);
+ }
+
+ //***************************************************************
+ // make plist changes after files have been renamed or created
+ //***************************************************************
+
+ // if the shortname keychain exists in the search list, either rename or remove the entry
+ if (mSavedList.member(shortnameDLDbIdentifier)) {
+ if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
+ // change shortname to shortname.keychain (login.keychain will be added later if not present)
+ secdebug("KCLogin", "Renaming %s to %s in keychain search list",
+ (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
+ (shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : "<NULL>");
+ mSavedList.rename(shortnameDLDbIdentifier, shortnameDotDLDbIdentifier);
+ } else if (!mSavedList.member(loginDLDbIdentifier)) {
+ // change shortname to login.keychain
+ secdebug("KCLogin", "Renaming %s to %s in keychain search list",
+ (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
+ (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ mSavedList.rename(shortnameDLDbIdentifier, loginDLDbIdentifier);
+ } else {
+ // already have login.keychain in list, and renaming to shortname.keychain isn't an option,
+ // so just remove the entry
+ secdebug("KCLogin", "Removing %s from keychain search list", (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>");
+ mSavedList.remove(shortnameDLDbIdentifier);
+ }
+
+ // note: save() will cause the plist to be unlinked if the only remaining entry is for login.keychain
+ mSavedList.save();
+ mSavedList.revert(true);
+ }
+
+ // make sure that login.keychain is in the search list
+ if (!mSavedList.member(loginDLDbIdentifier)) {
+ secdebug("KCLogin", "Adding %s to keychain search list", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ mSavedList.add(loginDLDbIdentifier);
+ mSavedList.save();
+ mSavedList.revert(true);
+ }
+
+ // if we have a shortname.keychain, always include it in the plist (after login.keychain)
+ if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
+ mSavedList.add(shortnameDotDLDbIdentifier);
+ mSavedList.save();
+ mSavedList.revert(true);
+ }
+
+ // make sure that the default keychain is in the search list; if not, reset the default to login.keychain
+ if (!mSavedList.member(mSavedList.defaultDLDbIdentifier())) {
+ secdebug("KCLogin", "Changing default keychain to %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ mSavedList.defaultDLDbIdentifier(loginDLDbIdentifier);
+ mSavedList.save();
+ mSavedList.revert(true);
+ }
+
+ //***************************************************************
+ // auto-unlock the login keychain(s)
+ //***************************************************************
+ // all our preflight fixups are finally done, so we can now attempt to unlock the login keychain
+
+ OSStatus loginResult = errSecSuccess;
+ if (!loginUnlocked) {
+ try
+ {
+ Keychain theKeychain(keychain(loginDLDbIdentifier));
+ secdebug("KCLogin", "Attempting to unlock login keychain \"%s\" with %d-character password",
+ (theKeychain) ? theKeychain->name() : "<NULL>", (unsigned int)passwordLength);
+ theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
+ loginUnlocked = true;
+ }
+ catch(const CssmError &e)
+ {
+ loginResult = e.osStatus(); // save this result
+ }
+ }
+
+ if (!loginUnlocked) {
+ try {
+ loginResult = errSecSuccess;
+ Keychain theKeychain(keychain(loginDLDbIdentifier));
+
+ // build a fake key
+ CssmKey key;
+ key.header().BlobType = CSSM_KEYBLOB_RAW;
+ key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
+ key.header().AlgorithmId = CSSM_ALGID_3DES_3KEY;
+ key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
+ key.header().KeyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT | CSSM_KEYATTR_EXTRACTABLE;
+ key.header().KeyAttr = 0;
+ key.KeyData = CssmData(const_cast<void *>(password), passwordLength);
+
+ // unwrap it into the CSP (but keep it raw)
+ UnwrapKey unwrap(theKeychain->csp(), CSSM_ALGID_NONE);
+ CssmKey masterKey;
+ CssmData descriptiveData;
+ unwrap(key,
+ KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_EXTRACTABLE),
+ masterKey, &descriptiveData, NULL);
+
+ CssmClient::Db db = theKeychain->database();
+
+ // create the keychain, using appropriate credentials
+ Allocator &alloc = db->allocator();
+ AutoCredentials cred(alloc); // will leak, but we're quitting soon :-)
+
+ // use this passphrase
+ cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
+ new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
+ new(alloc) ListElement(CssmData::wrap(theKeychain->csp()->handle())),
+ new(alloc) ListElement(CssmData::wrap(masterKey)),
+ new(alloc) ListElement(CssmData()));
+ db->authenticate(CSSM_DB_ACCESS_READ, &cred);
+ db->unlock();
+ loginUnlocked = true;
+ } catch (const CssmError &e) {
+ loginResult = e.osStatus();
+ }
+ }
+
+ // if "shortname.keychain" exists and is in the search list, attempt to auto-unlock it with the same password
+ if (shortnameDotKeychainExists && mSavedList.member(shortnameDotDLDbIdentifier)) {
+ try
+ {
+ Keychain shortnameDotKC(keychain(shortnameDotDLDbIdentifier));
+ secdebug("KCLogin", "Attempting to unlock %s",
+ (shortnameDotKC) ? shortnameDotKC->name() : "<NULL>");
+ shortnameDotKC->unlock(CssmData(const_cast<void *>(password), passwordLength));
+ }
+ catch(const CssmError &e)
+ {
+ // ignore; failure to unlock this keychain is not considered an error
+ }
+ }
+
+ if (loginResult != errSecSuccess) {
+ MacOSError::throwMe(loginResult);
+ }
+}
+
+void StorageManager::stashLogin()
+{
+ OSStatus loginResult = errSecSuccess;
+
+ DLDbIdentifier loginDLDbIdentifier;
+ {
+ mSavedList.revert(true);
+ loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
+ }
+
+ secdebug("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ if (!loginDLDbIdentifier)
+ MacOSError::throwMe(errSecNoSuchKeychain);
+
+ try
+ {
+ CssmData empty;
+ Keychain theKeychain(keychain(loginDLDbIdentifier));
+ secdebug("KCLogin", "Attempting to use stash for login keychain \"%s\"",
+ (theKeychain) ? theKeychain->name() : "<NULL>");
+ theKeychain->stashCheck();
+ }
+ catch(const CssmError &e)
+ {
+ loginResult = e.osStatus(); // save this result
+ }
+
+
+ if (loginResult != errSecSuccess) {
+ MacOSError::throwMe(loginResult);
+ }
+}
+
+void StorageManager::stashKeychain()
+{
+ OSStatus loginResult = errSecSuccess;
+
+ DLDbIdentifier loginDLDbIdentifier;
+ {
+ mSavedList.revert(true);
+ loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
+ }
+
+ secdebug("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+ if (!loginDLDbIdentifier)
+ MacOSError::throwMe(errSecNoSuchKeychain);
+
+ try
+ {
+ Keychain theKeychain(keychain(loginDLDbIdentifier));
+ secdebug("KCLogin", "Attempting to stash login keychain \"%s\"",
+ (theKeychain) ? theKeychain->name() : "<NULL>");
+ theKeychain->stash();
+ }
+ catch(const CssmError &e)
+ {
+ loginResult = e.osStatus(); // save this result
+ }
+
+
+ if (loginResult != errSecSuccess) {
+ MacOSError::throwMe(loginResult);
+ }
+}
+
+void StorageManager::logout()
+{
+ // nothing left to do here
+}
+
+void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
+{
+ StLock<Mutex>_(mMutex);
+
+ loginKeychain()->changePassphrase(oldPassword, newPassword);
+ secdebug("KClogin", "Changed login keychain password successfully");
+}
+
+
+void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword)
+{
+ StLock<Mutex>_(mMutex);
+
+ loginKeychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, newPassword);
+ secdebug("KClogin", "Changed login keychain password successfully");
+}
+
+// Clear out the keychain search list and rename the existing login.keychain.
+//
+void StorageManager::resetKeychain(Boolean resetSearchList)
+{
+ StLock<Mutex>_(mMutex);
+
+ // Clear the keychain search list.
+ try
+ {
+ if ( resetSearchList )
+ {
+ StorageManager::KeychainList keychainList;
+ setSearchList(keychainList);
+ }
+ // Get a reference to the existing login keychain...
+ // If we don't have one, we throw (not requiring a rename).
+ //
+ Keychain keychain = loginKeychain();
+ //
+ // Rename the existing login.keychain (i.e. put it aside).
+ //
+ CFMutableStringRef newName = NULL;
+ newName = CFStringCreateMutable(NULL, 0);
+ CFStringRef currName = NULL;
+ currName = CFStringCreateWithCString(NULL, keychain->name(), kCFStringEncodingUTF8);
+ if ( newName && currName )
+ {
+ CFStringAppend(newName, currName);
+ CFStringRef kcSuffix = CFSTR(kKeychainSuffix);
+ if ( CFStringHasSuffix(newName, kcSuffix) ) // remove the .keychain extension
+ {
+ CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
+ CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
+ }
+ CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix)); // add "_renamed_"
+ try
+ {
+ renameUnique(keychain, newName);
+ }
+ catch(...)
+ {
+ // we need to release 'newName' & 'currName'
+ }
+ } // else, let the login call report a duplicate
+ if ( newName )
+ CFRelease(newName);
+ if ( currName )
+ CFRelease(currName);
+ }
+ catch(...)
+ {
+ // We either don't have a login keychain, or there was a
+ // failure to rename the existing one.
+ }
+}
+
+#pragma mark ____ File Related ____
+
+Keychain StorageManager::make(const char *pathName)
+{
+ return make(pathName, true);
+}
+
+Keychain StorageManager::make(const char *pathName, bool add)
+{
+ StLock<Mutex>_(mMutex);
+
+ string fullPathName;
+ if ( pathName[0] == '/' )
+ fullPathName = pathName;
+ else
+ {
+ // Get Home directory from environment.
+ switch (mDomain)
+ {
+ case kSecPreferencesDomainUser:
+ {
+ const char *homeDir = getenv("HOME");
+ if (homeDir == NULL)
+ {
+ // If $HOME is unset get the current user's home directory
+ // from the passwd file.
+ uid_t uid = geteuid();
+ if (!uid) uid = getuid();
+ struct passwd *pw = getpwuid(uid);
+ if (!pw)
+ MacOSError::throwMe(errSecParam);
+ homeDir = pw->pw_dir;
+ }
+ fullPathName = homeDir;
+ }
+ break;
+ case kSecPreferencesDomainSystem:
+ fullPathName = "";
+ break;
+ default:
+ assert(false); // invalid domain for this
+ }
+
+ fullPathName += "/Library/Keychains/";
+ fullPathName += pathName;
+ }
+
+ const CSSM_NET_ADDRESS *DbLocation = NULL; // NULL for keychains
+ const CSSM_VERSION *version = NULL;
+ uint32 subserviceId = 0;
+ CSSM_SERVICE_TYPE subserviceType = CSSM_SERVICE_DL | CSSM_SERVICE_CSP;
+ const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version,
+ subserviceId, subserviceType);
+ DLDbIdentifier dLDbIdentifier(ssuid, fullPathName.c_str(), DbLocation);
+ return makeKeychain(dLDbIdentifier, add);
+}
+
+Keychain StorageManager::makeLoginAuthUI(const Item *item)
+{
+ StLock<Mutex>_(mMutex);
+
+ // Create a login/default keychain for the user using UI.
+ // The user can cancel out of the operation, or create a new login keychain.
+ // If auto-login is turned off, the user will be asked for their login password.
+ //
+ OSStatus result = errSecSuccess;
+ Keychain keychain; // We return this keychain.
+ //
+ // Set up the Auth ref to bring up UI.
+ //
+ AuthorizationItem *currItem, *authEnvirItemArrayPtr = NULL;
+ AuthorizationRef authRef = NULL;
+ try
+ {
+ result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef);
+ if ( result )
+ MacOSError::throwMe(result);
+
+ AuthorizationEnvironment envir;
+ envir.count = 6; // up to 6 hints can be used.
+ authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count);
+ if ( !authEnvirItemArrayPtr )
+ MacOSError::throwMe(errAuthorizationInternal);
+
+ currItem = envir.items = authEnvirItemArrayPtr;
+
+ //
+ // 1st Hint (optional): The keychain item's account attribute string.
+ // When item is specified, we assume an 'add' operation is being attempted.
+ char buff[256];
+ UInt32 actLen = 0;
+ SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff };
+ if ( item )
+ {
+ try
+ {
+ (*item)->getAttribute(attr, &actLen);
+ }
+ catch(...)
+ {
+ actLen = 0; // This item didn't have the account attribute, so don't display one in the UI.
+ }
+ }
+ currItem->name = AGENT_HINT_ATTR_NAME; // name str that identifies this hint as attr name
+ if ( actLen ) // Fill in the hint if we have an account attr
+ {
+ if ( actLen >= sizeof(buff) )
+ buff[sizeof(buff)-1] = 0;
+ else
+ buff[actLen] = 0;
+ currItem->valueLength = strlen(buff)+1;
+ currItem->value = buff;
+ }
+ else
+ {
+ currItem->valueLength = 0;
+ currItem->value = NULL;
+ }
+ currItem->flags = 0;
+
+ //
+ // 2nd Hint (optional): The item's keychain full path.
+ //
+ currItem++;
+ char* currDefaultName = NULL;
+ try
+ {
+ currDefaultName = (char*)defaultKeychain()->name(); // Use the name if we have it.
+ currItem->name = AGENT_HINT_LOGIN_KC_NAME; // Name str that identifies this hint as kc path
+ currItem->valueLength = (currDefaultName) ? strlen(currDefaultName) : 0;
+ currItem->value = (currDefaultName) ? (void*)currDefaultName : (void*)"";
+ currItem->flags = 0;
+ currItem++;
+ }
+ catch(...)
+ {
+ envir.count--;
+ }
+
+ //
+ // 3rd Hint (required): check if curr default keychain is unavailable.
+ // This is determined by the parent not existing.
+ //
+ currItem->name = AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER;
+ Boolean loginUnavail = false;
+ try
+ {
+ Keychain defaultKC = defaultKeychain();
+ if ( !defaultKC->exists() )
+ loginUnavail = true;
+ }
+ catch(...) // login.keychain not present
+ {
+ }
+ currItem->valueLength = sizeof(Boolean);
+ currItem->value = (void*)&loginUnavail;
+ currItem->flags = 0;
+
+ //
+ // 4th Hint (required): userName
+ //
+ currItem++;
+ currItem->name = AGENT_HINT_LOGIN_KC_USER_NAME;
+ char* uName = getenv("USER");
+ string userName = uName ? uName : "";
+ if ( userName.length() == 0 )
+ {
+ uid_t uid = geteuid();
+ if (!uid) uid = getuid();
+ struct passwd *pw = getpwuid(uid); // fallback case...
+ if (pw)
+ userName = pw->pw_name;
+ endpwent();
+ }
+ if ( userName.length() == 0 ) // did we ultimately get one?
+ MacOSError::throwMe(errAuthorizationInternal);
+
+ currItem->value = (void*)userName.c_str();
+ currItem->valueLength = userName.length();
+ currItem->flags = 0;
+
+ //
+ // 5th Hint (required): flags if user has more than 1 keychain (used for a later warning when reset to default).
+ //
+ currItem++;
+ currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR;
+ Boolean moreThanOneKCExists = false;
+ {
+ // if item is NULL, then this is a user-initiated full reset
+ if (item && mSavedList.searchList().size() > 1)
+ moreThanOneKCExists = true;
+ }
+ currItem->value = &moreThanOneKCExists;
+ currItem->valueLength = sizeof(Boolean);
+ currItem->flags = 0;
+
+ //
+ // 6th Hint (required): If no item is involved, this is a user-initiated full reset.
+ // We want to suppress the "do you want to reset to defaults?" panel in this case.
+ //
+ currItem++;
+ currItem->name = AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL;
+ Boolean suppressResetPanel = (item == NULL) ? TRUE : FALSE;
+ currItem->valueLength = sizeof(Boolean);
+ currItem->value = (void*)&suppressResetPanel;
+ currItem->flags = 0;
+
+ //
+ // Set up the auth rights and make the auth call.
+ //
+ AuthorizationItem authItem = { LOGIN_KC_CREATION_RIGHT, 0 , NULL, 0 };
+ AuthorizationRights rights = { 1, &authItem };
+ AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
+ result = AuthorizationCopyRights(authRef, &rights, &envir, flags, NULL);
+ if ( result )
+ MacOSError::throwMe(result);
+ try
+ {
+ resetKeychain(true); // Clears the plist, moves aside existing login.keychain
+ }
+ catch (...) // can throw if no existing login.keychain is found
+ {
+ }
+ login(authRef, (UInt32)userName.length(), userName.c_str()); // Create login.keychain
+ keychain = loginKeychain(); // Get newly-created login keychain
+ defaultKeychain(keychain); // Set it to be the default
+
+ free(authEnvirItemArrayPtr);
+ AuthorizationFree(authRef, kAuthorizationFlagDefaults);
+ }
+
+ catch (...)
+ {
+ // clean up allocations, then rethrow error
+ if ( authEnvirItemArrayPtr )
+ free(authEnvirItemArrayPtr);
+ if ( authRef )
+ AuthorizationFree(authRef, kAuthorizationFlagDefaults);
+ throw;
+ }
+
+ return keychain;
+}
+
+Keychain StorageManager::defaultKeychainUI(Item &item)
+{
+ StLock<Mutex>_(mMutex);
+
+ Keychain returnedKeychain;
+ try
+ {
+ returnedKeychain = defaultKeychain(); // If we have one, return it.
+ if ( returnedKeychain->exists() )
+ return returnedKeychain;
+ }
+ catch(...) // We could have one, but it isn't available (i.e. on a un-mounted volume).
+ {
+ }
+ if ( globals().getUserInteractionAllowed() )
+ {
+ returnedKeychain = makeLoginAuthUI(&item); // If no Keychains is present, one will be created.
+ if ( !returnedKeychain )
+ MacOSError::throwMe(errSecInvalidKeychain); // Something went wrong...
+ }
+ else
+ MacOSError::throwMe(errSecInteractionNotAllowed); // If UI isn't allowed, return an error.
+
+ return returnedKeychain;
+}
+
+void
+StorageManager::addToDomainList(SecPreferencesDomain domain,
+ const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ // make the identifier
+ CSSM_VERSION version = {0, 0};
+ DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
+ subServiceType, dbName, NULL);
+
+ if (domain == mDomain)
+ {
+ // manipulate the user's list
+ {
+ mSavedList.revert(true);
+ mSavedList.add(id);
+ mSavedList.save();
+ }
+
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+ else
+ {
+ // manipulate the other list
+ DLDbListCFPref(domain).add(id);
+ }
+}
+
+void
+StorageManager::isInDomainList(SecPreferencesDomain domain,
+ const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ CSSM_VERSION version = {0, 0};
+ DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
+ subServiceType, dbName, NULL);
+
+ // determine the list to search
+ bool result;
+ if (domain == mDomain)
+ {
+ result = mSavedList.member(id);
+ }
+ else
+ {
+ result = DLDbListCFPref(domain).member(id);
+ }
+
+ // do the search
+ if (!result)
+ {
+ MacOSError::throwMe(errSecNoSuchKeychain);
+ }
+}
+
+void
+StorageManager::removeFromDomainList(SecPreferencesDomain domain,
+ const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (domain == kSecPreferencesDomainDynamic)
+ MacOSError::throwMe(errSecInvalidPrefsDomain);
+
+ // make the identifier
+ CSSM_VERSION version = {0, 0};
+ DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
+ subServiceType, dbName, NULL);
+
+ if (domain == mDomain)
+ {
+ // manipulate the user's list
+ {
+ mSavedList.revert(true);
+ mSavedList.remove(id);
+ mSavedList.save();
+ }
+
+ KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+ }
+ else
+ {
+ // manipulate the other list
+ DLDbListCFPref(domain).remove(id);
+ }
+}
+
+bool
+StorageManager::keychainOwnerPermissionsValidForDomain(const char* path, SecPreferencesDomain domain)
+{
+ struct stat sb;
+ mode_t perms;
+ const char* sysPrefDir = "/Library/Preferences";
+ const char* errMsg = "Will not set default";
+ char* mustOwnDir = NULL;
+ struct passwd* pw = NULL;
+
+ // get my uid
+ uid_t uid = geteuid();
+ if (!uid) uid = getuid();
+
+ // our (e)uid must own the appropriate preferences or home directory
+ // for the specified preference domain whose default we will be modifying
+ switch (domain) {
+ case kSecPreferencesDomainUser:
+ mustOwnDir = getenv("HOME");
+ if (mustOwnDir == NULL) {
+ pw = getpwuid(uid);
+ if (!pw) return false;
+ mustOwnDir = pw->pw_dir;
+ }
+ break;
+ case kSecPreferencesDomainSystem:
+ mustOwnDir = (char*)sysPrefDir;
+ break;
+ case kSecPreferencesDomainCommon:
+ mustOwnDir = (char*)sysPrefDir;
+ break;
+ default:
+ return false;
+ }
+
+ if (mustOwnDir != NULL) {
+ struct stat dsb;
+ if ( (stat(mustOwnDir, &dsb) != 0) || (dsb.st_uid != uid) ) {
+ fprintf(stderr, "%s: UID=%d does not own directory %s\n", errMsg, (int)uid, mustOwnDir);
+ mustOwnDir = NULL; // will return below after calling endpwent()
+ }
+ }
+
+ if (pw != NULL)
+ endpwent();
+
+ if (mustOwnDir == NULL)
+ return false;
+
+ // check that file actually exists
+ if (stat(path, &sb) != 0) {
+ fprintf(stderr, "%s: file %s does not exist\n", errMsg, path);
+ return false;
+ }
+
+ // check flags
+ if (sb.st_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) {
+ fprintf(stderr, "%s: file %s is immutable\n", errMsg, path);
+ return false;
+ }
+
+ // check ownership
+ if (sb.st_uid != uid) {
+ fprintf(stderr, "%s: file %s is owned by UID=%d, but we have UID=%d\n",
+ errMsg, path, (int)sb.st_uid, (int)uid);
+ return false;
+ }
+
+ // check mode
+ perms = sb.st_mode;
+ perms |= 0600; // must have owner read/write permission set
+ if (sb.st_mode != perms) {
+ fprintf(stderr, "%s: file %s does not have the expected permissions\n", errMsg, path);
+ return false;
+ }
+
+ // user owns file and can read/write it
+ return true;
+}