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 <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.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 "TrustSettingsSchema.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) {
secdebug("servermode", "StorageManager initialized in server mode");
sessionAttrs = sessionIsRoot;
} else {
MacOSError::check(SessionGetInfo(callerSecuritySession, NULL, &sessionAttrs));
}
if ((sessionAttrs & sessionIsRoot)
IFDEBUG( && !(sessionAttrs & sessionHasGraphicAccess))) {
secdebug("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;
}
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 = mKeychains.find(dLDbIdentifier);
if (it != mKeychains.end())
{
if (it->second == NULL) {
mKeychains.erase(it);
}
else
{
return it->second;
}
}
if (gServerMode) {
secdebug("servermode", "keychain reference in server mode");
const char *dbname = dLDbIdentifier.dbName();
if (!dbname || (strcmp(dbname, SYSTEM_ROOT_STORE_PATH)!=0))
return Keychain();
}
Module module(dLDbIdentifier.ssuid().guid());
DL dl;
if (dLDbIdentifier.ssuid().subserviceType() & CSSM_SERVICE_CSP)
dl = SSCSPDL(module);
else
dl = DL(module);
dl->subserviceId(dLDbIdentifier.ssuid().subserviceId());
dl->version(dLDbIdentifier.ssuid().version());
Db db(dl, dLDbIdentifier.dbName());
Keychain keychain(db);
mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, &*keychain));
keychain->inCache(true);
return keychain;
}
void
StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier,
KeychainImpl *keychainImpl)
{
StLock<Mutex>_(mKeychainMapMutex);
KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
if (it != mKeychains.end() && (KeychainImpl*) it->second == keychainImpl)
mKeychains.erase(it);
keychainImpl->inCache(false);
}
void
StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier)
{
StLock<Mutex>_(mKeychainMapMutex);
KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
if (it != mKeychains.end())
{
if (it->second != NULL) {
KeychainImpl *keychainImpl = it->second;
keychainImpl->inCache(false);
}
mKeychains.erase(it);
}
}
Keychain
StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add)
{
StLock<Mutex>_(mKeychainMapMutex);
Keychain theKeychain = keychain(dLDbIdentifier);
bool post = false;
bool updateList = (add && !isAppSandboxed());
if (updateList)
{
mSavedList.revert(false);
DLDbList searchList = mSavedList.searchList();
if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
return theKeychain;
mCommonList.revert(false);
searchList = mCommonList.searchList();
if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
return theKeychain;
if (!theKeychain->exists())
return theKeychain;
mSavedList.revert(true);
mSavedList.add(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 = (!isAppSandboxed());
if (updateList)
{
mSavedList.revert(true);
if (!mSavedList.defaultDLDbIdentifier())
{
mSavedList.defaultDLDbIdentifier(dLDbIdentifier);
defaultChanged = true;
}
mSavedList.add(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(wrPermErr);
DLDbIdentifier oldDefaultId;
DLDbIdentifier newDefaultId(keychain->dlDbIdentifier());
{
oldDefaultId = mSavedList.defaultDLDbIdentifier();
mSavedList.revert(true);
mSavedList.defaultDLDbIdentifier(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);
}
void
StorageManager::loginKeychain(Keychain keychain)
{
StLock<Mutex>_(mMutex);
mSavedList.revert(true);
mSavedList.loginDLDbIdentifier(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();
keychain->database()->rename(newName);
if (dLDbIdentifier == defaultId)
changedDefault=true;
newDLDbIdentifier = keychain->dlDbIdentifier();
mSavedList.rename(dLDbIdentifier, newDLDbIdentifier);
if (changedDefault)
mSavedList.defaultDLDbIdentifier(newDLDbIdentifier);
mSavedList.save();
if (keychain->inCache())
{
KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
if (it != mKeychains.end() && (KeychainImpl*) it->second == keychain.get())
{
mKeychains.erase(it);
}
}
KeychainMap::iterator it = mKeychains.find(newDLDbIdentifier);
if (it != mKeychains.end())
{
Keychain oldKeychain(it->second);
oldKeychain->inCache(false);
}
if (keychain->inCache())
{
mKeychains.insert(KeychainMap::value_type(newDLDbIdentifier,
keychain));
}
}
KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
if (changedDefault)
KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent,
newDLDbIdentifier);
}
void StorageManager::renameUnique(Keychain keychain, CFStringRef newName)
{
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);
CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix)); char toUseBuff2[MAXPATHLEN];
if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) {
struct stat filebuf;
if ( lstat(toUseBuff2, &filebuf) )
{
rename(keychain, toUseBuff2);
KeychainList kcList;
kcList.push_back(keychain);
remove(kcList, false);
doneCreating = true;
}
else
index++;
}
else
doneCreating = true; 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; }
CFStringRef vExpanded = MakeExpandedPath (CFStringGetCStringPtr (v, 0));
CFComparisonResult result = CFStringCompare (vExpanded, idString.get(), 0);
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(dLDbIdentifier);
if (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 commonList = mCommonList.searchList();
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)
{
if (it_end == keychainList.begin())
break;
--it_end;
if (!((*it_end)->dlDbIdentifier() == *it_common))
{
++it_end;
break;
}
}
DLDbList searchList, oldSearchList(mSavedList.searchList());
for (KeychainList::const_iterator it = keychainList.begin(); it != it_end; ++it)
{
searchList.push_back((*it)->dlDbIdentifier());
}
{
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:
secdebug("storagemgr", "switching to system domain"); break;
case kSecPreferencesDomainUser:
secdebug("storagemgr", "switching to user domain (uid %d)", getuid()); break;
default:
secdebug("storagemgr", "switching to weird prefs domain %d", domain); break;
}
#endif
mDomain = domain;
mSavedList.set(domain);
}
void
StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
{
StLock<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(paramErr);
}
}
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((*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)
{
StLock<Mutex>_(mMutex);
AuthorizationItemSet* info = NULL;
OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); Boolean created = false;
if ( result == noErr && info->count )
{
AuthorizationItem* currItem = info->items;
for (UInt32 index = 1; index <= info->count; index++) {
if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
{
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)
{
StLock<Mutex>_(mMutex);
if ( name == NULL || password == NULL )
MacOSError::throwMe(paramErr);
login(name[0], name + 1, password[0], password + 1);
}
void StorageManager::login(UInt32 nameLength, const void *name,
UInt32 passwordLength, const void *password)
{
if (passwordLength != 0 && password == NULL)
{
secdebug("KCLogin", "StorageManager::login: invalid argument (NULL password)");
MacOSError::throwMe(paramErr);
}
DLDbIdentifier loginDLDbIdentifier;
{
mSavedList.revert(true);
loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
}
secdebug("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) {
secdebug("KCLogin", "StorageManager::login: invalid argument (NULL uid)");
MacOSError::throwMe(paramErr);
}
char *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";
bool shortnameKeychainExists = false;
bool shortnameDotKeychainExists = false;
bool loginKeychainExists = false;
bool loginRenamed1KeychainExists = false;
{
struct stat st;
int stat_result;
stat_result = ::stat(shortnameKeychain.c_str(), &st);
shortnameKeychainExists = (stat_result == 0);
stat_result = ::stat(shortnameDotKeychain.c_str(), &st);
shortnameDotKeychainExists = (stat_result == 0);
stat_result = ::stat(loginDotKeychain.c_str(), &st);
loginKeychainExists = (stat_result == 0);
stat_result = ::stat(loginRenamed1Keychain.c_str(), &st);
loginRenamed1KeychainExists = (stat_result == 0);
}
bool loginUnlocked = false;
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(loginDLDbIdentifier)) )) {
try
{
Keychain loginRenamed1KC(keychain(loginRenamed1DLDbIdentifier));
secdebug("KCLogin", "Attempting to unlock %s with %d-character password",
(loginRenamed1KC) ? loginRenamed1KC->name() : "<NULL>", (unsigned int)passwordLength);
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);
}
}
if (!loginKeychainExists) {
Keychain theKeychain(keychain(loginDLDbIdentifier));
secdebug("KCLogin", "Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
theKeychain->create(passwordLength, password);
secdebug("KCLogin", "Login keychain created successfully");
loginKeychainExists = true;
loginKeychain(theKeychain);
theKeychain->setSettings(INT_MAX, false);
loginUnlocked = true;
mSavedList.revert(true);
}
if (mSavedList.member(shortnameDLDbIdentifier)) {
if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
secdebug("KCLogin", "Renaming %s to %s in keychain search list",
(shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
(shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : "<NULL>");
mSavedList.rename(shortnameDLDbIdentifier, shortnameDotDLDbIdentifier);
} else if (!mSavedList.member(loginDLDbIdentifier)) {
secdebug("KCLogin", "Renaming %s to %s in keychain search list",
(shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
(loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
mSavedList.rename(shortnameDLDbIdentifier, loginDLDbIdentifier);
} else {
secdebug("KCLogin", "Removing %s from keychain search list", (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>");
mSavedList.remove(shortnameDLDbIdentifier);
}
mSavedList.save();
mSavedList.revert(true);
}
if (!mSavedList.member(loginDLDbIdentifier)) {
secdebug("KCLogin", "Adding %s to keychain search list", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
mSavedList.add(loginDLDbIdentifier);
mSavedList.save();
mSavedList.revert(true);
}
if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
mSavedList.add(shortnameDotDLDbIdentifier);
mSavedList.save();
mSavedList.revert(true);
}
if (!mSavedList.member(mSavedList.defaultDLDbIdentifier())) {
secdebug("KCLogin", "Changing default keychain to %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
mSavedList.defaultDLDbIdentifier(loginDLDbIdentifier);
mSavedList.save();
mSavedList.revert(true);
}
OSStatus loginResult = noErr;
if (!loginUnlocked) {
try
{
Keychain theKeychain(keychain(loginDLDbIdentifier));
secdebug("KCLogin", "Attempting to unlock login keychain \"%s\" with %d-character password",
(theKeychain) ? theKeychain->name() : "<NULL>", (unsigned int)passwordLength);
theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
loginUnlocked = true;
}
catch(const CssmError &e)
{
loginResult = e.osStatus(); }
}
if (shortnameDotKeychainExists && mSavedList.member(shortnameDotDLDbIdentifier)) {
try
{
Keychain shortnameDotKC(keychain(shortnameDotDLDbIdentifier));
secdebug("KCLogin", "Attempting to unlock %s",
(shortnameDotKC) ? shortnameDotKC->name() : "<NULL>");
shortnameDotKC->unlock(CssmData(const_cast<void *>(password), passwordLength));
}
catch(const CssmError &e)
{
}
}
if (loginResult != noErr) {
MacOSError::throwMe(loginResult);
}
}
void StorageManager::logout()
{
}
void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
{
StLock<Mutex>_(mMutex);
loginKeychain()->changePassphrase(oldPassword, newPassword);
secdebug("KClogin", "Changed login keychain password successfully");
}
void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword)
{
StLock<Mutex>_(mMutex);
loginKeychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, newPassword);
secdebug("KClogin", "Changed login keychain password successfully");
}
void StorageManager::resetKeychain(Boolean resetSearchList)
{
StLock<Mutex>_(mMutex);
try
{
if ( resetSearchList )
{
StorageManager::KeychainList keychainList;
setSearchList(keychainList);
}
Keychain keychain = loginKeychain();
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) ) {
CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
}
CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix)); try
{
renameUnique(keychain, newName);
}
catch(...)
{
}
} if ( newName )
CFRelease(newName);
if ( currName )
CFRelease(currName);
}
catch(...)
{
}
}
#pragma mark ____ File Related ____
Keychain StorageManager::make(const char *pathName)
{
return make(pathName, true);
}
Keychain StorageManager::make(const char *pathName, bool add)
{
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(paramErr);
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 makeKeychain(dLDbIdentifier, add);
}
Keychain StorageManager::makeLoginAuthUI(const Item *item)
{
StLock<Mutex>_(mMutex);
OSStatus result = noErr;
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[255];
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 > 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;
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, userName.length(), userName.c_str()); 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); 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(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(id);
}
else
{
result = DLDbListCFPref(domain).member(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(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;
}