#include "agentquery.h"
#include "key.h"
#include "server.h"
+#include "session.h"
#include "cfnotifier.h" // legacy
#include "notifications.h"
#include "SecurityAgentClient.h"
#include <Security/acl_any.h> // for default owner ACLs
-
-
-//
-// The map of database common segments
-//
-Mutex Database::commonLock;
-Database::CommonMap Database::commons;
+#include <Security/wrapkey.h>
+#include <Security/endian.h>
//
DbIdentifier ident(id, newSig);
// create common block and initialize
- common = new Common(ident);
+ CommonMap &commons = proc.session.databases();
+ common = new Common(ident, commons);
StLock<Mutex> _(*common);
- { StLock<Mutex> _(commonLock);
+ { StLock<Mutex> _(commons);
assert(commons.find(ident) == commons.end()); // better be new!
- commons[ident] = common = new Common(ident);
+ commons[ident] = common;
common->useCount++;
}
// new common is now visible but we hold its lock
+
+ // establish the new master secret
+ establishNewSecrets(cred, SecurityAgent::newDatabase);
- // obtain initial passphrase and generate keys
+ // set initial database parameters
common->mParams = params;
- common->setupKeys(cred);
+
+ // we're unlocked now
+ common->makeNewSecrets();
// establish initial ACL
if (owner)
mValidData = true;
// for now, create the blob immediately
- //@@@ this could be deferred, at the cost of some additional
- //@@@ state monitoring. What happens if it locks before we have a blob?
encode();
// register with process
process.addDatabase(this);
- IFDEBUG(debug("SSdb", "database %s(%p) created, common at %p",
- common->dbName(), this, common));
+ secdebug("SSdb", "database %s(%p) created, common at %p",
+ common->dbName(), this, common);
IFDUMPING("SSdb", debugDump("creation complete"));
}
// perform basic validation on the incoming blob
assert(blob);
blob->validate(CSSMERR_APPLEDL_INVALID_DATABASE_BLOB);
- switch (blob->version) {
+ switch (blob->version()) {
#if defined(COMPAT_OSX_10_0)
case blob->version_MacOS_10_0:
break;
// check to see if we already know about this database
DbIdentifier ident(id, blob->randomSignature);
- StLock<Mutex> mapLock(commonLock);
+ CommonMap &commons = proc.session.databases();
+ StLock<Mutex> mapLock(commons);
CommonMap::iterator it = commons.find(ident);
if (it != commons.end()) {
// already there
//@@@ arbitrate sequence number here, perhaps update common->mParams
StLock<Mutex> _(*common); // lock common against other users
common->useCount++;
- IFDEBUG(debug("SSdb",
+ secdebug("SSdb",
"open database %s(%p) version %lx at known common %p(%d)",
- common->dbName(), this, blob->version, common, int(common->useCount)));
+ common->dbName(), this, blob->version(), common, int(common->useCount));
} else {
// newly introduced
- commons[ident] = common = new Common(ident);
+ commons[ident] = common = new Common(ident, commons);
common->mParams = blob->params;
common->useCount++;
- IFDEBUG(debug("SSdb", "open database %s(%p) version %lx with new common %p",
- common->dbName(), this, blob->version, common));
+ secdebug("SSdb", "open database %s(%p) version %lx with new common %p",
+ common->dbName(), this, blob->version(), common);
}
// register with process
//
Database::~Database()
{
- IFDEBUG(debug("SSdb", "deleting database %s(%p) common %p (%d refs)",
- common->dbName(), this, common, int(common->useCount)));
+ secdebug("SSdb", "deleting database %s(%p) common %p (%d refs)",
+ common->dbName(), this, common, int(common->useCount));
IFDUMPING("SSdb", debugDump("deleting database instance"));
process.removeDatabase(this);
CssmAllocator::standard().free(mCred);
+ CssmAllocator::standard().free(mBlob);
// take the commonLock to avoid races against re-use of the common
- StLock<Mutex> __(commonLock);
+ CommonMap &commons = process.session.databases();
+ StLock<Mutex> __(commons);
if (--common->useCount == 0 && common->isLocked()) {
// last use of this database, and it's locked - discard
IFDUMPING("SSdb", debugDump("discarding common"));
void Database::authenticate(const AccessCredentials *cred)
{
StLock<Mutex> _(*common);
+ AccessCredentials *newCred = DataWalkers::copy(cred, CssmAllocator::standard());
CssmAllocator::standard().free(mCred);
- mCred = DataWalkers::copy(cred, CssmAllocator::standard());
+ mCred = newCred;
}
//
-// Encode the current database as a blob.
-// Note that this returns memory we own and keep.
+// Return the database blob, recalculating it as needed.
//
-DbBlob *Database::encode()
+DbBlob *Database::blob()
{
StLock<Mutex> _(*common);
if (!validBlob()) {
- // unlock the database
- makeUnlocked();
-
- // create new up-to-date blob
- DbBlob *blob = common->encode(*this);
- CssmAllocator::standard().free(mBlob);
- mBlob = blob;
- version = common->version;
- debug("SSdb", "encoded database %p(%s) version %ld", this, dbName(), version);
+ makeUnlocked(); // unlock to get master secret
+ encode(); // (re)encode blob if needed
}
- activity();
- assert(mBlob);
+ activity(); // reset timeout
+ assert(validBlob()); // better have a valid blob now...
return mBlob;
}
+//
+// Encode the current database as a blob.
+// Note that this returns memory we own and keep.
+// Caller must hold common lock.
+//
+void Database::encode()
+{
+ DbBlob *blob = common->encode(*this);
+ CssmAllocator::standard().free(mBlob);
+ mBlob = blob;
+ version = common->version;
+ secdebug("SSdb", "encoded database %p common %p(%s) version %ld params=(%ld,%d)",
+ this, common, dbName(), version,
+ common->mParams.idleTimeout, common->mParams.lockOnSleep);
+}
+
+
//
// Change the passphrase on a database
//
void Database::changePassphrase(const AccessCredentials *cred)
{
+ // get and hold the common lock (don't let other threads break in here)
StLock<Mutex> _(*common);
- if (isLocked()) {
- CssmAutoData passphrase(CssmAllocator::standard(CssmAllocator::sensitive));
- if (getBatchPassphrase(cred, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, passphrase)) {
- // incoming sample contained data for unlock
- makeUnlocked(passphrase);
- } else {
- // perform standard unlock
- makeUnlocked();
- }
- } else if (!mValidData) // need to decode to get our ACLs, passphrase available
- decode(common->passphrase);
-
- // get the new passphrase
- // @@@ unstaged version -- revise to filter passphrases
- Process &cltProc = Server::active().connection().process;
- IFDEBUG(debug("SSdb", "New passphrase query from PID %d (UID %d)", cltProc.pid(), cltProc.uid()));
- QueryNewPassphrase query(cltProc.uid(), cltProc.session, *common, SecurityAgent::changePassphrase);
- query(cred, common->passphrase);
+
+ // establish OLD secret - i.e. unlock the database
+ //@@@ do we want to leave the final lock state alone?
+ makeUnlocked(cred);
+
+ // establish NEW secret
+ establishNewSecrets(cred, SecurityAgent::changePassphrase);
common->version++; // blob state changed
- IFDEBUG(debug("SSdb", "Database %s(%p) passphrase changed", common->dbName(), this));
+ secdebug("SSdb", "Database %s(%p) master secret changed", common->dbName(), this);
+ encode(); // force rebuild of local blob
// send out a notification
KeychainNotifier::passphraseChanged(identifier());
- notify(passphraseChangedEvent);
// I guess this counts as an activity
activity();
//
-// Unlock this database (if needed) by obtaining the passphrase in some
-// suitable way and then proceeding to unlock with it. Performs retries
-// where appropriate. Does absolutely nothing if the database is already unlocked.
+// Extract the database master key as a proper Key object.
+//
+Key *Database::extractMasterKey(Database *db,
+ const AccessCredentials *cred, const AclEntryPrototype *owner,
+ uint32 usage, uint32 attrs)
+{
+ // get and hold common lock
+ StLock<Mutex> _(*common);
+
+ // unlock to establish master secret
+ makeUnlocked(cred);
+
+ // extract the raw cryptographic key
+ CssmClient::WrapKey wrap(Server::csp(), CSSM_ALGID_NONE);
+ CssmKey key;
+ wrap(common->masterKey(), key);
+
+ // make the key object and return it
+ return new Key(db, key, attrs & Key::managedAttributes, owner);
+}
+
+
+//
+// Construct a binary blob of moderate size that is suitable for constructing
+// an index identifying this database.
+// We construct this from the database's marker blob, which is created with
+// the database is made, and stays stable thereafter.
+// Note: Ownership of the index blob passes to the caller.
+// @@@ This means that physical copies share this index.
+//
+void Database::getDbIndex(CssmData &indexData)
+{
+ if (!mBlob)
+ encode(); // force blob creation
+ assert(mBlob);
+ CssmData signature = CssmData::wrap(mBlob->randomSignature);
+ indexData = CssmAutoData(CssmAllocator::standard(), signature).release();
+}
+
+
+//
+// Unlock this database (if needed) by obtaining the master secret in some
+// suitable way and then proceeding to unlock with it.
+// Does absolutely nothing if the database is already unlocked.
+// The makeUnlocked forms are identical except the assume the caller already
+// holds the common lock.
//
void Database::unlock()
{
StLock<Mutex> _(*common);
makeUnlocked();
}
-
+
void Database::makeUnlocked()
+{
+ return makeUnlocked(mCred);
+}
+
+void Database::makeUnlocked(const AccessCredentials *cred)
{
IFDUMPING("SSdb", debugDump("default procedures unlock"));
if (isLocked()) {
- assert(mBlob || (mValidData && common->passphrase));
-
- Process &cltProc = Server::active().connection().process;
- IFDEBUG(debug("SSdb", "Unlock query from process %d (UID %d)", cltProc.pid(), cltProc.uid()));
- QueryUnlock query(cltProc.uid(), cltProc.session, *this);
- query(mCred);
- if (isLocked()) // still locked, unlock failed
- CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
-
- // successfully unlocked
+ assert(mBlob || (mValidData && common->hasMaster()));
+ establishOldSecrets(cred);
activity(); // set timeout timer
- } else if (!mValidData) // need to decode to get our ACLs, passphrase available
- decode(common->passphrase);
+ } else if (!mValidData) { // need to decode to get our ACLs, passphrase available
+ if (!decode())
+ CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
+ }
+ assert(!isLocked());
+ assert(mValidData);
}
//
-// Perform programmatic unlock of a database, given a passphrase.
+// The following unlock given an explicit passphrase, rather than using
+// (special cred sample based) default procedures.
//
void Database::unlock(const CssmData &passphrase)
{
return;
else
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
- } else if (!mValidData)
- decode(common->passphrase);
+ } else if (!mValidData) { // need to decode to get our ACLs, passphrase available
+ if (!decode())
+ CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
+ }
+ assert(!isLocked());
+ assert(mValidData);
}
//
-// Perform an actual unlock operation given a passphrase.
+// Nonthrowing passphrase-based unlock. This returns false if unlock failed.
+// Note that this requires an explicitly given passphrase.
// Caller must hold common lock.
//
bool Database::decode(const CssmData &passphrase)
{
- if (mValidData && common->passphrase) { // just check
- return common->unlock(passphrase);
- } else { // decode our blob
+ assert(mBlob);
+ common->setup(mBlob, passphrase);
+ return decode();
+}
+
+
+//
+// Given the established master secret, decode the working keys and other
+// functional secrets for this database. Return false (do NOT throw) if
+// the decode fails. Call this in low(er) level code once you established
+// the master key.
+//
+bool Database::decode()
+{
+ assert(mBlob);
+ assert(common->hasMaster());
+ void *privateAclBlob;
+ if (common->unlock(mBlob, &privateAclBlob)) {
+ if (!mValidData) {
+ importBlob(mBlob->publicAclBlob(), privateAclBlob);
+ mValidData = true;
+ }
+ CssmAllocator::standard().free(privateAclBlob);
+ return true;
+ }
+ secdebug("SSdb", "%p decode failed", this);
+ return false;
+}
+
+
+//
+// Given an AccessCredentials for this database, wring out the existing primary
+// database secret by whatever means necessary.
+// On entry, caller must hold the database common lock. It will be held throughout.
+// On exit, the crypto core has its master secret. If things go wrong,
+// we will throw a suitable exception. Note that encountering any malformed
+// credential sample will throw, but this is not guaranteed -- don't assume
+// that NOT throwing means creds is entirely well-formed.
+//
+// How this works:
+// Walk through the creds. Fish out those credentials (in order) that
+// are for unlock processing (they have no ACL subject correspondents),
+// and (try to) obey each in turn, until one produces a valid secret
+// or you run out. If no special samples are found at all, interpret that as
+// "use the system global default," which happens to be hard-coded right here.
+//
+void Database::establishOldSecrets(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()) {
+ // interactively prompt the user - no additional data
+ case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT:
+ {
+ secdebug("SSdb", "%p attempting interactive unlock", this);
+ QueryUnlock query(*this);
+ if (query() == SecurityAgent::noReason)
+ return;
+ }
+ break;
+ // try to use an explicitly given passphrase - Data:passphrase
+ case CSSM_SAMPLE_TYPE_PASSWORD:
+ if (sample.length() != 2)
+ CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
+ secdebug("SSdb", "%p attempting passphrase unlock", this);
+ if (decode(sample[1]))
+ return;
+ break;
+ // try to open with a given master key - Data:CSP or KeyHandle, Data:CssmKey
+ case CSSM_WORDID_SYMMETRIC_KEY:
+ assert(mBlob);
+ secdebug("SSdb", "%p attempting explicit key unlock", this);
+ common->setup(mBlob, keyFromCreds(sample));
+ if (decode())
+ return;
+ break;
+ // explicitly defeat the default action but don't try anything in particular
+ case CSSM_WORDID_CANCELED:
+ secdebug("SSdb", "%p defeat default action", this);
+ break;
+ default:
+ // Unknown sub-sample for unlocking.
+ // If we wanted to be fascist, we could now do
+ // CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED);
+ // But instead we try to be tolerant and continue on.
+ // This DOES however count as an explicit attempt at specifying unlock,
+ // so we will no longer try the default case below...
+ secdebug("SSdb", "%p unknown sub-sample unlock (%ld) ignored", this, sample.type());
+ break;
+ }
+ }
+ } else {
+ // default action
assert(mBlob);
- void *privateAclBlob;
- if (common->unlock(mBlob, passphrase, &privateAclBlob)) {
- if (!mValidData) {
- importBlob(mBlob->publicAclBlob(), privateAclBlob);
- mValidData = true;
+ SystemKeychainKey systemKeychain(kSystemUnlockFile);
+ if (systemKeychain.matches(mBlob->randomSignature)) {
+ secdebug("SSdb", "%p attempting system unlock", this);
+ common->setup(mBlob, CssmClient::Key(Server::csp(), systemKeychain.key(), true));
+ if (decode())
+ return;
+ }
+
+ QueryUnlock query(*this);
+ if (query() == SecurityAgent::noReason)
+ return;
+ }
+
+ // out of options - no secret obtained
+ CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
+}
+
+
+//
+// Same thing, but obtain a new secret somehow and set it into the common.
+//
+void Database::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()) {
+ // interactively prompt the user
+ case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT:
+ {
+ secdebug("SSdb", "%p specified interactive passphrase", this);
+ QueryNewPassphrase query(*this, reason);
+ CssmAutoData passphrase(CssmAllocator::standard(CssmAllocator::sensitive));
+ if (query(passphrase) == SecurityAgent::noReason) {
+ common->setup(NULL, passphrase);
+ return;
+ }
+ }
+ break;
+ // try to use an explicitly given passphrase
+ case CSSM_SAMPLE_TYPE_PASSWORD:
+ secdebug("SSdb", "%p specified explicit passphrase", this);
+ if (sample.length() != 2)
+ CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
+ common->setup(NULL, sample[1]);
+ return;
+ // try to open with a given master key
+ case CSSM_WORDID_SYMMETRIC_KEY:
+ secdebug("SSdb", "%p specified explicit master key", this);
+ common->setup(NULL, keyFromCreds(sample));
+ return;
+ // explicitly defeat the default action but don't try anything in particular
+ case CSSM_WORDID_CANCELED:
+ secdebug("SSdb", "%p defeat default action", this);
+ break;
+ default:
+ // Unknown sub-sample for acquiring new secret.
+ // If we wanted to be fascist, we could now do
+ // CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED);
+ // But instead we try to be tolerant and continue on.
+ // This DOES however count as an explicit attempt at specifying unlock,
+ // so we will no longer try the default case below...
+ secdebug("SSdb", "%p unknown sub-sample acquisition (%ld) ignored",
+ this, sample.type());
+ break;
}
- CssmAllocator::standard().free(privateAclBlob);
- return true;
+ }
+ } else {
+ // default action -- interactive (only)
+ QueryNewPassphrase query(*this, reason);
+ CssmAutoData passphrase(CssmAllocator::standard(CssmAllocator::sensitive));
+ if (query(passphrase) == SecurityAgent::noReason) {
+ common->setup(NULL, passphrase);
+ return;
}
}
- return false;
+
+ // out of options - no secret obtained
+ CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
+}
+
+
+//
+// Given a (truncated) Database credentials TypedList specifying a master key,
+// locate the key and return a reference to it.
+//
+CssmClient::Key Database::keyFromCreds(const TypedList &sample)
+{
+ // decode TypedList structure (sample type; Data:CSPHandle; Data:CSSM_KEY)
+ assert(sample.type() == CSSM_WORDID_SYMMETRIC_KEY);
+ if (sample.length() != 3
+ || sample[1].type() != CSSM_LIST_ELEMENT_DATUM
+ || sample[2].type() != CSSM_LIST_ELEMENT_DATUM)
+ CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
+ CSSM_CSP_HANDLE &handle = *sample[1].data().interpretedAs<CSSM_CSP_HANDLE>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
+ CssmKey &key = *sample[2].data().interpretedAs<CssmKey>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
+
+ if (key.header().cspGuid() == gGuidAppleCSPDL) {
+ // handleOrKey is a SecurityServer KeyHandle; ignore key argument
+ return Server::key(handle);
+ } else {
+ // not a KeyHandle reference; use key as a raw 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);
+ }
}
//
// Verify a putative database passphrase.
-// This requires that the database be already unlocked;
-// it will not unlock the database (and will not lock it
-// if the proffered phrase is wrong).
+// If the database is already unlocked, just check the passphrase.
+// Otherwise, unlock with that passphrase and report success.
+// Caller must hold the common lock.
//
bool Database::validatePassphrase(const CssmData &passphrase) const
{
- assert(!isLocked());
- return passphrase == common->passphrase;
+ if (common->hasMaster()) {
+ // verify against known secret
+ return common->validatePassphrase(passphrase);
+ } else {
+ // no master secret - perform "blind" unlock to avoid actual unlock
+ try {
+ DatabaseCryptoCore test;
+ test.setup(mBlob, passphrase);
+ test.decodeCore(mBlob, NULL);
+ return true;
+ } catch (...) {
+ return false;
+ }
+ }
}
//
void Database::lock()
{
- common->lock();
+ common->lock(false);
}
// This is an interim stop-gap measure, until we can work out how database
// state should interact with true multi-session operation.
//
-void Database::lockAllDatabases(bool forSleep)
+void Database::lockAllDatabases(CommonMap &commons, bool forSleep)
{
- StLock<Mutex> _(commonLock); // hold all changes to Common map
- debug("SSdb", "locking all %d known databases", int(commons.size()));
+ StLock<Mutex> _(commons); // hold all changes to Common map
for (CommonMap::iterator it = commons.begin(); it != commons.end(); it++)
it->second->lock(true, forSleep); // lock, already holding commonLock
}
//
KeyBlob *Database::encodeKey(const CssmKey &key, const CssmData &pubAcl, const CssmData &privAcl)
{
- makeUnlocked();
+ unlock();
// tell the cryptocore to form the key blob
return common->encodeKeyCore(key, pubAcl, privAcl);
// Given a "blobbed" key for this database, decode it into its real
// key object and (re)populate its ACL.
//
-void Database::decodeKey(KeyBlob *blob, CssmKey &key,
- void * &pubAcl, void * &privAcl)
+void Database::decodeKey(KeyBlob *blob, CssmKey &key, void * &pubAcl, void * &privAcl)
{
- makeUnlocked(); // we need our keys
+ unlock(); // we need our keys
common->decodeKeyCore(blob, key, pubAcl, privAcl);
// memory protocol: pubAcl points into blob; privAcl was allocated
common->mParams = params;
common->version++; // invalidate old blobs
activity();
+ secdebug("SSdb", "%p common %p(%s) set params=(%ld,%d)",
+ this, common, dbName(), params.idleTimeout, params.lockOnSleep);
}
makeUnlocked();
}
-void Database::noticeAclChange()
+void Database::changedAcl()
{
StLock<Mutex> _(*common);
version = 0;
//
// Database::Common basic features
//
-Database::Common::Common(const DbIdentifier &id)
-: mIdentifier(id), sequence(0), passphrase(CssmAllocator::standard(CssmAllocator::sensitive)),
- useCount(0), version(1),
- mIsLocked(true)
+Database::Common::Common(const DbIdentifier &id, CommonMap &commonPool)
+: pool(commonPool), mIdentifier(id), sequence(0), useCount(0), version(1),
+ mIsLocked(true), mValidParams(false)
{ }
Database::Common::~Common()
{
// explicitly unschedule ourselves
Server::active().clearTimer(this);
+ pool.erase(identifier());
+}
+
+
+void Database::Common::makeNewSecrets()
+{
+ // we already have a master key (right?)
+ assert(hasMaster());
+
+ // tell crypto core to generate the use keys
+ DatabaseCryptoCore::generateNewSecrets();
+
+ // we're now officially "unlocked"; set the timer
+ mIsLocked = false;
+ activity();
}
void Database::discard(Common *common)
{
- // LOCKING: commonLock held, *common NOT held
- debug("SSdb", "discarding dbcommon %p (no users, locked)", common);
- commons.erase(common->identifier());
+ // LOCKING: pool lock held, *common NOT held
+ secdebug("SSdb", "discarding dbcommon %p (no users, locked)", common);
delete common;
}
-bool Database::Common::unlock(DbBlob *blob, const CssmData &passphrase,
- void **privateAclBlob)
+
+//
+// All unlocking activity ultimately funnels through this method.
+// This unlocks a Common using the secrets setup in its crypto core
+// component, and performs all the housekeeping needed to represent
+// the state change.
+//
+bool Database::Common::unlock(DbBlob *blob, void **privateAclBlob)
{
try {
// Tell the cryptocore to (try to) decode itself. This will fail
// in an astonishing variety of ways if the passphrase is wrong.
- decodeCore(blob, passphrase, privateAclBlob);
+ assert(hasMaster());
+ decodeCore(blob, privateAclBlob);
+ secdebug("SSdb", "%p unlock successful", this);
} catch (...) {
- //@@@ which errors should we let through? Any?
+ secdebug("SSdb", "%p unlock failed", this);
return false;
}
-
- // save the passphrase (we'll need it for database encoding)
- this->passphrase = passphrase;
- // retrieve some public arguments
- mParams = blob->params;
+ // get the database parameters only if we haven't got them yet
+ if (!mValidParams) {
+ mParams = blob->params;
+ n2hi(mParams.idleTimeout);
+ mValidParams = true; // sticky
+ }
// now successfully unlocked
mIsLocked = false;
// broadcast unlock notification
KeychainNotifier::unlock(identifier());
- notify(unlockedEvent);
return true;
}
-//
-// Fast-path unlock: secrets already valid; just check passphrase and approve.
-//
-bool Database::Common::unlock(const CssmData &passphrase)
-{
- assert(isValid());
- if (isLocked()) {
- if (passphrase == this->passphrase) {
- mIsLocked = false;
- KeychainNotifier::unlock(identifier());
- notify(unlockedEvent);
- return true; // okay
- } else
- return false; // failed
- } else
- return true; // was unlocked; no problem
-}
-
void Database::Common::lock(bool holdingCommonLock, bool forSleep)
{
StLock<Mutex> locker(*this);
if (forSleep && !mParams.lockOnSleep)
return; // it doesn't want to
- //@@@ discard secrets here? That would make fast-path impossible.
mIsLocked = true;
+ DatabaseCryptoCore::invalidate();
KeychainNotifier::lock(identifier());
- notify(lockedEvent);
+ Server::active().clearTimer(this);
// if no database refers to us now, we're history
- StLock<Mutex> _(commonLock, false);
+ StLock<Mutex> _(pool, false);
if (!holdingCommonLock)
_.lock();
if (useCount == 0) {
form.randomSignature = identifier();
form.sequence = sequence;
form.params = mParams;
- DbBlob *blob = encodeCore(form, passphrase, pubAcl, privAcl);
+ h2ni(form.params.idleTimeout);
+
+ assert(hasMaster());
+ DbBlob *blob = encodeCore(form, pubAcl, privAcl);
// clean up and go
db.allocator.free(pubAcl);
//
-// Send out database-related notifications
+// Perform deferred lock processing for a database.
//
-void Database::Common::notify(Listener::Event event)
+void Database::Common::action()
{
- IFDEBUG(debug("SSdb", "common %s(%p) sending event %ld", dbName(), this, event));
- DLDbFlatIdentifier flatId(mIdentifier); // walkable form of DLDbIdentifier
- CssmAutoData data(CssmAllocator::standard());
- copy(&flatId, CssmAllocator::standard(), data.get());
- Listener::notify(Listener::databaseNotifications, event, data);
+ secdebug("SSdb", "common %s(%p) locked by timer (%d refs)",
+ dbName(), this, int(useCount));
+ lock(false);
+}
+
+void Database::Common::activity()
+{
+ if (!isLocked())
+ Server::active().setTimer(this, Time::Interval(int(mParams.idleTimeout)));
}
//
-// Initialize a (new) database's key information.
-// This acquires the passphrase in the appropriate way.
-// When (successfully) done, the database is in the unlocked state.
+// Implementation of a "system keychain unlock key store"
//
-void Database::Common::setupKeys(const AccessCredentials *cred)
+SystemKeychainKey::SystemKeychainKey(const char *path)
+ : mPath(path)
{
- // get the new passphrase
- // @@@ Un-staged version of the API - revise with acceptability tests
- Process &cltProc = Server::active().connection().process;
- IFDEBUG(debug("SSdb", "New passphrase request from process %d (UID %d)", cltProc.pid(), cltProc.uid()));
- QueryNewPassphrase query(cltProc.uid(), cltProc.session, *this, SecurityAgent::newDatabase);
- query(cred, passphrase);
-
- // we have the passphrase now
- generateNewSecrets();
-
- // we're unlocked now
- mIsLocked = false;
- activity();
+ // explicitly set up a key header for a raw 3DES key
+ CssmKey::Header &hdr = mKey.header();
+ hdr.blobType(CSSM_KEYBLOB_RAW);
+ hdr.blobFormat(CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING);
+ hdr.keyClass(CSSM_KEYCLASS_SESSION_KEY);
+ hdr.algorithm(CSSM_ALGID_3DES_3KEY_EDE);
+ hdr.KeyAttr = 0;
+ hdr.KeyUsage = CSSM_KEYUSE_ANY;
+ mKey = CssmData::wrap(mBlob.masterKey);
}
+SystemKeychainKey::~SystemKeychainKey()
+{
+}
-//
-// Perform deferred lock processing for a database.
-//
-void Database::Common::action()
+bool SystemKeychainKey::matches(const DbBlob::Signature &signature)
{
- IFDEBUG(debug("SSdb", "common %s(%p) locked by timer (%d refs)",
- dbName(), this, int(useCount)));
- lock();
+ return update() && signature == mBlob.signature;
}
-void Database::Common::activity()
+bool SystemKeychainKey::update()
{
- if (!isLocked())
- Server::active().setTimer(this, int(mParams.idleTimeout));
+ // if we checked recently, just assume it's okay
+ if (mUpdateThreshold > Time::now())
+ return mValid;
+
+ // check the file
+ struct stat st;
+ if (::stat(mPath.c_str(), &st)) {
+ // something wrong with the file; can't use it
+ mUpdateThreshold = Time::now() + Time::Interval(checkDelay);
+ return mValid = false;
+ }
+ if (mValid && Time::Absolute(st.st_mtimespec) == mCachedDate)
+ return true;
+ mUpdateThreshold = Time::now() + Time::Interval(checkDelay);
+
+ try {
+ secdebug("syskc", "reading system unlock record from %s", mPath.c_str());
+ AutoFileDesc fd(mPath, O_RDONLY);
+ if (fd.read(mBlob) != sizeof(mBlob))
+ return false;
+ if (mBlob.isValid()) {
+ mCachedDate = st.st_mtimespec;
+ return mValid = true;
+ } else
+ return mValid = false;
+ } catch (...) {
+ secdebug("syskc", "system unlock record not available");
+ return false;
+ }
}