/* * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * 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 "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), mKeychains() { // 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); } } // Create KC if it doesn't exist Keychain StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier) { StLock _(mLock); return _keychain(dLDbIdentifier); } Keychain StorageManager::_keychain(const DLDbIdentifier &dLDbIdentifier) { 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)); return keychain; } // Called from KeychainImpl's destructor remove it from the map. void StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier, KeychainImpl *keychainImpl) { // @@@ Work out locking StLock _(mLock); KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); if (it != mKeychains.end() && it->second == keychainImpl) mKeychains.erase(it); } #if 0 // if a database is key-unlockable, authenticate it with any matching unlock keys found in the KC list void StorageManager::setDefaultCredentials(const Db &db) { try { CssmAutoData index(db->allocator()); if (!db->getUnlockKeyIndex(index.get())) return; // no suggested index (probably not a CSPDL) TrackingAllocator alloc(Allocator::standard()); KCCursor search(createCursor(CSSM_DL_DB_RECORD_SYMMETRIC_KEY, NULL)); CssmAutoData keyLabel(Allocator::standard()); keyLabel = StringData("SYSKC**"); keyLabel.append(index); static const CSSM_DB_ATTRIBUTE_INFO infoLabel = { CSSM_DB_ATTRIBUTE_NAME_AS_STRING, {"Label"}, CSSM_DB_ATTRIBUTE_FORMAT_BLOB }; search->add(CSSM_DB_EQUAL, infoLabel, keyLabel.get()); // could run a loop below to catch *all* eligible keys, // but that's stretching it; and beware CSP scope if you add this... AutoCredentials cred(alloc); Item keyItem; if (search->next(keyItem)) { CssmClient::Key key = dynamic_cast(*keyItem).key(); // create AccessCredentials from that key. Still allow interactive unlock const CssmKey &masterKey = key; CSSM_CSP_HANDLE cspHandle = key->csp()->handle(); cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_WORDID_SYMMETRIC_KEY), new(alloc) ListElement(CssmData::wrap(cspHandle)), new(alloc) ListElement(CssmData::wrap(masterKey))); cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT)); secdebug("storagemgr", "authenticating %s for default key credentials", db->name()); db->authenticate(db->accessRequest(), &cred); } } catch (...) { secdebug("storagemgr", "setDefaultCredentials for %s abandoned due to exception", db->name()); } } #endif // Create KC if it doesn't exist, add it to the search list if it exists and is not already on it. Keychain StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add) { Keychain keychain; bool post = false; { StLock _(mLock); keychain = _keychain(dLDbIdentifier); if (add) { mSavedList.revert(false); DLDbList searchList = mSavedList.searchList(); if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end()) return keychain; // Keychain is already in the searchList. mCommonList.revert(false); searchList = mCommonList.searchList(); if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end()) return keychain; // Keychain is already in the commonList don't add it to the searchList. // If the keychain doesn't exist don't bother adding it to the search list yet. if (!keychain->exists()) return keychain; // The keychain exists and is not in our search list add it to the search // list and the cache. mSavedList.revert(true); mSavedList.add(dLDbIdentifier); mSavedList.save(); post = true; } } if (post) { // Make sure we are not holding mLock when we post this event. KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent); } return keychain; } void StorageManager::created(const Keychain &keychain) // Be notified a Keychain just got created. { DLDbIdentifier dLDbIdentifier = keychain->dLDbIdentifier(); bool defaultChanged = false; { StLock _(mLock); 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; { StLock _(mLock); 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()); { StLock _(mLock); 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 == 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 == mDomain) defaultKeychain(keychain); else DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dLDbIdentifier()); } Keychain StorageManager::loginKeychain() { Keychain theKeychain; { StLock _(mLock); 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 _(mLock); mSavedList.revert(true); mSavedList.loginDLDbIdentifier(keychain->dLDbIdentifier()); mSavedList.save(); } size_t StorageManager::size() { StLock _(mLock); mSavedList.revert(false); mCommonList.revert(false); return mSavedList.searchList().size() + mCommonList.searchList().size(); } Keychain StorageManager::at(unsigned int ix) { StLock _(mLock); 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) { // This is not a generic purpose rename method for keychains. // The keychain doesn't remain in the cache. // bool changedDefault = false; DLDbIdentifier newDLDbIdentifier; { StLock _(mLock); mSavedList.revert(true); DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier(); // Find the keychain object for the given ref DLDbIdentifier dLDbIdentifier = keychain->dLDbIdentifier(); // Remove it from the saved list mSavedList.remove(dLDbIdentifier); if (dLDbIdentifier == defaultId) changedDefault=true; // Actually rename the database on disk. keychain->database()->rename(newName); newDLDbIdentifier = keychain->dLDbIdentifier(); // Now update the keychain map to use the newDLDbIdentifier KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); if (it != mKeychains.end()) { mKeychains.erase(it); mKeychains.insert(KeychainMap::value_type(newDLDbIdentifier, keychain)); } // If this was the default keychain change it accordingly if (changedDefault) mSavedList.defaultDLDbIdentifier(newDLDbIdentifier); mSavedList.save(); } // @@@ We need a kSecKeychainRenamedEvent so other clients can close this keychain and move on with life. //KCEventNotifier::PostKeychainEvent(kSecKeychainRenamedEvent); // 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; { StLock _(mLock); 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 keychainToRemove = *ix; DLDbIdentifier dLDbIdentifier = keychainToRemove->dLDbIdentifier(); // Remove it from the saved list mSavedList.remove(dLDbIdentifier); if (dLDbIdentifier == defaultId) unsetDefault=true; if (deleteDb) { keychainToRemove->database()->deleteDb(); // Now remove it from the map KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); if (it == mKeychains.end()) continue; mKeychains.erase(it); } } if (unsetDefault) mSavedList.defaultDLDbIdentifier(DLDbIdentifier()); mSavedList.save(); } // 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) { StLock _(mLock); mSavedList.revert(false); mCommonList.revert(false); // Merge mSavedList and common list DLDbList dLDbList = mSavedList.searchList(); DLDbList commonList = mCommonList.searchList(); KeychainList result; result.reserve(dLDbList.size() + commonList.size()); for (DLDbList::const_iterator it = dLDbList.begin(); it != dLDbList.end(); ++it) { Keychain keychain(_keychain(*it)); result.push_back(keychain); } for (DLDbList::const_iterator it = commonList.begin(); it != commonList.end(); ++it) { Keychain keychain(_keychain(*it)); result.push_back(keychain); } 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. StLock _(mLock); 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 == mDomain) { StLock _(mLock); mSavedList.revert(false); convertList(keychainList, mSavedList.searchList()); } else { convertList(keychainList, DLDbListCFPref(domain).searchList()); } } void StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList) { 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. StLock _(mLock); 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); } } else { DLDbListCFPref(domain).searchList(searchList); } } void StorageManager::domain(SecPreferencesDomain domain) { StLock _(mLock); 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()); for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix) { Keychain keychain(_keychain(*ix)); result.push_back(keychain); } 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"); mSavedList.revert(true); if (passwordLength != 0 && password == NULL) { x_debug("StorageManager::login: invalid argument (NULL password)"); MacOSError::throwMe(paramErr); } DLDbIdentifier 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. // CFArrayRef emptySearchList = nil; try { if ( resetSearchList ) { emptySearchList = CFArrayCreate(NULL, NULL, 0, NULL); StorageManager::KeychainList keychainList; convertToKeychainList(emptySearchList, 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. } if ( emptySearchList ) CFRelease(emptySearchList); } #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(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. // AuthorizationRef authRef = NULL; result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef); if ( result != noErr ) MacOSError::throwMe(errAuthorizationInternal); AuthorizationEnvironment envir; envir.count = 5; // 5 hints are used. AuthorizationItem* authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count); if ( !authEnvirItemArrayPtr ) { if ( authRef ) AuthorizationFree(authRef, kAuthorizationFlagDefaults); MacOSError::throwMe(errAuthorizationInternal); } envir.items = authEnvirItemArrayPtr; AuthorizationItem* currItem = 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; SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff }; 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 a 'srvr' 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*)globals().storageManager.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 = strlen(currDefaultName); currItem->value = (void*)currDefaultName; currItem->flags = 0; currItem++; } catch(...) { envir.count--; } // // 3rd Hint (optional): 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? { currItem->value = (void*)userName.c_str(); currItem->valueLength = userName.length(); } else // trouble getting user name; can't continue... { if ( authRef ) AuthorizationFree(authRef, kAuthorizationFlagDefaults); free(authEnvirItemArrayPtr); MacOSError::throwMe(errAuthorizationInternal); } currItem->flags = 0; // // 5th Hint (optional) flags if user has more than 1 keychain (used for a later warning when reset to default). // currItem++; // last hint... currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR; Boolean moreThanOneKCExists = false; { StLock _(mLock); if (mSavedList.searchList().size() > 1) moreThanOneKCExists = true; } currItem->value = &moreThanOneKCExists; currItem->valueLength = sizeof(Boolean); 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 }; result = AuthorizationCopyRights(authRef, &rights, &envir, kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, NULL); free(authEnvirItemArrayPtr); // done with the auth items. if ( result == errAuthorizationSuccess ) // On success, revert to defaults. { try { resetKeychain(true); // Clears the plist, moves aside existing login.keychain login(authRef, userName.length(), userName.c_str()); // Creates a login.keychain keychain = loginKeychain(); // Return it. defaultKeychain(keychain); // Set it to the default. } catch(...) { // Reset failed, login.keychain creation failed, or setting it to default. // We need to release 'authRef'... } } if ( authRef ) AuthorizationFree(authRef, kAuthorizationFlagDefaults); if ( result ) MacOSError::throwMe(result); // Any other error means we don't return a keychain. 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; }