StorageManager.cpp [plain text]
#include "StorageManager.h"
#include "KCEventNotifier.h"
#include <Security/cssmapple.h>
#include <sys/types.h>
#include <sys/param.h>
#include <syslog.h>
#include <pwd.h>
#include <algorithm>
#include <string>
#include <stdio.h>
#include <security_utilities/debugging.h>
#include <security_keychain/SecCFTypes.h>
#include <securityd_client/ssclient.h>
#include <Security/AuthorizationTags.h>
#include <Security/AuthorizationTagsPriv.h>
#include <Security/SecTask.h>
#include <security_keychain/SecCFTypes.h>
#include <Security/SecCFAllocator.h>
#include "TrustSettingsSchema.h"
#include <security_cdsa_client/wrapkey.h>
#include <securityd_client/ssblob.h>
#include <Security/SecBasePriv.h>
#include "TokenLogin.h"
#ifndef AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL
#define AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL "loginKCCreate:suppressResetPanel"
#endif
#include "KCCursor.h"
#include "Globals.h"
using namespace CssmClient;
using namespace KeychainCore;
#define kLoginKeychainPathPrefix "~/Library/Keychains/"
#define kUserLoginKeychainPath "~/Library/Keychains/login.keychain"
#define kEmptyKeychainSizeInBytes 20460
static SecPreferencesDomain defaultPreferenceDomain()
{
SessionAttributeBits sessionAttrs;
if (gServerMode) {
secnotice("servermode", "StorageManager initialized in server mode");
sessionAttrs = sessionIsRoot;
} else {
MacOSError::check(SessionGetInfo(callerSecuritySession, NULL, &sessionAttrs));
}
if ((sessionAttrs & sessionIsRoot)
IFDEBUG( && !(sessionAttrs & sessionHasGraphicAccess))) {
secnotice("storagemgr", "using system preferences");
return kSecPreferencesDomainSystem;
}
return kSecPreferencesDomainUser;
}
static bool isAppSandboxed()
{
bool result = false;
SecTaskRef task = SecTaskCreateFromSelf(NULL);
if(task != NULL) {
CFTypeRef appSandboxValue = SecTaskCopyValueForEntitlement(task,
CFSTR("com.apple.security.app-sandbox"), NULL);
if(appSandboxValue != NULL) {
result = true;
CFRelease(appSandboxValue);
}
CFRelease(task);
}
return result;
}
static bool shouldAddToSearchList(const DLDbIdentifier &dLDbIdentifier)
{
bool loginOrSystemKeychain = false;
const char *dbname = dLDbIdentifier.dbName();
if (dbname) {
if ((!strcmp(dbname, "/Library/Keychains/System.keychain")) ||
(strstr(dbname, "/login.keychain")) ) {
loginOrSystemKeychain = true;
}
}
return (loginOrSystemKeychain && !isAppSandboxed());
}
StorageManager::StorageManager() :
mSavedList(defaultPreferenceDomain()),
mCommonList(kSecPreferencesDomainCommon),
mDomain(kSecPreferencesDomainUser),
mMutex(Mutex::recursive)
{
}
Mutex*
StorageManager::getStorageManagerMutex()
{
return &mKeychainMapMutex;
}
Keychain
StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier)
{
StLock<Mutex>_(mKeychainMapMutex);
if (!dLDbIdentifier)
return Keychain();
KeychainMap::iterator it = mKeychainMap.end();
DLDbIdentifier munge_dldbi = forceMungeDLDbIDentifier(dLDbIdentifier);
it = mKeychainMap.find(munge_dldbi);
if (it != mKeychainMap.end()) {
return it->second;
}
DLDbIdentifier demunge_dldbi = demungeDLDbIdentifier(dLDbIdentifier);
it = mKeychainMap.find(demunge_dldbi);
if (it != mKeychainMap.end()) {
return it->second;
}
DLDbIdentifier dldbi = mungeDLDbIdentifier(dLDbIdentifier, false);
it = mKeychainMap.find(dldbi); if (it != mKeychainMap.end())
{
return it->second;
}
if (gServerMode) {
secnotice("servermode", "keychain reference in server mode");
return Keychain();
}
Db db(makeDb(dldbi));
Keychain keychain(db);
registerKeychain(keychain);
return keychain;
}
CssmClient::Db
StorageManager::makeDb(DLDbIdentifier dLDbIdentifier) {
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());
CssmClient::Db db(dl, dLDbIdentifier.dbName());
return db;
}
DLDbIdentifier
StorageManager::mungeDLDbIdentifier(const DLDbIdentifier& dLDbIdentifier, bool isReset) {
if(!dLDbIdentifier.dbName()) {
return dLDbIdentifier;
}
string path = dLDbIdentifier.dbName();
bool shouldCreateProtected = globals().integrityProtection();
if(dLDbIdentifier.mImpl == NULL) {
return DLDbIdentifier();
}
if(pathInHomeLibraryKeychains(path)) {
string pathdb = makeKeychainDbFilename(path);
struct stat st;
int path_stat_err = 0;
bool path_exists = (::stat(path.c_str(), &st) == 0);
if(!path_exists) {
path_stat_err = errno;
}
int pathdb_stat_err = 0;
bool pathdb_exists = (::stat(pathdb.c_str(), &st) == 0);
if(!pathdb_exists) {
pathdb_stat_err = errno;
}
bool switchPaths = shouldCreateProtected && (pathdb_exists || (!pathdb_exists && !path_exists) || isReset);
if(switchPaths) {
secinfo("integrity", "switching to keychain-db: %s from %s (%d %d %d_%d %d_%d)", pathdb.c_str(), path.c_str(), isReset, shouldCreateProtected, path_exists, path_stat_err, pathdb_exists, pathdb_stat_err);
path = pathdb;
} else {
secinfo("integrity", "not switching: %s from %s (%d %d %d_%d %d_%d)", pathdb.c_str(), path.c_str(), isReset, shouldCreateProtected, path_exists, path_stat_err, pathdb_exists, pathdb_stat_err);
}
}
DLDbIdentifier id(dLDbIdentifier.ssuid(), path.c_str(), dLDbIdentifier.dbLocation());
return id;
}
DLDbIdentifier
StorageManager::forceMungeDLDbIDentifier(const DLDbIdentifier& dLDbIdentifier) {
if(!dLDbIdentifier.dbName() || dLDbIdentifier.mImpl == NULL) {
return dLDbIdentifier;
}
string path = dLDbIdentifier.dbName();
string pathdb = makeKeychainDbFilename(path);
DLDbIdentifier id(dLDbIdentifier.ssuid(), pathdb.c_str(), dLDbIdentifier.dbLocation());
return id;
}
DLDbIdentifier
StorageManager::demungeDLDbIdentifier(const DLDbIdentifier& dLDbIdentifier) {
if(dLDbIdentifier.dbName() == NULL) {
return dLDbIdentifier;
}
string path = dLDbIdentifier.dbName();
string dbSuffix = "-db";
bool endsWithKeychainDb = (path.size() > dbSuffix.size() && (0 == path.compare(path.size() - dbSuffix.size(), dbSuffix.size(), dbSuffix)));
if(pathInHomeLibraryKeychains(path) && endsWithKeychainDb) {
path.erase(path.end() - 3, path.end());
}
DLDbIdentifier id(dLDbIdentifier.ssuid(), path.c_str(), dLDbIdentifier.dbLocation());
return id;
}
string
StorageManager::makeKeychainDbFilename(const string& filename) {
string keychainDbSuffix = "-db";
bool endsWithKeychainDb = (filename.size() > keychainDbSuffix.size() && (0 == filename.compare(filename.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix)));
if(endsWithKeychainDb) {
return filename;
} else {
return filename + keychainDbSuffix;
}
}
bool
StorageManager::pathInHomeLibraryKeychains(const string& path) {
return SecurityServer::CommonBlob::pathInHomeLibraryKeychains(path);
}
void
StorageManager::reloadKeychain(Keychain keychain) {
StLock<Mutex>_(mKeychainMapMutex);
DLDbIdentifier dLDbIdentifier = keychain->database()->dlDbIdentifier();
keychain->changeDatabase(makeDb(mungeDLDbIdentifier(dLDbIdentifier, false)));
registerKeychain(keychain);
}
void
StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier,
KeychainImpl *keychainImpl)
{
StLock<Mutex>_(mKeychainMapMutex);
forceRemoveFromCache(keychainImpl);
}
void
StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier)
{
StLock<Mutex>_(mKeychainMapMutex);
KeychainMap::iterator it = mKeychainMap.find(dLDbIdentifier);
if (it != mKeychainMap.end())
{
it->second->inCache(false);
mKeychainMap.erase(it);
}
}
void
StorageManager::tickleKeychain(KeychainImpl *keychainImpl) {
static dispatch_once_t onceToken = 0;
static dispatch_queue_t release_queue = NULL;
dispatch_once(&onceToken, ^{
release_queue = dispatch_queue_create("com.apple.security.keychain-cache-queue", DISPATCH_QUEUE_SERIAL);
});
__block KeychainImpl* kcImpl = keychainImpl;
if(!kcImpl) {
return;
}
if(kcImpl->dlDbIdentifier().ssuid().guid() != gGuidAppleCSPDL) {
return;
}
uint32_t seconds = 1;
const string path = kcImpl->name();
bool isSystemKeychain = (0 == path.compare("/Library/Keychains/System.keychain"));
if(pathInHomeLibraryKeychains(path) || isSystemKeychain) {
seconds = 5;
}
__block CFTypeRef kcHandle = kcImpl->handle();
__block bool releaseImmediately = false;
dispatch_sync(release_queue, ^() {
if(kcImpl->mCacheTimer) {
dispatch_source_set_timer(kcImpl->mCacheTimer, dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, NSEC_PER_SEC/2);
secdebug("keychain", "updating cache on %p %s", kcImpl, kcImpl->name());
releaseImmediately = true;
} else {
kcImpl->mCacheTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, release_queue);
dispatch_source_set_timer(kcImpl->mCacheTimer, dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, NSEC_PER_SEC/2);
secdebug("keychain", "taking cache on %p %s", kcImpl, kcImpl->name());
dispatch_source_set_event_handler(kcImpl->mCacheTimer, ^{
secdebug("keychain", "releasing cache on %p %s", kcImpl, kcImpl->name());
dispatch_source_cancel(kcImpl->mCacheTimer);
dispatch_release(kcImpl->mCacheTimer);
kcImpl->mCacheTimer = NULL;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^{
CFRelease(kcHandle);
});
});
dispatch_resume(kcImpl->mCacheTimer);
}
});
if(releaseImmediately) {
CFRelease(kcHandle);
}
}
Keychain
StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add, bool isReset)
{
StLock<Mutex>_(mKeychainMapMutex);
Keychain theKeychain = keychain(mungeDLDbIdentifier(dLDbIdentifier, isReset));
bool post = false;
bool updateList = (add && shouldAddToSearchList(dLDbIdentifier));
if (updateList)
{
mSavedList.revert(false);
DLDbList searchList = mSavedList.searchList();
if (find(searchList.begin(), searchList.end(), demungeDLDbIdentifier(dLDbIdentifier)) != searchList.end())
return theKeychain;
mCommonList.revert(false);
searchList = mCommonList.searchList();
if (find(searchList.begin(), searchList.end(), demungeDLDbIdentifier(dLDbIdentifier)) != searchList.end())
return theKeychain;
if (!theKeychain->exists())
return theKeychain;
mSavedList.revert(true);
mSavedList.add(demungeDLDbIdentifier(dLDbIdentifier));
mSavedList.save();
post = true;
}
if (post)
{
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
return theKeychain;
}
void
StorageManager::created(const Keychain &keychain)
{
StLock<Mutex>_(mKeychainMapMutex);
DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
bool defaultChanged = false;
bool updateList = shouldAddToSearchList(dLDbIdentifier);
if (updateList)
{
mSavedList.revert(true);
if (!mSavedList.defaultDLDbIdentifier())
{
mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(dLDbIdentifier));
defaultChanged = true;
}
mSavedList.add(demungeDLDbIdentifier(dLDbIdentifier));
mSavedList.save();
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
if (defaultChanged)
{
KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, dLDbIdentifier);
}
}
KCCursor
StorageManager::createCursor(SecItemClass itemClass,
const SecKeychainAttributeList *attrList)
{
StLock<Mutex>_(mMutex);
KeychainList searchList;
getSearchList(searchList);
return KCCursor(searchList, itemClass, attrList);
}
KCCursor
StorageManager::createCursor(const SecKeychainAttributeList *attrList)
{
StLock<Mutex>_(mMutex);
KeychainList searchList;
getSearchList(searchList);
return KCCursor(searchList, attrList);
}
void
StorageManager::lockAll()
{
StLock<Mutex>_(mMutex);
SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard());
ss.lockAll (false);
}
Keychain
StorageManager::defaultKeychain()
{
StLock<Mutex>_(mMutex);
Keychain theKeychain;
CFTypeRef ref;
{
mSavedList.revert(false);
DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier());
if (defaultDLDbIdentifier)
{
theKeychain = keychain(defaultDLDbIdentifier);
ref = theKeychain->handle(false);
}
}
if (theKeychain )
return theKeychain;
MacOSError::throwMe(errSecNoDefaultKeychain);
}
void
StorageManager::defaultKeychain(const Keychain &keychain)
{
StLock<Mutex>_(mMutex);
if (!keychainOwnerPermissionsValidForDomain(keychain->name(), mDomain))
MacOSError::throwMe(errSecWrPerm);
DLDbIdentifier oldDefaultId;
DLDbIdentifier newDefaultId(keychain->dlDbIdentifier());
{
oldDefaultId = mSavedList.defaultDLDbIdentifier();
mSavedList.revert(true);
mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(newDefaultId));
mSavedList.save();
}
if (!(oldDefaultId == newDefaultId))
{
KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId);
}
}
Keychain
StorageManager::defaultKeychain(SecPreferencesDomain domain)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
if (domain == mDomain)
return defaultKeychain();
else
{
DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier());
if (defaultDLDbIdentifier)
return keychain(defaultDLDbIdentifier);
MacOSError::throwMe(errSecNoDefaultKeychain);
}
}
void
StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
if (domain == mDomain)
defaultKeychain(keychain);
else
DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier());
}
Keychain
StorageManager::loginKeychain()
{
StLock<Mutex>_(mMutex);
Keychain theKeychain;
{
mSavedList.revert(false);
DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
if (loginDLDbIdentifier)
{
theKeychain = keychain(loginDLDbIdentifier);
}
}
if (theKeychain && theKeychain->exists())
return theKeychain;
MacOSError::throwMe(errSecNoSuchKeychain);
}
DLDbIdentifier
StorageManager::loginKeychainDLDbIdentifer()
{
StLock<Mutex>_(mMutex);
DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
return mungeDLDbIdentifier(loginDLDbIdentifier, false);
}
void
StorageManager::loginKeychain(Keychain keychain)
{
StLock<Mutex>_(mMutex);
mSavedList.revert(true);
mSavedList.loginDLDbIdentifier(demungeDLDbIdentifier(keychain->dlDbIdentifier()));
mSavedList.save();
}
size_t
StorageManager::size()
{
StLock<Mutex>_(mMutex);
mSavedList.revert(false);
mCommonList.revert(false);
return mSavedList.searchList().size() + mCommonList.searchList().size();
}
Keychain
StorageManager::at(unsigned int ix)
{
StLock<Mutex>_(mMutex);
mSavedList.revert(false);
DLDbList dLDbList = mSavedList.searchList();
if (ix < dLDbList.size())
{
return keychain(dLDbList[ix]);
}
else
{
ix -= dLDbList.size();
mCommonList.revert(false);
DLDbList commonList = mCommonList.searchList();
if (ix >= commonList.size())
MacOSError::throwMe(errSecInvalidKeychain);
return keychain(commonList[ix]);
}
}
Keychain
StorageManager::operator[](unsigned int ix)
{
StLock<Mutex>_(mMutex);
return at(ix);
}
void StorageManager::rename(Keychain keychain, const char* newName)
{
StLock<Mutex>_(mKeychainMapMutex);
bool changedDefault = false;
DLDbIdentifier newDLDbIdentifier;
{
mSavedList.revert(true);
DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
if(!keychain->database()->isLocked()) {
DLDbIdentifier dldbi(dLDbIdentifier.ssuid(), newName, dLDbIdentifier.dbLocation());
keychain->database()->transferTo(dldbi);
} else {
keychain->database()->rename(newName);
}
if (demungeDLDbIdentifier(dLDbIdentifier) == defaultId)
changedDefault=true;
newDLDbIdentifier = keychain->dlDbIdentifier();
mSavedList.rename(demungeDLDbIdentifier(dLDbIdentifier), demungeDLDbIdentifier(newDLDbIdentifier));
if (changedDefault)
mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(newDLDbIdentifier));
mSavedList.save();
if(keychain->inCache()) {
registerKeychain(keychain);
}
}
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
if (changedDefault)
KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent,
newDLDbIdentifier);
}
void StorageManager::registerKeychain(Keychain& kc) {
registerKeychainImpl(kc.get());
}
void StorageManager::registerKeychainImpl(KeychainImpl* kcimpl) {
if(!kcimpl) {
return;
}
{
StLock<Mutex> _(mKeychainMapMutex);
forceRemoveFromCache(kcimpl);
KeychainMap::iterator it = mKeychainMap.find(kcimpl->dlDbIdentifier());
if (it != mKeychainMap.end())
{
Keychain oldKeychain(it->second);
oldKeychain->inCache(false);
}
mKeychainMap.insert(KeychainMap::value_type(kcimpl->dlDbIdentifier(), kcimpl));
kcimpl->inCache(true);
} }
void StorageManager::forceRemoveFromCache(KeychainImpl* inKeychainImpl) {
try {
{
StLock<Mutex> _(mKeychainMapMutex);
for(KeychainMap::iterator it = mKeychainMap.begin(); it != mKeychainMap.end(); ) {
if(it->second == inKeychainImpl) {
it->second->inCache(false);
mKeychainMap.erase(it++);
} else {
it++;
}
}
} } catch(UnixError ue) {
secnotice("storagemgr", "caught UnixError: %d %s", ue.unixError(), ue.what());
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
secnotice("storagemgr", "caught CssmError: %d %s", (int) cssme.error, errStr);
} catch (MacOSError mose) {
secnotice("storagemgr", "MacOSError: %d", (int)mose.osStatus());
} catch(...) {
secnotice("storagemgr", "Unknown error");
}
}
void StorageManager::renameUnique(Keychain keychain, CFStringRef oldName, CFStringRef newName, bool appendDbSuffix)
{
StLock<Mutex>_(mMutex);
bool doneCreating = false;
int index = 1;
do
{
char newNameCString[MAXPATHLEN];
if ( CFStringGetCString(newName, newNameCString, MAXPATHLEN, kCFStringEncodingUTF8) ) {
CFMutableStringRef newNameCFStr = NULL;
newNameCFStr = CFStringCreateMutable(NULL, MAXPATHLEN);
if ( newNameCFStr )
{
CFStringAppendFormat(newNameCFStr, NULL, CFSTR("%s%d"), newNameCString, index);
if(appendDbSuffix) {
CFStringAppend(newNameCFStr, CFSTR(kKeychainDbSuffix));
} else {
CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix)); }
char toUseBuff2[MAXPATHLEN];
if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) {
struct stat filebuf;
if ( lstat(toUseBuff2, &filebuf) )
{
if(keychain) {
rename(keychain, toUseBuff2);
KeychainList kcList;
kcList.push_back(keychain);
remove(kcList, false);
} else {
char oldNameCString[MAXPATHLEN];
if ( CFStringGetCString(oldName, oldNameCString, MAXPATHLEN, kCFStringEncodingUTF8) ) {
int result = ::rename(oldNameCString, toUseBuff2);
secnotice("KClogin", "keychain force rename to %s: %d %d", newNameCString, result, (result == 0) ? 0 : errno);
if(result != 0) {
UnixError::throwMe(errno);
}
} else {
secnotice("KClogin", "path is wrong, quitting");
}
}
doneCreating = true;
}
else
index++;
}
else
doneCreating = true; CFRelease(newNameCFStr);
}
else
doneCreating = false; }
else
doneCreating = false; }
while (!doneCreating && index != INT_MAX);
}
#define KEYCHAIN_SYNC_KEY CFSTR("KeychainSyncList")
#define KEYCHAIN_SYNC_DOMAIN CFSTR("com.apple.keychainsync")
static CFStringRef MakeExpandedPath (const char* path)
{
std::string name = DLDbListCFPref::ExpandTildesInPath (std::string (path));
CFStringRef expanded = CFStringCreateWithCString (NULL, name.c_str (), 0);
return expanded;
}
void StorageManager::removeKeychainFromSyncList (const DLDbIdentifier &id)
{
StLock<Mutex>_(mMutex);
const char* idname = id.dbName ();
if (idname == NULL)
{
return;
}
CFRef<CFStringRef> idString = MakeExpandedPath (idname);
CFArrayRef value =
(CFArrayRef) CFPreferencesCopyValue (KEYCHAIN_SYNC_KEY,
KEYCHAIN_SYNC_DOMAIN,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
if (value == NULL)
{
return;
}
CFRef<CFMutableArrayRef> mtValue = CFArrayCreateMutableCopy (NULL, 0, value);
CFRelease (value);
CFIndex i;
CFIndex limit = CFArrayGetCount (mtValue.get());
bool found = false;
for (i = 0; i < limit; ++i)
{
CFDictionaryRef idx = (CFDictionaryRef) CFArrayGetValueAtIndex (mtValue.get(), i);
CFStringRef v = (CFStringRef) CFDictionaryGetValue (idx, CFSTR("DbName"));
if (v == NULL)
{
return; }
char* stringBuffer = NULL;
const char* pathString = CFStringGetCStringPtr(v, 0);
if (pathString == 0)
{
CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(v), kCFStringEncodingUTF8) + 1;
stringBuffer = (char*) malloc(maxLen);
CFStringGetCString(v, stringBuffer, maxLen, kCFStringEncodingUTF8);
pathString = stringBuffer;
}
CFStringRef vExpanded = MakeExpandedPath(pathString);
CFComparisonResult result = CFStringCompare (vExpanded, idString.get(), 0);
if (stringBuffer != NULL)
{
free(stringBuffer);
}
CFRelease (vExpanded);
if (result == 0)
{
CFArrayRemoveValueAtIndex (mtValue.get(), i);
found = true;
break;
}
}
if (found)
{
#ifndef NDEBUG
CFShow (mtValue.get());
#endif
CFPreferencesSetValue (KEYCHAIN_SYNC_KEY,
mtValue,
KEYCHAIN_SYNC_DOMAIN,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
CFPreferencesSynchronize (KEYCHAIN_SYNC_DOMAIN, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
}
}
void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb)
{
StLock<Mutex>_(mMutex);
bool unsetDefault = false;
bool updateList = (!isAppSandboxed());
if (updateList)
{
mSavedList.revert(true);
DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
for (KeychainList::const_iterator ix = kcsToRemove.begin();
ix != kcsToRemove.end(); ++ix)
{
Keychain theKeychain = *ix;
DLDbIdentifier dLDbIdentifier = theKeychain->dlDbIdentifier();
mSavedList.remove(demungeDLDbIdentifier(dLDbIdentifier));
if (demungeDLDbIdentifier(dLDbIdentifier) == defaultId) {
unsetDefault=true;
}
if (deleteDb)
{
removeKeychainFromSyncList (dLDbIdentifier);
removeKeychain(dLDbIdentifier, theKeychain.get());
}
}
if (unsetDefault)
mSavedList.defaultDLDbIdentifier(DLDbIdentifier());
mSavedList.save();
}
if (deleteDb)
{
for (KeychainList::const_iterator ix = kcsToRemove.begin();
ix != kcsToRemove.end(); ++ix)
{
(*ix)->database()->deleteDb();
}
}
if (updateList) {
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
if (unsetDefault)
KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent);
}
void
StorageManager::getSearchList(KeychainList &keychainList)
{
StLock<Mutex>_(mMutex);
if (gServerMode) {
keychainList.clear();
return;
}
mSavedList.revert(false);
mCommonList.revert(false);
DLDbList dLDbList = mSavedList.searchList();
DLDbList dynamicList = mDynamicList.searchList();
DLDbList commonList = mCommonList.searchList();
KeychainList result;
result.reserve(dLDbList.size() + dynamicList.size() + commonList.size());
{
for (DLDbList::const_iterator it = dynamicList.begin();
it != dynamicList.end(); ++it)
{
Keychain k = keychain(*it);
result.push_back(k);
}
for (DLDbList::const_iterator it = dLDbList.begin();
it != dLDbList.end(); ++it)
{
Keychain k = keychain(*it);
result.push_back(k);
}
for (DLDbList::const_iterator it = commonList.begin();
it != commonList.end(); ++it)
{
Keychain k = keychain(*it);
result.push_back(k);
}
}
keychainList.swap(result);
}
void
StorageManager::setSearchList(const KeychainList &keychainList)
{
StLock<Mutex>_(mMutex);
DLDbList searchList, oldSearchList(mSavedList.searchList());
for (KeychainList::const_iterator it = keychainList.begin(); it != keychainList.end(); ++it)
{
DLDbIdentifier dldbi = demungeDLDbIdentifier((*it)->dlDbIdentifier());
DLDbList commonList = mCommonList.searchList();
bool found = false;
for(DLDbList::const_iterator jt = commonList.begin(); jt != commonList.end(); ++jt) {
if((*jt) == dldbi) {
found = true;
}
}
DLDbList dynamicList = mDynamicList.searchList();
for(DLDbList::const_iterator jt = dynamicList.begin(); jt != dynamicList.end(); ++jt) {
if((*jt) == dldbi) {
found = true;
}
}
if(found) {
continue;
}
searchList.push_back(dldbi);
}
{
mSavedList.revert(true);
mSavedList.searchList(searchList);
mSavedList.save();
}
if (!(oldSearchList == searchList))
{
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
}
void
StorageManager::getSearchList(SecPreferencesDomain domain, KeychainList &keychainList)
{
StLock<Mutex>_(mMutex);
if (gServerMode) {
keychainList.clear();
return;
}
if (domain == kSecPreferencesDomainDynamic)
{
convertList(keychainList, mDynamicList.searchList());
}
else if (domain == mDomain)
{
mSavedList.revert(false);
convertList(keychainList, mSavedList.searchList());
}
else
{
convertList(keychainList, DLDbListCFPref(domain).searchList());
}
}
void StorageManager::forceUserSearchListReread()
{
mSavedList.forceUserSearchListReread();
}
void
StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
DLDbList searchList;
convertList(searchList, keychainList);
if (domain == mDomain)
{
DLDbList oldSearchList(mSavedList.searchList());
{
mSavedList.revert(true);
mSavedList.searchList(searchList);
mSavedList.save();
}
if (!(oldSearchList == searchList))
{
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
}
else
{
DLDbListCFPref(domain).searchList(searchList);
}
}
void
StorageManager::domain(SecPreferencesDomain domain)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
if (domain == mDomain)
return;
#if !defined(NDEBUG)
switch (domain)
{
case kSecPreferencesDomainSystem:
secnotice("storagemgr", "switching to system domain"); break;
case kSecPreferencesDomainUser:
secnotice("storagemgr", "switching to user domain (uid %d)", getuid()); break;
default:
secnotice("storagemgr", "switching to weird prefs domain %d", domain); break;
}
#endif
mDomain = domain;
mSavedList.set(domain);
}
void
StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
{
StLock<Mutex>_(mMutex);
if (!keychainOrArray)
getSearchList(keychainList);
else
{
CFTypeID typeID = CFGetTypeID(keychainOrArray);
if (typeID == CFArrayGetTypeID())
convertToKeychainList(CFArrayRef(keychainOrArray), keychainList);
else if (typeID == gTypes().KeychainImpl.typeID)
keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray)));
else
MacOSError::throwMe(errSecParam);
}
}
void
StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList)
{
CFIndex count = CFArrayGetCount(keychainArray);
if (!(count > 0))
return;
KeychainList keychains(count);
for (CFIndex ix = 0; ix < count; ++ix)
{
keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix)));
}
keychainList.swap(keychains);
}
CFArrayRef
StorageManager::convertFromKeychainList(const KeychainList &keychainList)
{
CFRef<CFMutableArrayRef> 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);
}
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(demungeDLDbIdentifier((*ix)->dlDbIdentifier()));
}
ids.swap(result);
}
void StorageManager::convertList(KeychainList &kcs, const DLDbList &ids)
{
StLock<Mutex>_(mMutex);
KeychainList result;
result.reserve(ids.size());
{
for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix)
result.push_back(keychain(*ix));
}
kcs.swap(result);
}
#pragma mark ____ Login Functions ____
void StorageManager::login(AuthorizationRef authRef, UInt32 nameLength, const char* name, bool isReset)
{
StLock<Mutex>_(mMutex);
AuthorizationItemSet* info = NULL;
OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); Boolean created = false;
if ( result == errSecSuccess && info->count )
{
AuthorizationItem* currItem = info->items;
for (UInt32 index = 1; index <= info->count; index++) {
if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
{
try
{
login(nameLength, name, (UInt32)currItem->valueLength, currItem->value, isReset);
created = true;
}
catch(...)
{
}
break;
}
currItem++;
}
}
if ( info )
AuthorizationFreeItemSet(info);
if ( !created )
MacOSError::throwMe(errAuthorizationInternal);
}
void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
{
StLock<Mutex>_(mMutex);
if ( name == NULL || password == NULL ) {
MacOSError::throwMe(errSecParam);
}
login(name[0], name + 1, password[0], password + 1, false);
}
void StorageManager::login(UInt32 nameLength, const void *name,
UInt32 passwordLength, const void *password, bool isReset)
{
if (passwordLength != 0 && password == NULL)
{
secnotice("KCLogin", "StorageManager::login: invalid argument (NULL password)");
MacOSError::throwMe(errSecParam);
}
DLDbIdentifier loginDLDbIdentifier;
{
mSavedList.revert(true);
loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
}
secnotice("KCLogin", "StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
if (!loginDLDbIdentifier)
MacOSError::throwMe(errSecNoSuchKeychain);
int uid = geteuid();
struct passwd *pw = getpwuid(uid);
if (pw == NULL) {
secnotice("KCLogin", "StorageManager::login: invalid argument (NULL uid)");
MacOSError::throwMe(errSecParam);
}
std::string userName = pw->pw_name;
std::string keychainPath = DLDbListCFPref::ExpandTildesInPath(kLoginKeychainPathPrefix);
std::string shortnameKeychain = keychainPath + userName;
std::string shortnameDotKeychain = shortnameKeychain + ".keychain";
std::string loginDotKeychain = keychainPath + "login.keychain";
std::string loginRenamed1Keychain = keychainPath + "login_renamed1.keychain";
std::string loginKeychainDb = keychainPath + "login.keychain-db";
bool shortnameKeychainExists = false;
bool shortnameDotKeychainExists = false;
bool loginKeychainExists = false;
bool loginRenamed1KeychainExists = false;
bool loginKeychainDbExists = false;
{
struct stat st;
int stat_result;
stat_result = ::stat(shortnameKeychain.c_str(), &st);
shortnameKeychainExists = (stat_result == 0);
stat_result = ::stat(shortnameDotKeychain.c_str(), &st);
shortnameDotKeychainExists = (stat_result == 0);
stat_result = ::stat(loginDotKeychain.c_str(), &st);
loginKeychainExists = (stat_result == 0);
stat_result = ::stat(loginRenamed1Keychain.c_str(), &st);
loginRenamed1KeychainExists = (stat_result == 0);
stat_result = ::stat(loginKeychainDb.c_str(), &st);
loginKeychainDbExists = (stat_result == 0);
}
loginKeychainExists |= loginKeychainDbExists;
bool loginUnlocked = false;
CSSM_VERSION version = {0, 0};
DLDbIdentifier shortnameDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameKeychain.c_str(), NULL);
DLDbIdentifier shortnameDotDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameDotKeychain.c_str(), NULL);
DLDbIdentifier loginRenamed1DLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, loginRenamed1Keychain.c_str(), NULL);
if (shortnameKeychainExists) {
int rename_stat = 0;
if (loginKeychainExists) {
struct stat st;
int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
if (tmp_result == 0) {
if (st.st_size <= kEmptyKeychainSizeInBytes) {
tmp_result = ::unlink(loginDotKeychain.c_str());
rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
shortnameKeychainExists = (rename_stat != 0);
}
}
}
if (shortnameKeychainExists) {
if (loginKeychainExists && !shortnameDotKeychainExists) {
rename_stat = ::rename(shortnameKeychain.c_str(), shortnameDotKeychain.c_str());
shortnameDotKeychainExists = (rename_stat == 0);
} else if (!loginKeychainExists) {
rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
loginKeychainExists = (rename_stat == 0);
} else {
char pathbuf[MAXPATHLEN];
std::string shortnameRenamedXXXKeychain = keychainPath;
shortnameRenamedXXXKeychain += userName;
shortnameRenamedXXXKeychain += "_renamed_XXX.keychain";
::strlcpy(pathbuf, shortnameRenamedXXXKeychain.c_str(), sizeof(pathbuf));
::mkstemps(pathbuf, 9); rename_stat = ::rename(shortnameKeychain.c_str(), pathbuf);
shortnameKeychainExists = (rename_stat != 0);
}
}
if (rename_stat != 0) {
MacOSError::throwMe(errno);
}
}
if (loginRenamed1KeychainExists && (!loginKeychainExists ||
(mSavedList.searchList().size() == 1 && mSavedList.member(demungeDLDbIdentifier(loginDLDbIdentifier))) )) {
try
{
Keychain loginRenamed1KC(keychain(loginRenamed1DLDbIdentifier));
secnotice("KCLogin", "Attempting to unlock renamed KC \"%s\"",
(loginRenamed1KC) ? loginRenamed1KC->name() : "<NULL>");
loginRenamed1KC->unlock(CssmData(const_cast<void *>(password), passwordLength));
if (loginKeychainExists) {
struct stat st;
int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
if (tmp_result == 0) {
if (st.st_size <= kEmptyKeychainSizeInBytes) {
tmp_result = ::unlink(loginDotKeychain.c_str());
tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
} else if (!shortnameDotKeychainExists) {
tmp_result = ::rename(loginRenamed1Keychain.c_str(), shortnameDotKeychain.c_str());
shortnameDotKeychainExists = (tmp_result == 0);
} else {
throw 1; }
}
} else {
int tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
loginKeychainExists = (tmp_result == 0);
}
}
catch(...)
{
char pathbuf[MAXPATHLEN];
std::string loginRenamedXXXKeychain = keychainPath;
loginRenamedXXXKeychain += "login_renamed_XXX.keychain";
::strlcpy(pathbuf, loginRenamedXXXKeychain.c_str(), sizeof(pathbuf));
::mkstemps(pathbuf, 9); ::rename(loginRenamed1Keychain.c_str(), pathbuf);
}
}
CFRef<CFDictionaryRef> tokenLoginContext;
CFRef<CFStringRef> smartCardPassword;
OSStatus tokenContextStatus = TokenLoginGetContext(password, passwordLength, tokenLoginContext.take());
if (!loginKeychainExists || (isReset && !loginKeychainDbExists)) {
if (tokenContextStatus == errSecSuccess) {
secnotice("KCLogin", "Going to create login keychain for sc login");
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate(NULL, NULL, 0, &authRef);
if (status == errSecSuccess) {
AuthorizationItem right = { "com.apple.builtin.sc-kc-new-passphrase", 0, NULL, 0 };
AuthorizationItemSet rightSet = { 1, &right };
uint32_t reason, tries;
reason = 0;
tries = 0;
AuthorizationItem envRights[] = {
{ AGENT_HINT_RETRY_REASON, sizeof(reason), &reason, 0 },
{ AGENT_HINT_TRIES, sizeof(tries), &tries, 0 }};
AuthorizationItemSet envSet = { sizeof(envRights) / sizeof(*envRights), envRights };
status = AuthorizationCopyRights(authRef, &rightSet, &envSet, kAuthorizationFlagDefaults|kAuthorizationFlagInteractionAllowed|kAuthorizationFlagExtendRights, NULL);
if (status == errSecSuccess) {
AuthorizationItemSet *returnedInfo;
status = AuthorizationCopyInfo(authRef, NULL, &returnedInfo);
if (status == errSecSuccess) {
if (returnedInfo && (returnedInfo->count > 0)) {
for (uint32_t index = 0; index < returnedInfo->count; index++) {
AuthorizationItem &item = returnedInfo->items[index];
if (!strcmp(AGENT_PASSWORD, item.name)) {
CFIndex len = item.valueLength;
if (len) {
secnotice("KCLogin", "User entered pwd");
smartCardPassword = CFStringCreateWithBytes(SecCFAllocatorZeroize(), (UInt8 *)item.value, (CFIndex)len, kCFStringEncodingUTF8, TRUE);
memset(item.value, 0, len);
}
}
}
}
}
if(returnedInfo) {
AuthorizationFreeItemSet(returnedInfo);
}
}
AuthorizationFree(authRef, 0);
}
}
Keychain theKeychain = makeKeychain(loginDLDbIdentifier, false, true);
secnotice("KCLogin", "Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
if (tokenContextStatus == errSecSuccess) {
if (smartCardPassword.get()) {
CFIndex length = CFStringGetLength(smartCardPassword);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
char *buffer = (char *)malloc(maxSize);
if (CFStringGetCString(smartCardPassword, buffer, maxSize, kCFStringEncodingUTF8)) {
secnotice("KCLogin", "Keychain is created using password provided by sc user");
theKeychain->create((UInt32)strlen(buffer), buffer);
memset(buffer, 0, maxSize);
} else {
secnotice("KCLogin", "Conversion failed");
MacOSError::throwMe(errSecNotAvailable);
}
} else {
secnotice("KCLogin", "User did not provide kc password");
MacOSError::throwMe(errSecNotAvailable);
}
} else {
theKeychain->create(passwordLength, password);
}
secnotice("KCLogin", "Login keychain created successfully");
loginKeychainExists = true;
loginKeychain(theKeychain);
theKeychain->setSettings(INT_MAX, false);
loginUnlocked = true;
mSavedList.revert(true);
}
if (mSavedList.member(demungeDLDbIdentifier(shortnameDLDbIdentifier))) {
if (shortnameDotKeychainExists && !mSavedList.member(demungeDLDbIdentifier(shortnameDotDLDbIdentifier))) {
secnotice("KCLogin", "Renaming %s to %s in keychain search list",
(shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
(shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : "<NULL>");
mSavedList.rename(demungeDLDbIdentifier(shortnameDLDbIdentifier),
demungeDLDbIdentifier(shortnameDotDLDbIdentifier));
} else if (!mSavedList.member(demungeDLDbIdentifier(loginDLDbIdentifier))) {
secnotice("KCLogin", "Renaming %s to %s in keychain search list",
(shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
(loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
mSavedList.rename(demungeDLDbIdentifier(shortnameDLDbIdentifier),
demungeDLDbIdentifier(loginDLDbIdentifier));
} else {
secnotice("KCLogin", "Removing %s from keychain search list", (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>");
mSavedList.remove(demungeDLDbIdentifier(shortnameDLDbIdentifier));
}
mSavedList.save();
mSavedList.revert(true);
}
if (!mSavedList.member(demungeDLDbIdentifier(loginDLDbIdentifier))) {
secnotice("KCLogin", "Adding %s to keychain search list", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
mSavedList.add(demungeDLDbIdentifier(loginDLDbIdentifier));
mSavedList.save();
mSavedList.revert(true);
}
if (shortnameDotKeychainExists && !mSavedList.member(demungeDLDbIdentifier(shortnameDotDLDbIdentifier))) {
mSavedList.add(demungeDLDbIdentifier(shortnameDotDLDbIdentifier));
mSavedList.save();
mSavedList.revert(true);
}
if (!mSavedList.member(mSavedList.defaultDLDbIdentifier())) {
secnotice("KCLogin", "Changing default keychain to %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(loginDLDbIdentifier));
mSavedList.save();
mSavedList.revert(true);
}
OSStatus loginResult = errSecSuccess;
if (!loginUnlocked) {
try
{
Keychain theKeychain(keychain(loginDLDbIdentifier));
secnotice("KCLogin", "Attempting to unlock login keychain \"%s\"",
(theKeychain) ? theKeychain->name() : "<NULL>");
theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
loginUnlocked = true;
}
catch(const CssmError &e)
{
loginResult = e.osStatus(); }
}
if (!loginUnlocked || tokenContextStatus == errSecSuccess) {
Keychain theKeychain(keychain(loginDLDbIdentifier));
bool tokenLoginDataUpdated = false;
for (UInt32 i = 0; i < 2; i++) {
loginResult = errSecSuccess;
CFRef<CFDictionaryRef> tokenLoginData;
if (tokenLoginContext) {
OSStatus status = TokenLoginGetLoginData(tokenLoginContext, tokenLoginData.take());
if (status != errSecSuccess) {
if (tokenLoginDataUpdated) {
loginResult = status;
break;
}
secnotice("KCLogin", "Error %d, reconstructing unlock data", (int)status);
status = TokenLoginUpdateUnlockData(tokenLoginContext, smartCardPassword);
if (status == errSecSuccess) {
loginResult = TokenLoginGetLoginData(tokenLoginContext, tokenLoginData.take());
if (loginResult != errSecSuccess) {
break;
}
tokenLoginDataUpdated = true;
}
}
}
try {
if (tokenLoginData) {
secnotice("KCLogin", "Going to unlock keybag using scBlob");
OSStatus status = TokenLoginUnlockKeybag(tokenLoginContext, tokenLoginData);
secnotice("KCLogin", "Keybag unlock result %d", (int)status);
if (status)
CssmError::throwMe(status); }
CssmKey key;
key.header().BlobType = CSSM_KEYBLOB_RAW;
key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
key.header().AlgorithmId = CSSM_ALGID_3DES_3KEY;
key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
key.header().KeyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT | CSSM_KEYATTR_EXTRACTABLE;
key.header().KeyAttr = 0;
CFRef<CFDataRef> tokenLoginUnlockKey;
if (tokenLoginData) {
OSStatus status = TokenLoginGetUnlockKey(tokenLoginContext, tokenLoginUnlockKey.take());
if (status)
CssmError::throwMe(status); key.KeyData = CssmData(tokenLoginUnlockKey.get());
} else {
key.KeyData = CssmData(const_cast<void *>(password), passwordLength);
}
UnwrapKey unwrap(theKeychain->csp(), CSSM_ALGID_NONE);
CssmKey masterKey;
CssmData descriptiveData;
unwrap(key,
KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_EXTRACTABLE),
masterKey, &descriptiveData, NULL);
CssmClient::Db db = theKeychain->database();
Allocator &alloc = db->allocator();
AutoCredentials cred(alloc);
cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
new(alloc) ListElement(CssmData::wrap(theKeychain->csp()->handle())),
new(alloc) ListElement(CssmData::wrap(masterKey)),
new(alloc) ListElement(CssmData()));
db->authenticate(CSSM_DB_ACCESS_READ, &cred);
db->unlock();
loginUnlocked = true;
} catch (const CssmError &e) {
if (tokenLoginData && !tokenLoginDataUpdated) {
loginResult = TokenLoginUpdateUnlockData(tokenLoginContext, smartCardPassword);
if (loginResult == errSecSuccess) {
tokenLoginDataUpdated = true;
continue;
}
}
else {
loginResult = e.osStatus();
}
}
break;
}
}
if (shortnameDotKeychainExists && mSavedList.member(demungeDLDbIdentifier(shortnameDotDLDbIdentifier))) {
try
{
Keychain shortnameDotKC(keychain(shortnameDotDLDbIdentifier));
secnotice("KCLogin", "Attempting to unlock short name keychain \"%s\"",
(shortnameDotKC) ? shortnameDotKC->name() : "<NULL>");
shortnameDotKC->unlock(CssmData(const_cast<void *>(password), passwordLength));
}
catch(const CssmError &e)
{
}
}
if (loginResult != errSecSuccess) {
MacOSError::throwMe(loginResult);
}
}
void StorageManager::stashLogin()
{
OSStatus loginResult = errSecSuccess;
DLDbIdentifier loginDLDbIdentifier;
{
mSavedList.revert(true);
loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
}
secnotice("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
if (!loginDLDbIdentifier)
MacOSError::throwMe(errSecNoSuchKeychain);
try
{
CssmData empty;
Keychain theKeychain(keychain(loginDLDbIdentifier));
secnotice("KCLogin", "Attempting to use stash for login keychain \"%s\"",
(theKeychain) ? theKeychain->name() : "<NULL>");
theKeychain->stashCheck();
}
catch(const CssmError &e)
{
loginResult = e.osStatus(); }
if (loginResult != errSecSuccess) {
MacOSError::throwMe(loginResult);
}
}
void StorageManager::stashKeychain()
{
OSStatus loginResult = errSecSuccess;
DLDbIdentifier loginDLDbIdentifier;
{
mSavedList.revert(true);
loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
}
secnotice("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
if (!loginDLDbIdentifier)
MacOSError::throwMe(errSecNoSuchKeychain);
try
{
Keychain theKeychain(keychain(loginDLDbIdentifier));
secnotice("KCLogin", "Attempting to stash login keychain \"%s\"",
(theKeychain) ? theKeychain->name() : "<NULL>");
theKeychain->stash();
}
catch(const CssmError &e)
{
loginResult = e.osStatus(); }
if (loginResult != errSecSuccess) {
MacOSError::throwMe(loginResult);
}
}
void StorageManager::logout()
{
}
void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
{
StLock<Mutex>_(mMutex);
loginKeychain()->changePassphrase(oldPassword, newPassword);
secnotice("KClogin", "Changed login keychain password successfully");
}
void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword)
{
StLock<Mutex>_(mMutex);
loginKeychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, newPassword);
secnotice("KClogin", "Changed login keychain password successfully");
}
void StorageManager::resetKeychain(Boolean resetSearchList)
{
StLock<Mutex>_(mMutex);
Keychain keychain = NULL;
DLDbIdentifier dldbi;
try
{
if ( resetSearchList )
{
StorageManager::KeychainList keychainList;
setSearchList(keychainList);
}
keychain = loginKeychain();
} catch(const CommonError& e) {
secnotice("KClogin", "Failed to open login keychain due to an error: %s", e.what());
dldbi = loginKeychainDLDbIdentifer();
struct stat exists;
if(::stat(dldbi.dbName(), &exists) != 0) {
secnotice("KClogin", "no file exists; resetKeychain() is done");
return;
}
}
try{
CFMutableStringRef newName = NULL;
newName = CFStringCreateMutable(NULL, 0);
CFStringRef currName = NULL;
if(keychain) {
currName = CFStringCreateWithCString(NULL, keychain->name(), kCFStringEncodingUTF8);
} else {
currName = CFStringCreateWithCString(NULL, dldbi.dbName(), kCFStringEncodingUTF8);
}
if ( newName && currName )
{
CFStringAppend(newName, currName);
CFStringRef kcSuffix = CFSTR(kKeychainSuffix);
CFStringRef kcDbSuffix = CFSTR(kKeychainDbSuffix);
bool hasDbSuffix = false;
if ( CFStringHasSuffix(newName, kcSuffix) ) {
CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
}
if (CFStringHasSuffix(newName, kcDbSuffix)) {
hasDbSuffix = true;
CFRange suffixRange = CFStringFind(newName, kcDbSuffix, 0);
CFStringFindAndReplace(newName, kcDbSuffix, CFSTR(""), suffixRange, 0);
}
CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix)); try
{
secnotice("KClogin", "attempting keychain rename to %@", newName);
renameUnique(keychain, currName, newName, hasDbSuffix);
}
catch(const CommonError& e)
{
secnotice("KClogin", "Failed to renameUnique due to an error: %s", e.what());
}
catch(...)
{
secnotice("KClogin", "Failed to renameUnique due to an unknown error");
}
} else {
secnotice("KClogin", "don't have paths, quitting");
}
if ( newName )
CFRelease(newName);
if ( currName )
CFRelease(currName);
}
catch(const CommonError& e) {
secnotice("KClogin", "Failed to reset login keychain due to an error: %s", e.what());
}
catch(...)
{
secnotice("KClogin", "Failed to reset keychain due to an unknown error");
}
}
#pragma mark ____ File Related ____
Keychain StorageManager::make(const char *pathName)
{
return make(pathName, true);
}
Keychain StorageManager::make(const char *pathName, bool add)
{
return make(pathName, add, false);
}
Keychain StorageManager::make(const char *pathName, bool add, bool isReset) {
return makeKeychain(makeDLDbIdentifier(pathName), add, isReset);
}
DLDbIdentifier StorageManager::makeDLDbIdentifier(const char *pathName) {
StLock<Mutex>_(mMutex);
string fullPathName;
if ( pathName[0] == '/' )
fullPathName = pathName;
else
{
switch (mDomain)
{
case kSecPreferencesDomainUser:
{
const char *homeDir = getenv("HOME");
if (homeDir == NULL)
{
uid_t uid = geteuid();
if (!uid) uid = getuid();
struct passwd *pw = getpwuid(uid);
if (!pw)
MacOSError::throwMe(errSecParam);
homeDir = pw->pw_dir;
}
fullPathName = homeDir;
}
break;
case kSecPreferencesDomainSystem:
fullPathName = "";
break;
default:
assert(false); }
fullPathName += "/Library/Keychains/";
fullPathName += pathName;
}
const CSSM_NET_ADDRESS *DbLocation = NULL; 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 dlDbIdentifier;
}
Keychain StorageManager::makeLoginAuthUI(const Item *item, bool isReset)
{
StLock<Mutex>_(mMutex);
OSStatus result = errSecSuccess;
Keychain keychain; AuthorizationItem *currItem, *authEnvirItemArrayPtr = NULL;
AuthorizationRef authRef = NULL;
try
{
result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef);
if ( result )
MacOSError::throwMe(result);
AuthorizationEnvironment envir;
envir.count = 6; authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count);
if ( !authEnvirItemArrayPtr )
MacOSError::throwMe(errAuthorizationInternal);
currItem = envir.items = authEnvirItemArrayPtr;
char buff[256];
UInt32 actLen = 0;
SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff };
if ( item )
{
try
{
(*item)->getAttribute(attr, &actLen);
}
catch(...)
{
actLen = 0; }
}
currItem->name = AGENT_HINT_ATTR_NAME; if ( actLen ) {
if ( actLen >= sizeof(buff) )
buff[sizeof(buff)-1] = 0;
else
buff[actLen] = 0;
currItem->valueLength = strlen(buff)+1;
currItem->value = buff;
}
else
{
currItem->valueLength = 0;
currItem->value = NULL;
}
currItem->flags = 0;
currItem++;
char* currDefaultName = NULL;
try
{
currDefaultName = (char*)defaultKeychain()->name(); currItem->name = AGENT_HINT_LOGIN_KC_NAME; currItem->valueLength = (currDefaultName) ? strlen(currDefaultName) : 0;
currItem->value = (currDefaultName) ? (void*)currDefaultName : (void*)"";
currItem->flags = 0;
currItem++;
}
catch(...)
{
envir.count--;
}
currItem->name = AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER;
Boolean loginUnavail = false;
try
{
Keychain defaultKC = defaultKeychain();
if ( !defaultKC->exists() )
loginUnavail = true;
}
catch(...) {
}
currItem->valueLength = sizeof(Boolean);
currItem->value = (void*)&loginUnavail;
currItem->flags = 0;
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); if (pw)
userName = pw->pw_name;
endpwent();
}
if ( userName.length() == 0 ) MacOSError::throwMe(errAuthorizationInternal);
currItem->value = (void*)userName.c_str();
currItem->valueLength = userName.length();
currItem->flags = 0;
currItem++;
currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR;
Boolean moreThanOneKCExists = false;
{
if (item && mSavedList.searchList().size() > 1)
moreThanOneKCExists = true;
}
currItem->value = &moreThanOneKCExists;
currItem->valueLength = sizeof(Boolean);
currItem->flags = 0;
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;
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); }
catch (...) {
}
login(authRef, (UInt32)userName.length(), userName.c_str(), isReset); keychain = loginKeychain(); defaultKeychain(keychain);
free(authEnvirItemArrayPtr);
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
}
catch (...)
{
if ( authEnvirItemArrayPtr )
free(authEnvirItemArrayPtr);
if ( authRef )
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
throw;
}
return keychain;
}
Keychain StorageManager::defaultKeychainUI(Item &item)
{
StLock<Mutex>_(mMutex);
Keychain returnedKeychain;
try
{
returnedKeychain = defaultKeychain(); if ( returnedKeychain->exists() )
return returnedKeychain;
}
catch(...) {
}
if ( globals().getUserInteractionAllowed() )
{
returnedKeychain = makeLoginAuthUI(&item, false); if ( !returnedKeychain )
MacOSError::throwMe(errSecInvalidKeychain); }
else
MacOSError::throwMe(errSecInteractionNotAllowed);
return returnedKeychain;
}
void
StorageManager::addToDomainList(SecPreferencesDomain domain,
const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
CSSM_VERSION version = {0, 0};
DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
subServiceType, dbName, NULL);
if (domain == mDomain)
{
{
mSavedList.revert(true);
mSavedList.add(demungeDLDbIdentifier(id));
mSavedList.save();
}
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
else
{
DLDbListCFPref(domain).add(id);
}
}
void
StorageManager::isInDomainList(SecPreferencesDomain domain,
const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
CSSM_VERSION version = {0, 0};
DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
subServiceType, dbName, NULL);
bool result;
if (domain == mDomain)
{
result = mSavedList.member(demungeDLDbIdentifier(id));
}
else
{
result = DLDbListCFPref(domain).member(demungeDLDbIdentifier(id));
}
if (!result)
{
MacOSError::throwMe(errSecNoSuchKeychain);
}
}
void
StorageManager::removeFromDomainList(SecPreferencesDomain domain,
const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
{
StLock<Mutex>_(mMutex);
if (domain == kSecPreferencesDomainDynamic)
MacOSError::throwMe(errSecInvalidPrefsDomain);
CSSM_VERSION version = {0, 0};
DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
subServiceType, dbName, NULL);
if (domain == mDomain)
{
{
mSavedList.revert(true);
mSavedList.remove(demungeDLDbIdentifier(id));
mSavedList.save();
}
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
}
else
{
DLDbListCFPref(domain).remove(id);
}
}
bool
StorageManager::keychainOwnerPermissionsValidForDomain(const char* path, SecPreferencesDomain domain)
{
struct stat sb;
mode_t perms;
const char* sysPrefDir = "/Library/Preferences";
const char* errMsg = "Will not set default";
char* mustOwnDir = NULL;
struct passwd* pw = NULL;
uid_t uid = geteuid();
if (!uid) uid = getuid();
switch (domain) {
case kSecPreferencesDomainUser:
mustOwnDir = getenv("HOME");
if (mustOwnDir == NULL) {
pw = getpwuid(uid);
if (!pw) return false;
mustOwnDir = pw->pw_dir;
}
break;
case kSecPreferencesDomainSystem:
mustOwnDir = (char*)sysPrefDir;
break;
case kSecPreferencesDomainCommon:
mustOwnDir = (char*)sysPrefDir;
break;
default:
return false;
}
if (mustOwnDir != NULL) {
struct stat dsb;
if ( (stat(mustOwnDir, &dsb) != 0) || (dsb.st_uid != uid) ) {
fprintf(stderr, "%s: UID=%d does not own directory %s\n", errMsg, (int)uid, mustOwnDir);
mustOwnDir = NULL; }
}
if (pw != NULL)
endpwent();
if (mustOwnDir == NULL)
return false;
if (stat(path, &sb) != 0) {
fprintf(stderr, "%s: file %s does not exist\n", errMsg, path);
return false;
}
if (sb.st_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) {
fprintf(stderr, "%s: file %s is immutable\n", errMsg, path);
return false;
}
if (sb.st_uid != uid) {
fprintf(stderr, "%s: file %s is owned by UID=%d, but we have UID=%d\n",
errMsg, path, (int)sb.st_uid, (int)uid);
return false;
}
perms = sb.st_mode;
perms |= 0600; if (sb.st_mode != perms) {
fprintf(stderr, "%s: file %s does not have the expected permissions\n", errMsg, path);
return false;
}
return true;
}