/* * Copyright (c) 2000-2004 Apple Computer, 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 //%%% 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; // normal debug calls, which get stubbed out for deployment builds #define x_debug(str) secdebug("KClogin",(str)) #define x_debug1(fmt,arg1) secdebug("KClogin",(fmt),(arg1)) #define x_debug2(fmt,arg1,arg2) secdebug("KClogin",(fmt),(arg1),(arg2)) //----------------------------------------------------------------------------------- StorageManager::StorageManager() : mSavedList(kSecPreferencesDomainUser), mCommonList(kSecPreferencesDomainCommon), mDomain(kSecPreferencesDomainUser) { // get session attributes SessionAttributeBits sessionAttrs; if (OSStatus err = SessionGetInfo(callerSecuritySession, NULL, &sessionAttrs)) CssmError::throwMe(err); // If this is the root session, switch to 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", "switching to system preferences"); mDomain = kSecPreferencesDomainSystem; mSavedList.set(kSecPreferencesDomainSystem); } } Keychain StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier) { StLock stAPILock(globals().apiLock); if (!dLDbIdentifier) return Keychain(); KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); if (it != mKeychains.end()) return it->second; // 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 stAPILock(globals().apiLock); // If this keychain isn't in the map anymore we're done if (!keychainImpl->inCache()) return; KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); assert(it != mKeychains.end() && it->second == keychainImpl); if (it != mKeychains.end() && it->second == keychainImpl) mKeychains.erase(it); keychainImpl->inCache(false); } void StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier) { // Lock the recursive mutex StLock stAPILock(globals().apiLock); KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); if (it != mKeychains.end()) { KeychainImpl *keychainImpl = it->second; assert(keychainImpl->inCache()); mKeychains.erase(it); keychainImpl->inCache(false); } } // Create keychain if it doesn't exist, add it to the search list if add is // true, it exists and it is not already on it. Keychain StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add) { bool post = false; Keychain theKeychain = keychain(dLDbIdentifier); if (add) { 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) { DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier(); bool defaultChanged = false; { 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) { KeychainList searchList; getSearchList(searchList); return KCCursor(searchList, itemClass, attrList); } KCCursor StorageManager::createCursor(const SecKeychainAttributeList *attrList) { KeychainList searchList; getSearchList(searchList); return KCCursor(searchList, attrList); } void StorageManager::lockAll() { SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard()); ss.lockAll (false); } Keychain StorageManager::defaultKeychain() { Keychain theKeychain; { mSavedList.revert(false); DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier()); if (defaultDLDbIdentifier) { theKeychain = keychain(defaultDLDbIdentifier); } } if (theKeychain /* && theKeychain->exists() */) return theKeychain; MacOSError::throwMe(errSecNoDefaultKeychain); } void StorageManager::defaultKeychain(const Keychain &keychain) { 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) { 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) { if (domain == kSecPreferencesDomainDynamic) MacOSError::throwMe(errSecInvalidPrefsDomain); if (domain == mDomain) defaultKeychain(keychain); else DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier()); } Keychain StorageManager::loginKeychain() { 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) { mSavedList.revert(true); mSavedList.loginDLDbIdentifier(keychain->dlDbIdentifier()); mSavedList.save(); } size_t StorageManager::size() { mSavedList.revert(false); mCommonList.revert(false); return mSavedList.searchList().size() + mCommonList.searchList().size(); } Keychain StorageManager::at(unsigned int ix) { 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) { return at(ix); } void StorageManager::rename(Keychain keychain, const char* newName) { 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(); // Now update the Keychain cache StLock stAPILock(globals().apiLock); if (keychain->inCache()) { KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); assert(it != mKeychains.end() && it->second == keychain.get()); if (it != mKeychains.end() && it->second == keychain.get()) { // Remove the keychain from the cache under it's 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) { 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); 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); } void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb) { bool unsetDefault = false; { 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) { // 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(); } } // 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) { 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()); { // Only hold globals().apiLock during the cache lookups StLock stAPILock(globals().apiLock); for (DLDbList::const_iterator it = dLDbList.begin(); it != dLDbList.end(); ++it) { result.push_back(keychain(*it)); } for (DLDbList::const_iterator it = dynamicList.begin(); it != dynamicList.end(); ++it) { result.push_back(keychain(*it)); } for (DLDbList::const_iterator it = commonList.begin(); it != commonList.end(); ++it) { result.push_back(keychain(*it)); } } keychainList.swap(result); } void StorageManager::setSearchList(const KeychainList &keychainList) { 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) { 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::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList) { 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) { 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) { 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(paramErr); } } // static methods. void StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList) { assert(keychainArray); CFIndex count = CFArrayGetCount(keychainArray); 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) { KeychainList result; result.reserve(ids.size()); { StLock stAPILock(globals().apiLock); 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) { AuthorizationItemSet* info = NULL; OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); // get the results of the copy rights call. Boolean created = false; if ( result == noErr && 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, 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) { if ( name == NULL || password == NULL ) MacOSError::throwMe(paramErr); login(name[0], name + 1, password[0], password + 1); } void StorageManager::login(UInt32 nameLength, const void *name, UInt32 passwordLength, const void *password) { x_debug("StorageManager::login: entered"); if (passwordLength != 0 && password == NULL) { x_debug("StorageManager::login: invalid argument (NULL password)"); MacOSError::throwMe(paramErr); } DLDbIdentifier loginDLDbIdentifier; { mSavedList.revert(true); loginDLDbIdentifier = mSavedList.loginDLDbIdentifier(); } x_debug1("StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : ""); if (!loginDLDbIdentifier) MacOSError::throwMe(errSecNoSuchKeychain); Keychain theKeychain(keychain(loginDLDbIdentifier)); try { x_debug2("Attempting to unlock login keychain %s with %d-character password", (theKeychain) ? theKeychain->name() : "", (unsigned int)passwordLength); theKeychain->unlock(CssmData(const_cast(password), passwordLength)); x_debug("Login keychain unlocked successfully"); } catch(const CssmError &e) { if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) throw; x_debug1("Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : ""); theKeychain->create(passwordLength, password); x_debug("Login keychain created successfully"); // 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); } } void StorageManager::logout() { // nothing left to do here } void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword) { loginKeychain()->changePassphrase(oldPassword, newPassword); secdebug("KClogin", "Changed login keychain password successfully"); } void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword) { 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) { // 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) { 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(paramErr); 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) { // 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 = noErr; 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[255]; 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 > 255 ) buff[255] = 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, 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) { 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) { 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) { 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) { 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); } }