/* * 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; }