#include "kcdatabase.h"
#include "agentquery.h"
#include "kckey.h"
#include "server.h"
#include "session.h"
#include "notifications.h"
#include <vector> // @@@ 4003540 workaround
#include <security_cdsa_utilities/acl_any.h> // for default owner ACLs
#include <security_cdsa_utilities/cssmendian.h>
#include <security_cdsa_client/wrapkey.h>
#include <security_cdsa_client/genkey.h>
#include <security_cdsa_client/signclient.h>
#include <security_cdsa_client/cryptoclient.h>
#include <security_cdsa_client/macclient.h>
#include <securityd_client/dictionary.h>
#include <security_utilities/endian.h>
#include "securityd_service/securityd_service/securityd_service_client.h"
#include <AssertMacros.h>
#include <syslog.h>
#include <sys/sysctl.h>
#include <sys/kauth.h>
#include <sys/csr.h>
__BEGIN_DECLS
#include <corecrypto/ccmode_siv.h>
__END_DECLS
void unflattenKey(const CssmData &flatKey, CssmKey &rawKey);
KeychainDbCommon::CommonSet KeychainDbCommon::mCommonSet;
ReadWriteLock KeychainDbCommon::mRWCommonLock;
static void get_process_euid(pid_t pid, uid_t * out_euid)
{
if (!out_euid) return;
struct kinfo_proc proc_info = {};
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
size_t len = sizeof(struct kinfo_proc);
int ret;
ret = sysctl(mib, (sizeof(mib)/sizeof(int)), &proc_info, &len, NULL, 0);
if ((ret == 0) && (proc_info.kp_eproc.e_ucred.cr_uid != 0)) {
*out_euid = proc_info.kp_eproc.e_ucred.cr_uid;
}
}
static int
unlock_keybag(KeychainDatabase & db, const void * secret, int secret_len)
{
int rc = -1;
if (!db.common().isLoginKeychain()) return 0;
service_context_t context = db.common().session().get_current_service_context();
if (context.s_uid == AU_DEFAUDITID) {
get_process_euid(db.process().pid(), &context.s_uid);
}
rc = service_client_kb_unlock(&context, secret, secret_len);
if (rc == KB_BagNotLoaded) {
if (service_client_kb_load(&context) == KB_BagNotFound) {
rc = service_client_kb_create(&context, secret, secret_len);
} else {
rc = service_client_kb_unlock(&context, secret, secret_len);
}
}
if (rc != 0) { if (!db.common().session().keybagGetState(session_keybag_check_master_key)) {
CssmAutoData encKey(Allocator::standard(Allocator::sensitive));
db.common().get_encryption_key(encKey);
if ((rc = service_client_kb_unlock(&context, encKey.data(), (int)encKey.length())) == 0) {
rc = service_client_kb_change_secret(&context, encKey.data(), (int)encKey.length(), secret, secret_len);
}
if (rc != 0) { bool no_pin = false;
if ((secret_len > 0) && service_client_kb_is_locked(&context, NULL, &no_pin) == 0) {
if (no_pin) {
syslog(LOG_ERR, "Updating iCloud keychain passphrase for uid %d", context.s_uid);
service_client_kb_change_secret(&context, NULL, 0, secret, secret_len);
}
}
}
} }
if (rc == 0) {
db.common().session().keybagSetState(session_keybag_unlocked|session_keybag_loaded|session_keybag_check_master_key);
} else {
syslog(LOG_ERR, "Failed to unlock iCloud keychain for uid %d", context.s_uid);
}
return rc;
}
static void
change_secret_on_keybag(KeychainDatabase & db, const void * secret, int secret_len, const void * new_secret, int new_secret_len)
{
if (!db.common().isLoginKeychain()) return;
service_context_t context = db.common().session().get_current_service_context();
if (context.s_uid == AU_DEFAUDITID) {
get_process_euid(db.process().pid(), &context.s_uid);
}
int rc = service_client_kb_change_secret(&context, secret, secret_len, new_secret, new_secret_len);
if (rc == KB_BagNotLoaded) {
if (service_client_kb_load(&context) == KB_BagNotFound) {
rc = service_client_kb_create(&context, new_secret, new_secret_len);
} else {
rc = service_client_kb_change_secret(&context, secret, secret_len, new_secret, new_secret_len);
}
}
if (rc != KB_Success) {
service_client_kb_save(&context);
}
bool locked = false;
if ((service_client_kb_is_locked(&context, &locked, NULL) == KB_Success) && locked) {
rc = service_client_kb_unlock(&context, new_secret, new_secret_len);
if (rc != KB_Success) {
syslog(LOG_ERR, "Failed to unlock iCloud keychain for uid %d (%d)", context.s_uid, (int)rc);
}
}
}
static bool
unlock_keybag_with_cred(KeychainDatabase &db, const AccessCredentials *cred){
list<CssmSample> samples;
if (cred && cred->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, samples)) {
for (list<CssmSample>::iterator it = samples.begin(); it != samples.end(); it++) {
TypedList &sample = *it;
sample.checkProper();
switch (sample.type()) {
case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT: {
StSyncLock<Mutex, Mutex> uisync(db.common().uiLock(), db.common());
bool locked = false;
service_context_t context = db.common().session().get_current_service_context();
if ((service_client_kb_is_locked(&context, &locked, NULL) == 0) && locked) {
QueryKeybagPassphrase keybagQuery(db.common().session(), 3);
keybagQuery.inferHints(Server::process());
if (keybagQuery.query() == SecurityAgent::noReason) {
return true;
}
}
else {
return true;
}
break;
}
case CSSM_SAMPLE_TYPE_PASSWORD: {
if (sample.length() != 2)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
secinfo("KCdb", "attempting passphrase unlock of keybag");
if (unlock_keybag(db, sample[1].data().data(), (int)sample[1].data().length())) {
return true;
}
break;
}
default: {
secinfo("KCdb", "keybag: unknown sub-sample unlock (%d) ignored", sample.type());
break;
}
}
}
}
return false;
}
KeychainDatabase::KeychainDatabase(const DLDbIdentifier &id, const DBParameters ¶ms, Process &proc,
const AccessCredentials *cred, const AclEntryPrototype *owner)
: LocalDatabase(proc), mValidData(false), mSecret(Allocator::standard(Allocator::sensitive)), mSaveSecret(false), version(0), mBlob(NULL), mRecoded(false)
{
mCred = DataWalkers::copy(cred, Allocator::standard());
DbBlob::Signature newSig;
Server::active().random(newSig.bytes);
DbIdentifier ident(id, newSig);
RefPointer<KeychainDbCommon> newCommon = new KeychainDbCommon(proc.session(), ident, CommonBlob::getCurrentVersionForDb(ident.dbName()));
newCommon->initializeKeybag();
StLock<Mutex> _(*newCommon);
parent(*newCommon);
newCommon->insert();
establishNewSecrets(cred, SecurityAgent::newDatabase);
common().mParams = params;
common().makeNewSecrets();
if (owner)
acl().cssmSetInitial(*owner);
else
acl().cssmSetInitial(new AnyAclSubject());
mValidData = true;
encode();
proc.addReference(*this);
activity();
secinfo("KCdb", "creating keychain %p %s with common %p", this, (char*)this->dbName(), &common());
}
KeychainDatabase::KeychainDatabase(const DLDbIdentifier &id, const DbBlob *blob, Process &proc,
const AccessCredentials *cred)
: LocalDatabase(proc), mValidData(false), mSecret(Allocator::standard(Allocator::sensitive)), mSaveSecret(false), version(0), mBlob(NULL), mRecoded(false)
{
validateBlob(blob);
mCred = DataWalkers::copy(cred, Allocator::standard());
mBlob = blob->copy();
DbIdentifier ident(id, blob->randomSignature);
Session &session = process().session();
RefPointer<KeychainDbCommon> com;
secinfo("kccommon", "looking for a common at %s", ident.dbName());
if (KeychainDbCommon::find(ident, session, com)) {
parent(*com);
secinfo("KCdb", "joining keychain %p %s with common %p", this, (char*)this->dbName(), &common());
} else {
secinfo("kccommon", "no common found");
parent(*com);
common().mParams = blob->params;
secinfo("KCdb", "making keychain %p %s with common %p", this, (char*)this->dbName(), &common());
}
proc.addReference(*this);
}
void KeychainDbCommon::insert()
{
StReadWriteLock _(mRWCommonLock, StReadWriteLock::Write);
insertHoldingLock();
}
void KeychainDbCommon::insertHoldingLock()
{
mCommonSet.insert(this);
}
bool KeychainDbCommon::find(const DbIdentifier &ident, Session &session, RefPointer<KeychainDbCommon> &common, uint32 requestedVersion, KeychainDbCommon* cloneFrom)
{
{
StReadWriteLock _(mRWCommonLock, StReadWriteLock::Read);
for (CommonSet::const_iterator it = mCommonSet.begin(); it != mCommonSet.end(); ++it) {
if (&session == &(*it)->session() && ident == (*it)->identifier()) {
common = *it;
secinfo("kccommon", "found a common for %s at %p", ident.dbName(), common.get());
return true;
}
}
}
{
StReadWriteLock _(mRWCommonLock, StReadWriteLock::Write);
for (CommonSet::const_iterator it = mCommonSet.begin(); it != mCommonSet.end(); ++it) {
if (&session == &(*it)->session() && ident == (*it)->identifier()) {
common = *it;
secinfo("kccommon", "found a common for %s at %p", ident.dbName(), common.get());
return true;
}
}
if(cloneFrom) {
common = new KeychainDbCommon(session, ident, *cloneFrom);
} else if(requestedVersion != CommonBlob::version_none) {
common = new KeychainDbCommon(session, ident, requestedVersion);
} else {
common = new KeychainDbCommon(session, ident);
}
secinfo("kccommon", "made a new common for %s at %p", ident.dbName(), common.get());
common->insertHoldingLock();
}
common->initializeKeybag();
return false;
}
KeychainDatabase::KeychainDatabase(KeychainDatabase &src, Process &proc, DbHandle dbToClone)
: LocalDatabase(proc), mValidData(false), mSecret(Allocator::standard(Allocator::sensitive)), mSaveSecret(false), version(0), mBlob(NULL), mRecoded(false)
{
mCred = DataWalkers::copy(src.mCred, Allocator::standard());
std::string newDbName = std::string("////") + std::string(src.identifier().dbName());
DLDbIdentifier newDLDbIdent(src.identifier().dlDbIdentifier().ssuid(), newDbName.c_str(), src.identifier().dlDbIdentifier().dbLocation());
DbIdentifier ident(newDLDbIdent, src.identifier());
RefPointer<KeychainDbCommon> newCommon = new KeychainDbCommon(proc.session(), ident);
newCommon->initializeKeybag();
StLock<Mutex> _(*newCommon);
parent(*newCommon);
newCommon->insert();
common().mParams = src.common().mParams;
src.unlockDb(false); common().setup(src.blob(), src.common().masterKey());
RefPointer<KeychainDatabase> srcKC = Server::keychain(dbToClone);
common().importSecrets(srcKC->common());
CssmData pubAcl, privAcl;
src.acl().exportBlob(pubAcl, privAcl);
importBlob(pubAcl.data(), privAcl.data());
src.acl().allocator.free(pubAcl);
src.acl().allocator.free(privAcl);
mRecodingSource = &src;
common().setUnlocked();
mValidData = true;
encode();
proc.addReference(*this);
secinfo("SSdb", "database %s(%p) created as copy, common at %p",
common().dbName(), this, &common());
}
KeychainDatabase::KeychainDatabase(const DLDbIdentifier& id, KeychainDatabase &src, Process &proc)
: LocalDatabase(proc), mValidData(false), mSecret(Allocator::standard(Allocator::sensitive)), mSaveSecret(false), version(0), mBlob(NULL), mRecoded(false)
{
mCred = DataWalkers::copy(src.mCred, Allocator::standard());
DbIdentifier ident(id, src.identifier());
RefPointer<KeychainDbCommon> newCommon;
if(KeychainDbCommon::find(ident, process().session(), newCommon, CommonBlob::version_none, &src.common())) {
secinfo("kccommon", "Found common where we didn't expect. Possible strange behavior ahead.");
newCommon->cloneFrom(src.common());
}
StLock<Mutex> _(*newCommon);
parent(*newCommon);
common().mParams = src.common().mParams;
CssmData pubAcl, privAcl;
src.acl().exportBlob(pubAcl, privAcl);
importBlob(pubAcl.data(), privAcl.data());
src.acl().allocator.free(pubAcl);
src.acl().allocator.free(privAcl);
if(src.mBlob) {
mBlob = src.mBlob->copy();
version = src.version;
}
mValidData = src.mValidData;
proc.addReference(*this);
secinfo("SSdb", "database %s(%p) created as expected clone, common at %p", common().dbName(), this, &common());
}
KeychainDatabase::KeychainDatabase(uint32 requestedVersion, KeychainDatabase &src, Process &proc)
: LocalDatabase(proc), mValidData(false), mSecret(Allocator::standard(Allocator::sensitive)), mSaveSecret(false), version(0), mBlob(NULL), mRecoded(false)
{
mCred = DataWalkers::copy(src.mCred, Allocator::standard());
std::string newDbName = std::string("////") + std::string(src.identifier().dbName()) + std::string("_com.apple.security.keychain.migrating");
DLDbIdentifier newDLDbIdent(src.identifier().dlDbIdentifier().ssuid(), newDbName.c_str(), src.identifier().dlDbIdentifier().dbLocation());
DbIdentifier ident(newDLDbIdent, src.identifier());
StLock<Mutex> __(src.common());
RefPointer<KeychainDbCommon> newCommon;
if(KeychainDbCommon::find(ident, process().session(), newCommon, requestedVersion)) {
secinfo("kccommon", "Found common where we didn't expect. Possible strange behavior ahead.");
newCommon->cloneFrom(src.common(), requestedVersion);
}
newCommon->initializeKeybag();
StLock<Mutex> _(*newCommon);
parent(*newCommon);
src.unlockDb(false);
common().setup(src.blob(), src.common().masterKey(), false);
common().mParams = src.common().mParams;
common().makeNewSecrets();
CssmData pubAcl, privAcl;
src.acl().exportBlob(pubAcl, privAcl);
importBlob(pubAcl.data(), privAcl.data());
src.acl().allocator.free(pubAcl);
src.acl().allocator.free(privAcl);
mRecodingSource = &src;
common().setUnlocked();
mValidData = true;
encode();
proc.addReference(*this);
secinfo("SSdb", "database %s(%p) created as expected copy, common at %p",
common().dbName(), this, &common());
}
KeychainDatabase::~KeychainDatabase()
{
secinfo("KCdb", "deleting database %s(%p) common %p",
common().dbName(), this, &common());
Allocator::standard().free(mCred);
Allocator::standard().free(mBlob);
}
KeychainDbCommon &KeychainDatabase::common() const
{
return parent<KeychainDbCommon>();
}
const char *KeychainDatabase::dbName() const
{
return common().dbName();
}
bool KeychainDatabase::transient() const
{
return false; }
AclKind KeychainDatabase::aclKind() const
{
return dbAcl;
}
Database *KeychainDatabase::relatedDatabase()
{
return this;
}
void KeychainDatabase::authenticate(CSSM_DB_ACCESS_TYPE mode,
const AccessCredentials *cred)
{
StLock<Mutex> _(common());
switch (mode) {
case CSSM_DB_ACCESS_RESET:
secinfo("KCdb", "%p ACCESS_RESET triggers keychain lock", this);
common().lockDb();
break;
default:
secinfo("KCdb", "%p authenticate stores new database credentials", this);
AccessCredentials *newCred = DataWalkers::copy(cred, Allocator::standard());
Allocator::standard().free(mCred);
mCred = newCred;
}
}
RefPointer<Key> KeychainDatabase::makeKey(Database &db, const CssmKey &newKey,
uint32 moreAttributes, const AclEntryPrototype *owner)
{
if (moreAttributes & CSSM_KEYATTR_PERMANENT)
return new KeychainKey(db, newKey, moreAttributes, owner);
else
return process().makeTemporaryKey(newKey, moreAttributes, owner);
}
RefPointer<Key> KeychainDatabase::makeKey(const CssmKey &newKey,
uint32 moreAttributes, const AclEntryPrototype *owner)
{
return makeKey(*this, newKey, moreAttributes, owner);
}
DbBlob *KeychainDatabase::blob()
{
StLock<Mutex> _(common());
if (!validBlob()) {
makeUnlocked(false); encode(); }
activity(); assert(validBlob()); return mBlob;
}
void KeychainDatabase::encode()
{
DbBlob *blob = common().encode(*this);
Allocator::standard().free(mBlob);
mBlob = blob;
version = common().version;
secinfo("KCdb", "encoded database %p common %p(%s) version %u params=(%u,%u)",
this, &common(), dbName(), version,
common().mParams.idleTimeout, common().mParams.lockOnSleep);
}
void KeychainDatabase::changePassphrase(const AccessCredentials *cred)
{
StLock<Mutex> _(common());
if (common().isLoginKeychain()) mSaveSecret = true;
makeUnlocked(cred, false);
if(!establishNewSecrets(cred, SecurityAgent::changePassphrase)) {
secinfo("KCdb", "Old and new passphrases are the same. Database %s(%p) master secret did not change.",
common().dbName(), this);
return;
}
if (mSecret) { mSecret.reset(); }
mSaveSecret = false;
common().invalidateBlob(); secinfo("KCdb", "Database %s(%p) master secret changed", common().dbName(), this);
encode();
notify(kNotificationEventPassphraseChanged);
activity();
}
void KeychainDatabase::commitSecretsForSync(KeychainDatabase &cloneDb)
{
StLock<Mutex> _(common());
if (cloneDb.mRecodingSource != this)
CssmError::throwMe(CSSM_ERRCODE_INVALID_DB_HANDLE);
makeUnlocked(false); cloneDb.unlockDb(false);
std::vector<U32HandleObject::Handle> handleList;
U32HandleObject::findAllRefs<KeychainKey>(handleList);
size_t count = handleList.size();
if (count > 0) {
for (unsigned int n = 0; n < count; ++n) {
RefPointer<KeychainKey> kckey =
U32HandleObject::findRefAndLock<KeychainKey>(handleList[n], CSSMERR_CSP_INVALID_KEY_REFERENCE);
StLock<Mutex> _(*kckey);
if (kckey->database().global().identifier() == identifier()) {
kckey->key(); kckey->invalidateBlob();
secinfo("kcrecode", "changed extant key %p (proc %d)",
&*kckey, kckey->process().pid());
}
}
}
mRecoded = true;
common().importSecrets(cloneDb.common());
common().invalidateBlob();
}
RefPointer<Key> KeychainDatabase::extractMasterKey(Database &db,
const AccessCredentials *cred, const AclEntryPrototype *owner,
uint32 usage, uint32 attrs)
{
StLock<Mutex> _(common());
lockDb();
makeUnlocked(false);
CssmClient::WrapKey wrap(Server::csp(), CSSM_ALGID_NONE);
CssmKey key;
wrap(common().masterKey(), key);
return makeKey(db, key, attrs & LocalKey::managedAttributes, owner);
}
void KeychainDatabase::unlockDb(bool unlockKeybag)
{
StLock<Mutex> _(common());
makeUnlocked(unlockKeybag);
}
void KeychainDatabase::makeUnlocked(bool unlockKeybag)
{
return makeUnlocked(mCred, unlockKeybag);
}
void KeychainDatabase::makeUnlocked(const AccessCredentials *cred, bool unlockKeybag)
{
if (isLocked()) {
secnotice("KCdb", "%p(%p) unlocking for makeUnlocked()", this, &common());
assert(mBlob || (mValidData && common().hasMaster()));
establishOldSecrets(cred);
common().setUnlocked(); if (common().isLoginKeychain()) {
CssmKey master = common().masterKey();
CssmKey rawMaster;
CssmClient::WrapKey wrap(Server::csp(), CSSM_ALGID_NONE);
wrap(master, rawMaster);
service_context_t context = common().session().get_current_service_context();
service_client_stash_load_key(&context, rawMaster.keyData(), (int)rawMaster.length());
}
} else if (unlockKeybag && common().isLoginKeychain()) {
bool locked = false;
service_context_t context = common().session().get_current_service_context();
if ((service_client_kb_is_locked(&context, &locked, NULL) == 0) && locked) {
if (!unlock_keybag_with_cred(*this, cred)) {
syslog(LOG_NOTICE, "failed to unlock iCloud keychain");
}
}
}
if (!mValidData) { secnotice("KCdb", "%p(%p) is unlocked; decoding for makeUnlocked()", this, &common());
if (!decode())
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
assert(!isLocked());
assert(mValidData);
}
void KeychainDatabase::stashDbCheck()
{
CssmAutoData masterKey(Allocator::standard(Allocator::sensitive));
CssmAutoData encKey(Allocator::standard(Allocator::sensitive));
int rc = 0;
void * stash_key = NULL;
int stash_key_len = 0;
service_context_t context = common().session().get_current_service_context();
rc = service_client_stash_get_key(&context, &stash_key, &stash_key_len);
if (rc == 0) {
if (stash_key) {
masterKey.copy(CssmData((void *)stash_key,stash_key_len));
memset(stash_key, 0, stash_key_len);
free(stash_key);
}
} else {
secnotice("KCdb", "failed to get stash from securityd_service: %d", (int)rc);
CssmError::throwMe(rc);
}
{
StLock<Mutex> _(common());
CssmClient::Key key(Server::csp(), masterKey.get());
CssmKey::Header &hdr = key.header();
hdr.keyClass(CSSM_KEYCLASS_SESSION_KEY);
hdr.algorithm(CSSM_ALGID_3DES_3KEY_EDE);
hdr.usage(CSSM_KEYUSE_ANY);
hdr.blobType(CSSM_KEYBLOB_RAW);
hdr.blobFormat(CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING);
common().setup(mBlob, key);
if (!decode())
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
common().get_encryption_key(encKey);
}
if (service_client_kb_load(&context) == KB_BagNotFound) {
service_client_kb_create(&context, encKey.data(), (int)encKey.length());
}
}
void KeychainDatabase::stashDb()
{
CssmAutoData data(Allocator::standard(Allocator::sensitive));
{
StLock<Mutex> _(common());
if (!common().isValid()) {
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
}
CssmKey key = common().masterKey();
data.copy(key.keyData());
}
service_context_t context = common().session().get_current_service_context();
int rc = service_client_stash_set_key(&context, data.data(), (int)data.length());
if (rc != 0) CssmError::throwMe(rc);
}
void KeychainDatabase::unlockDb(const CssmData &passphrase, bool unlockKeybag)
{
StLock<Mutex> _(common());
makeUnlocked(passphrase, unlockKeybag);
}
void KeychainDatabase::makeUnlocked(const CssmData &passphrase, bool unlockKeybag)
{
if (isLocked()) {
if (decode(passphrase))
return;
else
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
} else if (!mValidData) { if (!decode())
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
if (unlockKeybag && common().isLoginKeychain()) {
bool locked = false;
service_context_t context = common().session().get_current_service_context();
if (!common().session().keybagGetState(session_keybag_check_master_key) || ((service_client_kb_is_locked(&context, &locked, NULL) == 0) && locked)) {
unlock_keybag(*this, passphrase.data(), (int)passphrase.length());
}
}
assert(!isLocked());
assert(mValidData);
}
bool KeychainDatabase::decode(const CssmData &passphrase)
{
assert(mBlob);
common().setup(mBlob, passphrase);
bool success = decode();
if (success && common().isLoginKeychain()) {
unlock_keybag(*this, passphrase.data(), (int)passphrase.length());
}
return success;
}
bool KeychainDatabase::decode()
{
assert(mBlob);
assert(common().hasMaster());
void *privateAclBlob;
if (common().unlockDb(mBlob, &privateAclBlob)) {
if (!mValidData) {
acl().importBlob(mBlob->publicAclBlob(), privateAclBlob);
mValidData = true;
}
Allocator::standard().free(privateAclBlob);
return true;
}
secinfo("KCdb", "%p decode failed", this);
return false;
}
void KeychainDatabase::establishOldSecrets(const AccessCredentials *creds)
{
bool forSystem = this->belongsToSystem();
if (forSystem) {
SystemKeychainKey systemKeychain(kSystemUnlockFile);
if (systemKeychain.matches(mBlob->randomSignature)) {
secinfo("KCdb", "%p attempting system unlock", this);
common().setup(mBlob, CssmClient::Key(Server::csp(), systemKeychain.key(), true));
if (decode())
return;
}
}
list<CssmSample> samples;
if (creds && creds->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, samples)) {
for (list<CssmSample>::iterator it = samples.begin(); it != samples.end(); it++) {
TypedList &sample = *it;
sample.checkProper();
switch (sample.type()) {
case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT:
if (!forSystem) {
if (interactiveUnlock())
return;
}
break;
case CSSM_SAMPLE_TYPE_PASSWORD:
if (sample.length() != 2)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
secinfo("KCdb", "%p attempting passphrase unlock", this);
if (decode(sample[1]))
return;
break;
case CSSM_SAMPLE_TYPE_SYMMETRIC_KEY:
case CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY:
assert(mBlob);
secinfo("KCdb", "%p attempting explicit key unlock", this);
common().setup(mBlob, keyFromCreds(sample, 4));
if (decode()) {
return;
}
break;
case CSSM_SAMPLE_TYPE_KEYBAG_KEY:
assert(mBlob);
secinfo("KCdb", "%p attempting keybag key unlock", this);
common().setup(mBlob, keyFromKeybag(sample));
if (decode()) {
return;
}
break;
case CSSM_WORDID_CANCELED:
secinfo("KCdb", "%p defeat default action", this);
break;
default:
secinfo("KCdb", "%p unknown sub-sample unlock (%d) ignored", this, sample.type());
break;
}
}
} else {
assert(mBlob);
if (!forSystem) {
if (interactiveUnlock())
return;
}
}
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
bool KeychainDatabase::checkCredentials(const AccessCredentials *creds) {
list<CssmSample> samples;
if (creds && creds->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, samples)) {
for (list<CssmSample>::iterator it = samples.begin(); it != samples.end(); it++) {
TypedList &sample = *it;
sample.checkProper();
switch (sample.type()) {
case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT:
secinfo("integrity", "%p ignoring keychain prompt", this);
break;
case CSSM_SAMPLE_TYPE_PASSWORD:
if (sample.length() != 2)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
secinfo("integrity", "%p checking passphrase", this);
if(validatePassphrase(sample[1])) {
return true;
}
break;
case CSSM_SAMPLE_TYPE_SYMMETRIC_KEY:
case CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY:
assert(mBlob);
secinfo("integrity", "%p attempting explicit key unlock", this);
try {
CssmClient::Key checkKey = keyFromCreds(sample, 4);
if(common().validateKey(checkKey)) {
return true;
}
} catch(...) {
secinfo("integrity", "%p caught error", this);
}
break;
}
}
}
return false;
}
uint32_t KeychainDatabase::interactiveUnlockAttempts = 0;
bool KeychainDatabase::interactiveUnlock()
{
secinfo("KCdb", "%p attempting interactive unlock", this);
interactiveUnlockAttempts++;
SecurityAgent::Reason reason = SecurityAgent::noReason;
QueryUnlock query(*this);
StSyncLock<Mutex, Mutex> uisync(common().uiLock(), common());
if (isLocked()) {
query.inferHints(Server::process());
reason = query();
if (mSaveSecret && reason == SecurityAgent::noReason) {
query.retrievePassword(mSecret);
}
query.disconnect();
} else {
secinfo("KCdb", "%p was unlocked during uiLock delay", this);
}
if (common().isLoginKeychain()) {
bool locked = false;
service_context_t context = common().session().get_current_service_context();
if ((service_client_kb_is_locked(&context, &locked, NULL) == 0) && locked) {
QueryKeybagNewPassphrase keybagQuery(common().session());
keybagQuery.inferHints(Server::process());
CssmAutoData pass(Allocator::standard(Allocator::sensitive));
CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
SecurityAgent::Reason queryReason = keybagQuery.query(oldPass, pass);
if (queryReason == SecurityAgent::noReason) {
service_client_kb_change_secret(&context, oldPass.data(), (int)oldPass.length(), pass.data(), (int)pass.length());
} else if (queryReason == SecurityAgent::resettingPassword) {
query.retrievePassword(pass);
service_client_kb_reset(&context, pass.data(), (int)pass.length());
}
}
}
return reason == SecurityAgent::noReason;
}
uint32_t KeychainDatabase::getInteractiveUnlockAttempts() {
if (csr_check(CSR_ALLOW_APPLE_INTERNAL)) {
return 0;
} else {
return interactiveUnlockAttempts;
}
}
bool KeychainDatabase::establishNewSecrets(const AccessCredentials *creds, SecurityAgent::Reason reason)
{
list<CssmSample> samples;
if (creds && creds->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, samples)) {
for (list<CssmSample>::iterator it = samples.begin(); it != samples.end(); it++) {
TypedList &sample = *it;
sample.checkProper();
switch (sample.type()) {
case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT:
{
secinfo("KCdb", "%p specified interactive passphrase", this);
QueryNewPassphrase query(*this, reason);
StSyncLock<Mutex, Mutex> uisync(common().uiLock(), common());
query.inferHints(Server::process());
CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
if (query(oldPassphrase, passphrase) == SecurityAgent::noReason) {
common().setup(NULL, passphrase);
change_secret_on_keybag(*this, oldPassphrase.data(), (int)oldPassphrase.length(), passphrase.data(), (int)passphrase.length());
return true;
}
}
break;
case CSSM_SAMPLE_TYPE_PASSWORD:
{
secinfo("KCdb", "%p specified explicit passphrase", this);
if (sample.length() != 2)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
if (common().isLoginKeychain()) {
CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
list<CssmSample> oldSamples;
creds->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, oldSamples);
for (list<CssmSample>::iterator oit = oldSamples.begin(); oit != oldSamples.end(); oit++) {
TypedList &tmpList = *oit;
tmpList.checkProper();
if (tmpList.type() == CSSM_SAMPLE_TYPE_PASSWORD) {
if (tmpList.length() == 2) {
oldPassphrase = tmpList[1].data();
}
}
}
if (!oldPassphrase.length() && mSecret && mSecret.length()) {
oldPassphrase = mSecret;
}
if ((oldPassphrase.length() == sample[1].data().length()) &&
!memcmp(oldPassphrase.data(), sample[1].data().data(), oldPassphrase.length()) &&
oldPassphrase.length()) {
return false;
}
common().setup(NULL, sample[1]);
change_secret_on_keybag(*this, oldPassphrase.data(), (int)oldPassphrase.length(), sample[1].data().data(), (int)sample[1].data().length());
}
else {
common().setup(NULL, sample[1]);
}
return true;
}
case CSSM_WORDID_SYMMETRIC_KEY:
case CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY:
secinfo("KCdb", "%p specified explicit master key", this);
common().setup(NULL, keyFromCreds(sample, 3));
return true;
case CSSM_WORDID_CANCELED:
secinfo("KCdb", "%p defeat default action", this);
break;
default:
secinfo("KCdb", "%p unknown sub-sample acquisition (%d) ignored",
this, sample.type());
break;
}
}
} else {
QueryNewPassphrase query(*this, reason);
StSyncLock<Mutex, Mutex> uisync(common().uiLock(), common());
query.inferHints(Server::process());
CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
if (query(oldPassphrase, passphrase) == SecurityAgent::noReason) {
common().setup(NULL, passphrase);
change_secret_on_keybag(*this, oldPassphrase.data(), (int)oldPassphrase.length(), passphrase.data(), (int)passphrase.length());
return true;
}
}
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
CssmClient::Key KeychainDatabase::keyFromCreds(const TypedList &sample, unsigned int requiredLength)
{
assert(sample.type() == CSSM_SAMPLE_TYPE_SYMMETRIC_KEY || sample.type() == CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY);
if (sample.length() != requiredLength
|| sample[1].type() != CSSM_LIST_ELEMENT_DATUM
|| sample[2].type() != CSSM_LIST_ELEMENT_DATUM
|| (requiredLength == 4 && sample[3].type() != CSSM_LIST_ELEMENT_DATUM))
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
KeyHandle &handle = *sample[1].data().interpretedAs<KeyHandle>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
if (sample[2].data().data() == NULL)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
CssmKey &key = *sample[2].data().interpretedAs<CssmKey>();
if (key.header().cspGuid() == gGuidAppleCSPDL) {
return safer_cast<LocalKey &>(*Server::key(handle));
} else
if (sample.type() == CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY) {
KeyHandle keyhandle = *sample[1].data().interpretedAs<KeyHandle>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
CssmData &flattenedKey = sample[3].data();
RefPointer<Key> unwrappingKey = Server::key(keyhandle);
Database &db=unwrappingKey->database();
CssmKey rawWrappedKey;
unflattenKey(flattenedKey, rawWrappedKey);
RefPointer<Key> masterKey;
CssmData emptyDescriptiveData;
const AccessCredentials *cred = NULL;
const AclEntryPrototype *owner = NULL;
CSSM_KEYUSE usage = CSSM_KEYUSE_ANY;
CSSM_KEYATTR_FLAGS attrs = CSSM_KEYATTR_EXTRACTABLE;
Allocator &alloc = Allocator::standard();
AutoCredentials promptCred(alloc, 3);
promptCred.sample(0) = TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT);
promptCred.sample(1) = TypedList(alloc, CSSM_SAMPLE_TYPE_THRESHOLD,
new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT)));
promptCred.sample(2) = TypedList(alloc, CSSM_SAMPLE_TYPE_PROMPTED_PASSWORD,
new(alloc) ListElement(alloc, CssmData()));
CssmClient::UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE); unwrap.mode(CSSM_ALGMODE_NONE);
unwrap.padding(CSSM_PADDING_PKCS1);
unwrap.cred(promptCred);
unwrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7));
Security::Context *tmpContext;
CSSM_CC_HANDLE CCHandle = unwrap.handle();
CSSM_GetContext (CCHandle, (CSSM_CONTEXT_PTR *)&tmpContext);
tmpContext->ContextType = CSSM_ALGCLASS_ASYMMETRIC;
tmpContext->AlgorithmType = CSSM_ALGID_RSA;
db.unwrapKey(*tmpContext, cred, owner, unwrappingKey, NULL, usage, attrs,
rawWrappedKey, masterKey, emptyDescriptiveData);
Allocator::standard().free(rawWrappedKey.KeyData.Data);
return safer_cast<LocalKey &>(*masterKey).key();
}
else if (sample.type() == CSSM_SAMPLE_TYPE_SYMMETRIC_KEY && sample.length() == 4 && sample[3].data().length() > 0) {
CssmData &flattenedKey = sample[3].data();
unflattenKey(flattenedKey, key);
if (key.header().blobType() != CSSM_KEYBLOB_RAW) {
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);
}
if (key.header().keyClass() != CSSM_KEYCLASS_SESSION_KEY) {
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
}
return CssmClient::Key(Server::csp(), key, true);
} else {
if (key.header().blobType() != CSSM_KEYBLOB_RAW)
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);
if (key.header().keyClass() != CSSM_KEYCLASS_SESSION_KEY)
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
return CssmClient::Key(Server::csp(), key, true);
}
}
void unflattenKey(const CssmData &flatKey, CssmKey &rawKey)
{
memcpy(&rawKey.KeyHeader, flatKey.Data, sizeof(CSSM_KEYHEADER));
memcpy(&rawKey.KeyData, flatKey.Data + sizeof(CSSM_KEYHEADER), sizeof(CSSM_DATA));
size_t keyDataLength = flatKey.length() - sizeof(CSSM_KEY);
rawKey.KeyData.Data = Allocator::standard().malloc<uint8>(keyDataLength);
rawKey.KeyData.Length = keyDataLength;
memcpy(rawKey.KeyData.Data, flatKey.Data + sizeof(CSSM_KEY), keyDataLength);
Security::n2hi(rawKey.KeyHeader); }
CssmClient::Key
KeychainDatabase::keyFromKeybag(const TypedList &sample)
{
service_context_t context;
uint8_t *session_key;
int session_key_size;
int rc;
const struct ccmode_siv *mode = ccaes_siv_decrypt_mode();
const size_t session_key_wrapped_len = 40;
const size_t version_len = 1, nonce_len = 16;
uint8_t *decrypted_data;
size_t decrypted_len;
assert(sample.type() == CSSM_SAMPLE_TYPE_KEYBAG_KEY);
CssmData &unlock_token = sample[2].data();
context = common().session().get_current_service_context();
rc = service_client_kb_unwrap_key(&context, unlock_token.data(), session_key_wrapped_len, key_class_ak, (void **)&session_key, &session_key_size);
if (rc != 0) {
CssmError::throwMe(CSSM_ERRCODE_INVALID_CRYPTO_DATA);
}
uint8_t *indata = (uint8_t *)unlock_token.data() + session_key_wrapped_len;
size_t inlen = unlock_token.length() - session_key_wrapped_len;
decrypted_len = ccsiv_plaintext_size(mode, inlen - (version_len + nonce_len));
decrypted_data = (uint8_t *)calloc(1, decrypted_len);
ccsiv_ctx_decl(mode->size, ctx);
rc = ccsiv_init(mode, ctx, session_key_size, session_key);
if (rc != 0) CssmError::throwMe(CSSM_ERRCODE_INVALID_CRYPTO_DATA);
rc = ccsiv_set_nonce(mode, ctx, nonce_len, indata + version_len);
if (rc != 0) CssmError::throwMe(CSSM_ERRCODE_INVALID_CRYPTO_DATA);
rc = ccsiv_aad(mode, ctx, 1, indata);
if (rc != 0) CssmError::throwMe(CSSM_ERRCODE_INVALID_CRYPTO_DATA);
rc = ccsiv_crypt(mode, ctx, inlen - (version_len + nonce_len), indata + version_len + nonce_len, decrypted_data);
if (rc != 0) CssmError::throwMe(CSSM_ERRCODE_INVALID_CRYPTO_DATA);
ccsiv_ctx_clear(mode->size, ctx);
free(session_key);
return makeRawKey(decrypted_data, decrypted_len, CSSM_ALGID_3DES_3KEY_EDE, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT);
}
CssmClient::Key KeychainDatabase::makeRawKey(void *data, size_t length,
CSSM_ALGORITHMS algid, CSSM_KEYUSE usage)
{
CssmKey key;
key.header().BlobType = CSSM_KEYBLOB_RAW;
key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
key.header().AlgorithmId = algid;
key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
key.header().KeyUsage = usage;
key.header().KeyAttr = 0;
key.KeyData = CssmData(data, length);
CssmClient::UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE);
CssmKey unwrappedKey;
CssmData descriptiveData;
unwrap(key,
CssmClient::KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE),
unwrappedKey, &descriptiveData, NULL);
return CssmClient::Key(Server::csp(), unwrappedKey);
}
bool KeychainDatabase::validatePassphrase(const CssmData &passphrase) const
{
if (common().hasMaster()) {
return common().validatePassphrase(passphrase);
} else {
try {
DatabaseCryptoCore test;
test.setup(mBlob, passphrase);
test.decodeCore(mBlob, NULL);
return true;
} catch (...) {
return false;
}
}
}
void KeychainDatabase::lockDb()
{
common().lockDb();
}
KeyBlob *KeychainDatabase::encodeKey(const CssmKey &key, const CssmData &pubAcl, const CssmData &privAcl)
{
bool inTheClear = false;
if((key.keyClass() == CSSM_KEYCLASS_PUBLIC_KEY) &&
!(key.attribute(CSSM_KEYATTR_PUBLIC_KEY_ENCRYPT))) {
inTheClear = true;
}
StLock<Mutex> _(common());
if(!inTheClear)
makeUnlocked(false);
return common().encodeKeyCore(key, pubAcl, privAcl, inTheClear);
}
void KeychainDatabase::decodeKey(KeyBlob *blob, CssmKey &key, void * &pubAcl, void * &privAcl)
{
StLock<Mutex> _(common());
if(!blob->isClearText())
makeUnlocked(false);
common().decodeKeyCore(blob, key, pubAcl, privAcl);
activity();
}
KeyBlob *KeychainDatabase::recodeKey(KeychainKey &oldKey)
{
if (mRecodingSource != &oldKey.referent<KeychainDatabase>()) {
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
}
StLock<Mutex> _ (oldKey.referent<KeychainDatabase>().common());
StLock<Mutex> __(common());
oldKey.instantiateAcl(); CssmData publicAcl, privateAcl;
oldKey.exportBlob(publicAcl, privateAcl);
bool inTheClear = false;
assert(oldKey.blob());
if(oldKey.blob() && oldKey.blob()->isClearText()) {
inTheClear = true;
}
KeyBlob *blob = common().encodeKeyCore(oldKey.cssmKey(), publicAcl, privateAcl, inTheClear);
oldKey.acl().allocator.free(publicAcl);
oldKey.acl().allocator.free(privateAcl);
return blob;
}
void KeychainDatabase::setParameters(const DBParameters ¶ms)
{
StLock<Mutex> _(common());
makeUnlocked(false);
common().mParams = params;
common().invalidateBlob(); activity(); secinfo("KCdb", "%p common %p(%s) set params=(%u,%u)",
this, &common(), dbName(), params.idleTimeout, params.lockOnSleep);
}
void KeychainDatabase::getParameters(DBParameters ¶ms)
{
StLock<Mutex> _(common());
makeUnlocked(false);
params = common().mParams;
}
SecurityServerAcl &KeychainDatabase::acl()
{
return *this;
}
void KeychainDatabase::instantiateAcl()
{
StLock<Mutex> _(common());
makeUnlocked(false);
}
void KeychainDatabase::changedAcl()
{
StLock<Mutex> _(common());
version = 0;
}
void KeychainDatabase::validateBlob(const DbBlob *blob)
{
assert(blob);
blob->validate(CSSMERR_APPLEDL_INVALID_DATABASE_BLOB);
switch (blob->version()) {
#if defined(COMPAT_OSX_10_0)
case DbBlob::version_MacOS_10_0:
break;
#endif
case DbBlob::version_MacOS_10_1:
break;
case DbBlob::version_partition:
break;
default:
CssmError::throwMe(CSSMERR_APPLEDL_INCOMPATIBLE_DATABASE_BLOB);
}
}
bool KeychainDatabase::isRecoding()
{
secnotice("integrity", "recoding source: %p", mRecodingSource.get());
return (mRecodingSource.get() != NULL || mRecoded);
}
void KeychainDatabase::recodeFinished()
{
secnotice("integrity", "recoding finished");
mRecodingSource = NULL;
mRecoded = false;
}
#if defined(DEBUGDUMP)
void KeychainDbCommon::dumpNode()
{
PerSession::dumpNode();
uint32 sig; memcpy(&sig, &mIdentifier.signature(), sizeof(sig));
Debug::dump(" %s[%8.8x]", mIdentifier.dbName(), sig);
if (isLocked()) {
Debug::dump(" locked");
} else {
time_t whenTime = time_t(when());
Debug::dump(" unlocked(%24.24s/%.2g)", ctime(&whenTime),
(when() - Time::now()).seconds());
}
Debug::dump(" params=(%u,%u)", mParams.idleTimeout, mParams.lockOnSleep);
}
void KeychainDatabase::dumpNode()
{
PerProcess::dumpNode();
Debug::dump(" %s vers=%u",
mValidData ? " data" : " nodata", version);
if (mBlob) {
uint32 sig; memcpy(&sig, &mBlob->randomSignature, sizeof(sig));
Debug::dump(" blob=%p[%8.8x]", mBlob, sig);
} else {
Debug::dump(" noblob");
}
}
#endif //DEBUGDUMP
KeychainDbCommon::KeychainDbCommon(Session &ssn, const DbIdentifier &id, uint32 requestedVersion)
: LocalDbCommon(ssn), DatabaseCryptoCore(requestedVersion), sequence(0), version(1), mIdentifier(id),
mIsLocked(true), mValidParams(false), mLoginKeychain(false)
{
{
Server &server = Server::active();
StLock<Mutex> _(server);
if (KeychainDbGlobal *dbglobal =
server.findFirst<KeychainDbGlobal, const DbIdentifier &>(&KeychainDbGlobal::identifier, identifier())) {
parent(*dbglobal);
secinfo("KCdb", "%p linking to existing DbGlobal %p", this, dbglobal);
} else {
parent(*new KeychainDbGlobal(identifier()));
secinfo("KCdb", "%p linking to new DbGlobal %p", this, &global());
}
session().addReference(*this);
if (strcasestr(id.dbName(), "login.keychain") != NULL) {
mLoginKeychain = true;
}
}
}
void KeychainDbCommon::initializeKeybag() {
if (mLoginKeychain && !session().keybagGetState(session_keybag_loaded)) {
service_context_t context = session().get_current_service_context();
if (service_client_kb_load(&context) == 0) {
session().keybagSetState(session_keybag_loaded);
}
}
}
KeychainDbCommon::KeychainDbCommon(Session &ssn, const DbIdentifier &id, KeychainDbCommon& toClone)
: LocalDbCommon(ssn), DatabaseCryptoCore(toClone.mBlobVersion), sequence(toClone.sequence), mParams(toClone.mParams), version(toClone.version),
mIdentifier(id), mIsLocked(toClone.mIsLocked), mValidParams(toClone.mValidParams), mLoginKeychain(toClone.mLoginKeychain)
{
cloneFrom(toClone);
{
Server &server = Server::active();
StLock<Mutex> _(server);
if (KeychainDbGlobal *dbglobal =
server.findFirst<KeychainDbGlobal, const DbIdentifier &>(&KeychainDbGlobal::identifier, identifier())) {
parent(*dbglobal);
secinfo("KCdb", "%p linking to existing DbGlobal %p", this, dbglobal);
} else {
parent(*new KeychainDbGlobal(identifier()));
secinfo("KCdb", "%p linking to new DbGlobal %p", this, &global());
}
session().addReference(*this);
}
}
KeychainDbCommon::~KeychainDbCommon()
{
secinfo("KCdb", "releasing keychain %p %s", this, (char*)this->dbName());
Server::active().clearTimer(this);
if (mLoginKeychain) {
session().keybagClearState(session_keybag_unlocked);
}
kill();
}
void KeychainDbCommon::cloneFrom(KeychainDbCommon& toClone, uint32 requestedVersion) {
sequence = toClone.sequence;
mParams = toClone.mParams;
version = toClone.version;
mIsLocked = toClone.mIsLocked;
mValidParams = toClone.mValidParams;
mLoginKeychain = toClone.mLoginKeychain;
DatabaseCryptoCore::initializeFrom(toClone, requestedVersion);
}
void KeychainDbCommon::kill()
{
StReadWriteLock _(mRWCommonLock, StReadWriteLock::Write);
mCommonSet.erase(this);
}
KeychainDbGlobal &KeychainDbCommon::global() const
{
return parent<KeychainDbGlobal>();
}
void KeychainDbCommon::select()
{ this->ref(); }
void KeychainDbCommon::unselect()
{ this->unref(); }
void KeychainDbCommon::makeNewSecrets()
{
assert(hasMaster());
DatabaseCryptoCore::generateNewSecrets();
setUnlocked();
}
bool KeychainDbCommon::unlockDb(DbBlob *blob, void **privateAclBlob)
{
try {
assert(hasMaster());
decodeCore(blob, privateAclBlob);
secinfo("KCdb", "%p unlock successful", this);
} catch (...) {
secinfo("KCdb", "%p unlock failed", this);
return false;
}
if (!mValidParams) {
mParams = blob->params;
n2hi(mParams.idleTimeout);
mValidParams = true; }
bool isLocked = mIsLocked;
setUnlocked();
if (isLocked) {
notify(kNotificationEventUnlocked);
secinfo("KCdb", "unlocking keychain %p %s", this, (char*)this->dbName());
}
return true;
}
void KeychainDbCommon::setUnlocked()
{
session().addReference(*this); mIsLocked = false; activity(); }
void KeychainDbCommon::lockDb()
{
{
StLock<Mutex> _(*this);
if (!isLocked()) {
DatabaseCryptoCore::invalidate();
notify(kNotificationEventLocked);
secinfo("KCdb", "locking keychain %p %s", this, (char*)this->dbName());
Server::active().clearTimer(this);
mIsLocked = true;
session().removeReference(*this);
}
}
}
DbBlob *KeychainDbCommon::encode(KeychainDatabase &db)
{
assert(!isLocked());
CssmData pubAcl, privAcl;
db.acl().exportBlob(pubAcl, privAcl);
DbBlob form;
form.randomSignature = identifier();
form.sequence = sequence;
form.params = mParams;
h2ni(form.params.idleTimeout);
assert(hasMaster());
DbBlob *blob = encodeCore(form, pubAcl, privAcl);
db.acl().allocator.free(pubAcl);
db.acl().allocator.free(privAcl);
return blob;
}
void KeychainDbCommon::action()
{
secinfo("KCdb", "common %s(%p) locked by timer", dbName(), this);
lockDb();
}
void KeychainDbCommon::activity()
{
if (!isLocked()) {
secinfo("KCdb", "setting DbCommon %p timer to %d",
this, int(mParams.idleTimeout));
Server::active().setTimer(this, Time::Interval(int(mParams.idleTimeout)));
}
}
void KeychainDbCommon::sleepProcessing()
{
secinfo("KCdb", "common %s(%p) sleep-lock processing", dbName(), this);
if (mParams.lockOnSleep && !isDefaultSystemKeychain()) {
StLock<Mutex> _(*this);
lockDb();
}
}
void KeychainDbCommon::lockProcessing()
{
lockDb();
}
bool KeychainDbCommon::belongsToSystem() const
{
if (const char *name = this->dbName())
return !strncmp(name, "/Library/Keychains/", 19);
return false;
}
bool KeychainDbCommon::isDefaultSystemKeychain() const
{
if (const char *name = this->dbName())
return !strncmp(name, "/Library/Keychains/System.keychain", 34);
return false;
}
KeychainDbGlobal::KeychainDbGlobal(const DbIdentifier &id)
: mIdentifier(id)
{
}
KeychainDbGlobal::~KeychainDbGlobal()
{
secinfo("KCdb", "DbGlobal %p destroyed", this);
}