X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/StorageManager.cpp?ds=inline diff --git a/Security/libsecurity_keychain/lib/StorageManager.cpp b/Security/libsecurity_keychain/lib/StorageManager.cpp new file mode 100644 index 00000000..f5d16aed --- /dev/null +++ b/Security/libsecurity_keychain/lib/StorageManager.cpp @@ -0,0 +1,1975 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include "TrustSettingsSchema.h" +#include + +//%%% 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_(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_(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_(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_(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_(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_(mMutex); + + KeychainList searchList; + getSearchList(searchList); + return KCCursor(searchList, itemClass, attrList); +} + +KCCursor +StorageManager::createCursor(const SecKeychainAttributeList *attrList) +{ + StLock_(mMutex); + + KeychainList searchList; + getSearchList(searchList); + return KCCursor(searchList, attrList); +} + +void +StorageManager::lockAll() +{ + StLock_(mMutex); + + SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard()); + ss.lockAll (false); +} + +Keychain +StorageManager::defaultKeychain() +{ + StLock_(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_(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_(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_(mMutex); + + if (domain == kSecPreferencesDomainDynamic) + MacOSError::throwMe(errSecInvalidPrefsDomain); + + if (domain == mDomain) + defaultKeychain(keychain); + else + DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier()); +} + +Keychain +StorageManager::loginKeychain() +{ + StLock_(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_(mMutex); + + mSavedList.revert(true); + mSavedList.loginDLDbIdentifier(keychain->dlDbIdentifier()); + mSavedList.save(); +} + +size_t +StorageManager::size() +{ + StLock_(mMutex); + + mSavedList.revert(false); + mCommonList.revert(false); + return mSavedList.searchList().size() + mCommonList.searchList().size(); +} + +Keychain +StorageManager::at(unsigned int ix) +{ + StLock_(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_(mMutex); + + return at(ix); +} + +void StorageManager::rename(Keychain keychain, const char* newName) +{ + + StLock_(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_(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_(mMutex); + + // make a CFString of our identifier + const char* idname = id.dbName (); + if (idname == NULL) + { + return; + } + + CFRef 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 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_(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_(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_(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_(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_(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_(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_(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 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_(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_(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_(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() : ""); + 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() : "", (unsigned int)passwordLength); + loginRenamed1KC->unlock(CssmData(const_cast(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() : ""); + 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() : "", + (shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : ""); + 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() : "", + (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : ""); + 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() : ""); + 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() : ""); + 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() : ""); + 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() : "", (unsigned int)passwordLength); + theKeychain->unlock(CssmData(const_cast(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(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() : ""); + shortnameDotKC->unlock(CssmData(const_cast(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() : ""); + 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() : ""); + 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() : ""); + if (!loginDLDbIdentifier) + MacOSError::throwMe(errSecNoSuchKeychain); + + try + { + Keychain theKeychain(keychain(loginDLDbIdentifier)); + secdebug("KCLogin", "Attempting to stash login keychain \"%s\"", + (theKeychain) ? theKeychain->name() : ""); + 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_(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_(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_(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_(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_(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_(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_(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_(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_(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; +}