X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/2965425374ca4413339436c2f706f7b5508402e2..9a27adb241486ab7597ffaea2b5613cd3d8e1f60:/Keychain/StorageManager.cpp?ds=sidebyside diff --git a/Keychain/StorageManager.cpp b/Keychain/StorageManager.cpp index ed3a7875..8a1a72df 100644 --- a/Keychain/StorageManager.cpp +++ b/Keychain/StorageManager.cpp @@ -28,29 +28,54 @@ #include #include +#include +#include #include #include #include #include -#include #include #include #include #include +#include +#include +#include #include "KCCursor.h" #include "Globals.h" -#include "DefaultKeychain.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(), - mKeychains(), - mSearchList() + mSavedList(kSecPreferencesDomainUser), + mCommonList(kSecPreferencesDomainCommon), + mDomain(kSecPreferencesDomainUser), + mKeychains() { - _doReload(); + // 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 @@ -64,6 +89,9 @@ StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier) Keychain StorageManager::_keychain(const DLDbIdentifier &dLDbIdentifier) { + if (!dLDbIdentifier) + return Keychain(); + KeychainMap::iterator it = mKeychains.find(dLDbIdentifier); if (it != mKeychains.end()) return it->second; @@ -79,43 +107,111 @@ StorageManager::_keychain(const DLDbIdentifier &dLDbIdentifier) dl->subserviceId(dLDbIdentifier.ssuid().subserviceId()); dl->version(dLDbIdentifier.ssuid().version()); Db db(dl, dLDbIdentifier.dbName()); - Keychain keychain(db); + Keychain keychain(db); // Add the keychain to the cache. - mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, keychain)); + 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 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(CssmAllocator::standard()); + + KCCursor search(createCursor(CSSM_DL_DB_RECORD_SYMMETRIC_KEY, NULL)); + CssmAutoData keyLabel(CssmAllocator::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()); + } +} + // 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) +StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add) { - Keychain keychain(keychain(dLDbIdentifier)); + Keychain keychain; + bool post = false; { StLock _(mLock); - if (find(mSearchList.begin(), mSearchList.end(), keychain) != mSearchList.end()) + keychain = _keychain(dLDbIdentifier); + + if (add) { - // This keychain is already on our search list. - return keychain; + 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 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. Then inform mMultiDLDb. - mSavedList.revert(true); - mSavedList.add(dLDbIdentifier); - mSavedList.save(); - - // @@@ Will happen again when kSecKeychainListChangedEvent notification is received. - _doReload(); } - // Make sure we are not holding mLock when we post this event. - KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent); + if (post) + { + // Make sure we are not holding mLock when we post this event. + KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent); + } return keychain; } @@ -124,108 +220,178 @@ void StorageManager::created(const Keychain &keychain) // Be notified a Keychain just got created. { DLDbIdentifier dLDbIdentifier = keychain->dLDbIdentifier(); + bool defaultChanged = false; { StLock _(mLock); - // If we don't have a default Keychain yet. Make the newly created keychain the default. - DefaultKeychain &defaultKeychain = globals().defaultKeychain; - if (!defaultKeychain.isSet()) - defaultKeychain.dLDbIdentifier(dLDbIdentifier); - - // Add the keychain to the search list and the cache. Then inform mMultiDLDb. 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(); - - // @@@ Will happen again when kSecKeychainListChangedEvent notification is received. - _doReload(); } - // Make sure we are not holding mLock when we post this event. + // 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 _(mLock); - return KCCursor(mSearchList, itemClass, attrList); + KeychainList searchList; + getSearchList(searchList); + return KCCursor(searchList, itemClass, attrList); } KCCursor StorageManager::createCursor(const SecKeychainAttributeList *attrList) { - StLock _(mLock); - return KCCursor(mSearchList, attrList); + KeychainList searchList; + getSearchList(searchList); + return KCCursor(searchList, attrList); } void StorageManager::lockAll() { - // Make a snapshot of all known keychains while holding mLock. - KeychainList keychainList; + SecurityServer::ClientSession ss(CssmAllocator::standard(), CssmAllocator::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); - for (KeychainMap::iterator ix = mKeychains.begin(); ix != mKeychains.end(); ix++) - keychainList.push_back(ix->second); + oldDefaultId = mSavedList.defaultDLDbIdentifier(); + mSavedList.revert(true); + mSavedList.defaultDLDbIdentifier(newDefaultId); + mSavedList.save(); } - // Lock each active keychain after having released mLock since locking keychains - // will send notifications. - for (KeychainList::iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix) + if (!(oldDefaultId == newDefaultId)) { - Keychain keychain = *ix; - if (keychain->isActive()) - keychain->lock(); + // Make sure we are not holding mLock when we post this event. + KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId); } } -void -StorageManager::_doReload() +Keychain +StorageManager::defaultKeychain(SecPreferencesDomain domain) { - KeychainList newList; - newList.reserve(mSavedList.size()); - for (CssmClient::DLDbList::iterator ix = mSavedList.begin(); ix != mSavedList.end(); ++ix) + if (domain == mDomain) + return defaultKeychain(); + else { - Keychain keychain(_keychain(*ix)); - newList.push_back(keychain); + DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier()); + if (defaultDLDbIdentifier) + return keychain(defaultDLDbIdentifier); + + MacOSError::throwMe(errSecNoDefaultKeychain); } - mSearchList.swap(newList); } void -StorageManager::reload(bool force) +StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain) { - StLock _(mLock); - _reload(force); + 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::_reload(bool force) +StorageManager::loginKeychain(Keychain keychain) { - // Reinitialize list from CFPrefs if changed. When force is true force a prefs revert now. - if (mSavedList.revert(force)) - _doReload(); + StLock _(mLock); + mSavedList.revert(true); + mSavedList.loginDLDbIdentifier(keychain->dLDbIdentifier()); + mSavedList.save(); } size_t StorageManager::size() { StLock _(mLock); - _reload(); - return mSearchList.size(); + mSavedList.revert(false); + mCommonList.revert(false); + return mSavedList.searchList().size() + mCommonList.searchList().size(); } Keychain StorageManager::at(unsigned int ix) { StLock _(mLock); - _reload(); - if (ix >= mSearchList.size()) - MacOSError::throwMe(errSecInvalidKeychain); + 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 mSearchList.at(ix); + return _keychain(commonList[ix]); + } } Keychain @@ -234,13 +400,105 @@ 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 = globals().defaultKeychain.dLDbIdentifier(); + DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier(); for (KeychainList::const_iterator ix = kcsToRemove.begin(); ix != kcsToRemove.end(); ++ix) { // Find the keychain object for the given ref @@ -262,41 +520,159 @@ void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb) mKeychains.erase(it); } } + + if (unsetDefault) + mSavedList.defaultDLDbIdentifier(DLDbIdentifier()); + mSavedList.save(); - _doReload(); } - // Make sure we are not holding mLock when we post this event. + // Make sure we are not holding mLock when we post these events. KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent); if (unsetDefault) - { - // Make sure we are not holding mLock when we call this since it posts an event. - globals().defaultKeychain.unset(); - } + KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent); } void StorageManager::getSearchList(KeychainList &keychainList) { - // Make a copy of the searchList StLock _(mLock); - StorageManager::KeychainList searchList(mSearchList); + 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); + } - // Return the copy of the list. - keychainList.swap(searchList); + keychainList.swap(result); } void StorageManager::setSearchList(const KeychainList &keychainList) { - // Make a copy of the passed in searchList - StorageManager::KeychainList keychains(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(); + } - // Set the current searchlist to be what was passed in, the old list will be freed - // upon exit of this stackframe. + 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); - mSearchList.swap(keychains); + 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 @@ -309,8 +685,8 @@ StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keyc CFTypeID typeID = CFGetTypeID(keychainOrArray); if (typeID == CFArrayGetTypeID()) convertToKeychainList(CFArrayRef(keychainOrArray), keychainList); - else if (typeID == gTypes().keychain.typeId) - keychainList.push_back(gTypes().keychain.required(SecKeychainRef(keychainOrArray))); + else if (typeID == gTypes().KeychainImpl.typeID) + keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray))); else MacOSError::throwMe(paramErr); } @@ -323,10 +699,9 @@ StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &ke assert(keychainArray); CFIndex count = CFArrayGetCount(keychainArray); KeychainList keychains(count); - CFClass &kcClass = gTypes().keychain; for (CFIndex ix = 0; ix < count; ++ix) { - keychains[ix] = kcClass.required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix))); + keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix))); } keychainList.swap(keychains); @@ -337,10 +712,9 @@ StorageManager::convertFromKeychainList(const KeychainList &keychainList) { CFRef keychainArray(CFArrayCreateMutable(NULL, keychainList.size(), &kCFTypeArrayCallBacks)); - CFClass &kcClass = gTypes().keychain; for (KeychainList::const_iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix) { - SecKeychainRef keychainRef = kcClass.handle(**ix); + SecKeychainRef keychainRef = (*ix)->handle(); CFArrayAppendValue(keychainArray, keychainRef); CFRelease(keychainRef); } @@ -350,10 +724,66 @@ StorageManager::convertFromKeychainList(const KeychainList &keychainList) 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 ) @@ -364,53 +794,38 @@ void StorageManager::login(ConstStringPtr name, ConstStringPtr password) void StorageManager::login(UInt32 nameLength, const void *name, UInt32 passwordLength, const void *password) { - // @@@ set up the login session on behalf of loginwindow - // @@@ (this code should migrate into loginwindow) -#if 0 - debug("KClogin", "setting up login session"); - if (OSStatus ssnErr = SessionCreate(sessionKeepCurrentBootstrap, - sessionHasGraphicAccess | sessionHasTTY)) - debug("KClogin", "session setup failed status=%ld", ssnErr); -#endif + x_debug("StorageManager::login: entered"); + mSavedList.revert(true); + if (passwordLength != 0 && password == NULL) + { + x_debug("StorageManager::login: invalid argument (NULL password)"); + MacOSError::throwMe(paramErr); + } - if (name == NULL || (passwordLength != 0 && password == NULL)) - MacOSError::throwMe(paramErr); + DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier()); + x_debug1("StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : ""); + if (!loginDLDbIdentifier) + MacOSError::throwMe(errSecNoSuchKeychain); - // Make sure name is zero terminated - string theName(reinterpret_cast(name), nameLength); - Keychain keychain = make(theName.c_str()); + Keychain theKeychain(keychain(loginDLDbIdentifier)); try { - keychain->unlock(CssmData(const_cast(password), passwordLength)); - debug("KClogin", "keychain unlock successful"); + 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; - debug("KClogin", "creating login keychain"); - keychain->create(passwordLength, password); + 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. - keychain->setSettings(INT_MAX, false); + theKeychain->setSettings(INT_MAX, false); } -#if 0 - // @@@ Create a authorization credential for the current user. - debug("KClogin", "creating login authorization"); - const AuthorizationItem envList[] = - { - { kAuthorizationEnvironmentUsername, nameLength, const_cast(name), 0 }, - { kAuthorizationEnvironmentPassword, passwordLength, const_cast(password), 0 }, - { kAuthorizationEnvironmentShared, 0, NULL, 0 } - }; - const AuthorizationEnvironment environment = - { - sizeof(envList) / sizeof(*envList), - const_cast(envList) - }; - if (OSStatus authErr = AuthorizationCreate(NULL, &environment, - kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize, NULL)) - debug("KClogin", "failed to create login auth, status=%ld", authErr); -#endif } void StorageManager::logout() @@ -420,18 +835,85 @@ void StorageManager::logout() void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword) { - globals().defaultKeychain.keychain()->changePassphrase(oldPassword, 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) { - globals().defaultKeychain.keychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, 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] == '/' ) @@ -439,18 +921,30 @@ Keychain StorageManager::make(const char *pathName) else { // Get Home directory from environment. - const char *homeDir = getenv("HOME"); - if (homeDir == NULL) - { - // If $HOME is unset get the current users home directory from the passwd file. - struct passwd *pw = getpwuid(getuid()); - if (!pw) - MacOSError::throwMe(paramErr); - - homeDir = pw->pw_dir; + 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 = homeDir; fullPathName += "/Library/Keychains/"; fullPathName += pathName; } @@ -459,20 +953,197 @@ Keychain StorageManager::make(const char *pathName) const CSSM_VERSION *version = NULL; uint32 subserviceId = 0; CSSM_SERVICE_TYPE subserviceType = CSSM_SERVICE_DL | CSSM_SERVICE_CSP; - const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version, + const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version, subserviceId, subserviceType); DLDbIdentifier dLDbIdentifier(ssuid, fullPathName.c_str(), DbLocation); - return makeKeychain(dLDbIdentifier); + return makeKeychain(dLDbIdentifier, add); } -KeychainSchema -StorageManager::keychainSchemaFor(const CssmClient::Db &db) +Keychain StorageManager::makeLoginAuthUI(Item &item) { - // @@@ Locking - KeychainSchema schema(db); - pair result = mKeychainSchemaSet.insert(db); - if (result.second) - return schema; - return *result.first; + // 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 = NULL; // 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 = NULL; + try + { + returnedKeychain = globals().storageManager.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; +}