#include "tokendatabase.h"
#include "tokenkey.h"
#include "tokenaccess.h"
#include "process.h"
#include "server.h"
#include "localkey.h" // to retrieve local raw keys
#include <security_cdsa_client/wrapkey.h>
TokenDbCommon::TokenDbCommon(Session &ssn, Token &tk, const char *name)
: DbCommon(ssn), mDbName(name ? name : ""), mHasAclState(false), mResetLevel(0)
{
secdebug("tokendb", "creating tokendbcommon %p: with token %p", this, &tk);
parent(tk);
}
TokenDbCommon::~TokenDbCommon()
{
secdebug("tokendb", "destroying tokendbcommon %p", this);
token().removeCommon(*this); }
Token &TokenDbCommon::token() const
{
return parent<Token>();
}
std::string TokenDbCommon::dbName() const
{
return token().printName();
}
Adornable &TokenDbCommon::store()
{
StLock<Mutex> _(*this);
if (!mHasAclState) {
session().addReference(*this); token().addCommon(*this); mHasAclState = true;
}
return *this;
}
void TokenDbCommon::resetAcls()
{
StLock<Mutex> _(*this);
if (mHasAclState) {
clearAdornments(); session().removeReference(*this); mHasAclState = false;
}
token().removeCommon(*this); }
void TokenDbCommon::notify(NotificationEvent event)
{
DbCommon::notify(event, DLDbIdentifier(dbName().c_str(), gGuidAppleSdCSPDL,
subservice(), CSSM_SERVICE_DL | CSSM_SERVICE_CSP));
}
void TokenDbCommon::lockProcessing()
{
Access access(token());
access().authenticate(CSSM_DB_ACCESS_RESET, NULL);
}
TokenDatabase::TokenDatabase(uint32 ssid, Process &proc,
const char *name, const AccessCredentials *cred)
: Database(proc)
{
RefPointer<Token> token = Token::find(ssid);
Session &session = process().session();
StLock<Mutex> _(session);
if (TokenDbCommon *dbcom = session.findFirst<TokenDbCommon, uint32>(&TokenDbCommon::subservice, ssid)) {
parent(*dbcom);
secdebug("tokendb", "open tokendb %p(%d) at known common %p",
this, subservice(), dbcom);
} else {
parent(*new TokenDbCommon(proc.session(), *token, name));
secdebug("tokendb", "open tokendb %p(%d) with new common %p",
this, subservice(), &common());
}
mOpenCreds = copy(cred, Allocator::standard());
proc.addReference(*this);
}
TokenDatabase::~TokenDatabase()
{
Allocator::standard().free(mOpenCreds);
}
TokenDbCommon &TokenDatabase::common() const
{
return parent<TokenDbCommon>();
}
TokenDaemon &TokenDatabase::tokend()
{
return common().token().tokend();
}
const char *TokenDatabase::dbName() const
{
mDbName = common().dbName();
return mDbName.c_str();
}
bool TokenDatabase::transient() const
{
return false;
}
SecurityServerAcl &TokenDatabase::acl()
{
return token();
}
void TokenDatabase::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
{
AclSource::getAcl(tag, count, acls);
for (unsigned n = 0; n < count; n++) {
AclEntryPrototype &proto = acls[n];
if (unsigned pin = pinFromAclTag(proto.tag(), "?")) { secdebug("tokendb", "%p updating PIN%d state response", this, pin);
TypedList &subject = proto.subject();
if (subject.length() > 2
&& subject[0].is(CSSM_LIST_ELEMENT_WORDID)
&& subject[0] == CSSM_WORDID_PIN
&& subject[1].is(CSSM_LIST_ELEMENT_WORDID)
&& subject[2].is(CSSM_LIST_ELEMENT_WORDID)) {
uint32 pin = subject[1];
if (!common().attachment<PreAuthorizationAcls::AclState>((void *)pin).accepted) {
secdebug("tokendb", "%p session state forces PIN%d reporting unauthorized", this, pin);
uint32 status = subject[2];
status &= ~CSSM_ACL_PREAUTH_TRACKING_AUTHORIZED; subject[2] = status;
#if !defined(NDEBUG)
if (subject.length() > 3 && subject[3].is(CSSM_LIST_ELEMENT_WORDID))
secdebug("tokendb", "%p PIN%d count=%d", this, pin, subject[3].word());
#endif //NDEBUG
}
}
}
}
}
bool TokenDatabase::isLocked()
{
Access access(token());
bool lockState = pinState(1);
secdebug("tokendb", "returning isLocked=%d", lockState);
return lockState;
}
bool TokenDatabase::pinState(uint32 pin, int *pinCount )
{
uint32 count;
AclEntryInfo *acls;
this->getAcl("PIN1?", count, acls);
bool locked = true; if (pinCount)
*pinCount = -1; switch (count) {
case 0:
secdebug("tokendb", "PIN%d query returned no entries", pin);
break;
default:
secdebug("tokendb", "PIN%d query returned multiple entries", pin);
break;
case 1:
{
TypedList &subject = acls[0].proto().subject();
if (subject.length() > 2
&& subject[0].is(CSSM_LIST_ELEMENT_WORDID)
&& subject[0] == CSSM_WORDID_PIN
&& subject[1].is(CSSM_LIST_ELEMENT_WORDID)
&& subject[2].is(CSSM_LIST_ELEMENT_WORDID)) {
uint32 status = subject[2];
locked = !(status & CSSM_ACL_PREAUTH_TRACKING_AUTHORIZED);
if (pinCount && locked && subject.length() > 3 && subject[3].is(CSSM_LIST_ELEMENT_WORDID))
*pinCount = subject[3];
}
}
break;
}
ChunkFreeWalker free;
for (uint32 n = 0; n < count; n++)
walk(free, acls[n]);
Allocator::standard().free(acls);
return locked;
}
void TokenDatabase::dbName(const char *name)
{
CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}
RefPointer<Key> TokenDatabase::makeKey(KeyHandle hKey, const CssmKey *key,
uint32 moreAttributes, const AclEntryPrototype *owner)
{
switch (key->blobType()) {
case CSSM_KEYBLOB_REFERENCE:
return new TokenKey(*this, hKey, key->header());
case CSSM_KEYBLOB_RAW:
return process().makeTemporaryKey(*key, moreAttributes, owner);
default:
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }
}
static CSSM_KEYATTR_FLAGS modattrs(CSSM_KEYATTR_FLAGS attrs)
{
static const CSSM_KEYATTR_FLAGS CSSM_KEYATTR_RETURN_FLAGS = 0xff000000;
switch (attrs & CSSM_KEYATTR_RETURN_FLAGS) {
case CSSM_KEYATTR_RETURN_REF:
case CSSM_KEYATTR_RETURN_DATA:
break; case CSSM_KEYATTR_RETURN_NONE:
CssmError::throwMe(CSSMERR_CSP_UNSUPPORTED_KEYATTR_MASK);
case CSSM_KEYATTR_RETURN_DEFAULT:
if (attrs & CSSM_KEYATTR_PERMANENT)
attrs |= CSSM_KEYATTR_RETURN_REF;
else
attrs |= CSSM_KEYATTR_RETURN_DATA;
break;
}
return attrs;
}
bool TokenDatabase::validateSecret(const AclSubject *subject, const AccessCredentials *cred)
{
secdebug("tokendb", "%p attempting remote validation", this);
try {
Access access(token());
access().authenticate(CSSM_DB_ACCESS_READ, cred);
secdebug("tokendb", "%p remote validation successful", this);
return true;
}
catch (...) {
secdebug("tokendb", "%p remote validation failed", this);
throw; }
}
void TokenDatabase::queryKeySizeInBits(Key &key, CssmKeySize &result)
{
Access access(token());
TRY
GUARD
access().queryKeySizeInBits(myKey(key).tokenHandle(), result);
DONE
}
void TokenDatabase::generateSignature(const Context &context, Key &key,
CSSM_ALGORITHMS signOnlyAlgorithm, const CssmData &data, CssmData &signature)
{
Access access(token(), key);
TRY
key.validate(CSSM_ACL_AUTHORIZATION_SIGN, context);
GUARD
access().generateSignature(context, myKey(key).tokenHandle(), data, signature, signOnlyAlgorithm);
DONE
}
void TokenDatabase::verifySignature(const Context &context, Key &key,
CSSM_ALGORITHMS verifyOnlyAlgorithm, const CssmData &data, const CssmData &signature)
{
Access access(token(), key);
TRY
GUARD
access().verifySignature(context, myKey(key).tokenHandle(), data, signature, verifyOnlyAlgorithm);
DONE
}
void TokenDatabase::generateMac(const Context &context, Key &key,
const CssmData &data, CssmData &mac)
{
Access access(token());
TRY
key.validate(CSSM_ACL_AUTHORIZATION_MAC, context);
GUARD
access().generateMac(context, myKey(key).tokenHandle(), data, mac);
DONE
}
void TokenDatabase::verifyMac(const Context &context, Key &key,
const CssmData &data, const CssmData &mac)
{
Access access(token());
TRY
key.validate(CSSM_ACL_AUTHORIZATION_MAC, context);
GUARD
access().verifyMac(context, myKey(key).tokenHandle(), data, mac);
DONE
}
void TokenDatabase::encrypt(const Context &context, Key &key,
const CssmData &clear, CssmData &cipher)
{
Access access(token());
TRY
key.validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context);
GUARD
access().encrypt(context, myKey(key).tokenHandle(), clear, cipher);
DONE
}
void TokenDatabase::decrypt(const Context &context, Key &key,
const CssmData &cipher, CssmData &clear)
{
Access access(token());
TRY
key.validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context);
GUARD
access().decrypt(context, myKey(key).tokenHandle(), cipher, clear);
DONE
}
void TokenDatabase::generateKey(const Context &context,
const AccessCredentials *cred, const AclEntryPrototype *owner,
CSSM_KEYUSE usage, CSSM_KEYATTR_FLAGS attrs, RefPointer<Key> &newKey)
{
Access access(token());
TRY
GUARD
KeyHandle hKey;
CssmKey *result;
access().generateKey(context, cred, owner, usage, modattrs(attrs), hKey, result);
newKey = makeKey(hKey, result, 0, owner);
DONE
}
void TokenDatabase::generateKey(const Context &context,
const AccessCredentials *cred, const AclEntryPrototype *owner,
CSSM_KEYUSE pubUsage, CSSM_KEYATTR_FLAGS pubAttrs,
CSSM_KEYUSE privUsage, CSSM_KEYATTR_FLAGS privAttrs,
RefPointer<Key> &publicKey, RefPointer<Key> &privateKey)
{
Access access(token());
TRY
GUARD
KeyHandle hPrivate, hPublic;
CssmKey *privKey, *pubKey;
access().generateKey(context, cred, owner,
pubUsage, modattrs(pubAttrs), privUsage, modattrs(privAttrs),
hPublic, pubKey, hPrivate, privKey);
publicKey = makeKey(hPublic, pubKey, 0, owner);
privateKey = makeKey(hPrivate, privKey, 0, owner);
DONE
}
void TokenDatabase::wrapKey(const Context &context, const AccessCredentials *cred,
Key *wrappingKey, Key &subjectKey,
const CssmData &descriptiveData, CssmKey &wrappedKey)
{
Access access(token());
InputKey cWrappingKey(wrappingKey);
InputKey cSubjectKey(subjectKey);
TRY
subjectKey.validate(context.algorithm() == CSSM_ALGID_NONE ?
CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR : CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED,
cred);
if (wrappingKey)
wrappingKey->validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context);
GUARD
CssmKey *rWrappedKey;
access().wrapKey(context, cred,
cWrappingKey, cWrappingKey, cSubjectKey, cSubjectKey,
descriptiveData, rWrappedKey);
wrappedKey = *rWrappedKey;
DONE
}
void TokenDatabase::unwrapKey(const Context &context,
const AccessCredentials *cred, const AclEntryPrototype *owner,
Key *wrappingKey, Key *publicKey, CSSM_KEYUSE usage, CSSM_KEYATTR_FLAGS attrs,
const CssmKey wrappedKey, RefPointer<Key> &unwrappedKey, CssmData &descriptiveData)
{
Access access(token());
InputKey cWrappingKey(wrappingKey);
InputKey cPublicKey(publicKey);
TRY
if (wrappingKey)
wrappingKey->validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context);
GUARD
KeyHandle hKey;
CssmKey *result;
access().unwrapKey(context, cred, owner,
cWrappingKey, cWrappingKey, cPublicKey, cPublicKey,
wrappedKey, usage, modattrs(attrs), descriptiveData, hKey, result);
unwrappedKey = makeKey(hKey, result, modattrs(attrs) & LocalKey::managedAttributes, owner);
DONE
}
void TokenDatabase::deriveKey(const Context &context, Key *sourceKey,
const AccessCredentials *cred, const AclEntryPrototype *owner,
CssmData *param, CSSM_KEYUSE usage, CSSM_KEYATTR_FLAGS attrs, RefPointer<Key> &derivedKey)
{
Access access(token());
InputKey cSourceKey(sourceKey);
TRY
if (sourceKey)
sourceKey->validate(CSSM_ACL_AUTHORIZATION_DERIVE, cred);
GUARD
KeyHandle hKey;
CssmKey *result;
CssmData params = param ? *param : CssmData();
access().deriveKey(noDb, context,
cSourceKey, cSourceKey,
usage, modattrs(attrs), params, cred, owner,
hKey, result);
if (param) {
*param = params;
}
derivedKey = makeKey(hKey, result, 0, owner);
DONE
}
void TokenDatabase::getOutputSize(const Context &context, Key &key,
uint32 inputSize, bool encrypt, uint32 &result)
{
Access access(token());
TRY
GUARD
access().getOutputSize(context, myKey(key).tokenHandle(), inputSize, encrypt, result);
DONE
}
void TokenDatabase::authenticate(CSSM_DB_ACCESS_TYPE mode, const AccessCredentials *cred)
{
Access access(token());
TRY
GUARD
if (mode != CSSM_DB_ACCESS_RESET && cred) {
secdebug("tokendb", "%p authenticate calling validate", this);
if (unsigned pin = pinFromAclTag(cred->EntryTag)) {
validate(CSSM_ACL_AUTHORIZATION_PREAUTH(pin), cred);
notify(kNotificationEventUnlocked);
return;
}
}
access().authenticate(mode, cred);
switch (mode) {
case CSSM_DB_ACCESS_RESET:
common().resetAcls();
notify(kNotificationEventLocked);
break;
default:
{
AccessCredentials *newCred = copy(cred, Allocator::standard());
Allocator::standard().free(mOpenCreds);
mOpenCreds = newCred;
}
break;
}
DONE
}
void TokenDatabase::findFirst(const CssmQuery &query,
CssmDbRecordAttributeData *inAttributes, mach_msg_type_number_t inAttributesLength,
CssmData *data, RefPointer<Key> &key,
RefPointer<Database::Search> &rSearch, RefPointer<Database::Record> &rRecord,
CssmDbRecordAttributeData * &outAttributes, mach_msg_type_number_t &outAttributesLength)
{
Access access(token());
RefPointer<Search> search = new Search(*this);
RefPointer<Record> record = new Record(*this);
TRY
KeyHandle hKey = noKey;
validate(CSSM_ACL_AUTHORIZATION_DB_READ, openCreds());
GUARD
record->tokenHandle() = access().Tokend::ClientSession::findFirst(query,
inAttributes, inAttributesLength, search->tokenHandle(), NULL, hKey,
outAttributes, outAttributesLength);
if (!record->tokenHandle()) { rRecord = NULL; return;
}
if (data) {
if (!hKey)
record->validate(CSSM_ACL_AUTHORIZATION_DB_READ, openCreds());
CssmDbRecordAttributeData *noAttributes;
mach_msg_type_number_t noAttributesLength;
access().Tokend::ClientSession::findRecordHandle(record->tokenHandle(),
NULL, 0, data, hKey, noAttributes, noAttributesLength);
if (hKey) { CssmKey &keyForm = *data->interpretedAs<CssmKey>(CSSMERR_CSP_INVALID_KEY);
key = new TokenKey(*this, hKey, keyForm.header());
}
}
rSearch = search->commit();
rRecord = record->commit();
DONE
}
void TokenDatabase::findNext(Database::Search *rSearch,
CssmDbRecordAttributeData *inAttributes, mach_msg_type_number_t inAttributesLength,
CssmData *data, RefPointer<Key> &key, RefPointer<Database::Record> &rRecord,
CssmDbRecordAttributeData * &outAttributes, mach_msg_type_number_t &outAttributesLength)
{
Access access(token());
RefPointer<Record> record = new Record(*this);
Search *search = safe_cast<Search *>(rSearch);
TRY
KeyHandle hKey = noKey;
validate(CSSM_ACL_AUTHORIZATION_DB_READ, openCreds());
GUARD
record->tokenHandle() = access().Tokend::ClientSession::findNext(
search->tokenHandle(), inAttributes, inAttributesLength,
NULL, hKey, outAttributes, outAttributesLength);
if (!record->tokenHandle()) { releaseSearch(*search); rRecord = NULL; return;
}
if (data) {
if (!hKey)
record->validate(CSSM_ACL_AUTHORIZATION_DB_READ, openCreds());
CssmDbRecordAttributeData *noAttributes;
mach_msg_type_number_t noAttributesLength;
access().Tokend::ClientSession::findRecordHandle(record->tokenHandle(),
NULL, 0, data, hKey, noAttributes, noAttributesLength);
if (hKey) { CssmKey &keyForm = *data->interpretedAs<CssmKey>(CSSMERR_CSP_INVALID_KEY);
key = new TokenKey(*this, hKey, keyForm.header());
}
}
rRecord = record->commit();
DONE
}
void TokenDatabase::findRecordHandle(Database::Record *rRecord,
CssmDbRecordAttributeData *inAttributes, mach_msg_type_number_t inAttributesLength,
CssmData *data, RefPointer<Key> &key,
CssmDbRecordAttributeData * &outAttributes, mach_msg_type_number_t &outAttributesLength)
{
Access access(token());
Record *record = safe_cast<Record *>(rRecord);
access.add(*record);
TRY
KeyHandle hKey = noKey;
validate(CSSM_ACL_AUTHORIZATION_DB_READ, openCreds());
if (data)
record->validate(CSSM_ACL_AUTHORIZATION_DB_READ, openCreds());
GUARD
access().Tokend::ClientSession::findRecordHandle(record->tokenHandle(),
inAttributes, inAttributesLength, data, hKey, outAttributes, outAttributesLength);
rRecord = record;
if (hKey != noKey && data) { CssmKey &keyForm = *data->interpretedAs<CssmKey>(CSSMERR_CSP_INVALID_KEY);
key = new TokenKey(*this, hKey, keyForm.header());
}
DONE
}
void TokenDatabase::insertRecord(CSSM_DB_RECORDTYPE recordType,
const CssmDbRecordAttributeData *attributes, mach_msg_type_number_t attributesLength,
const CssmData &data, RefPointer<Database::Record> &rRecord)
{
Access access(token());
RefPointer<Record> record = new Record(*this);
access.add(*record);
TRY
validate(CSSM_ACL_AUTHORIZATION_DB_INSERT, openCreds());
GUARD
access().Tokend::ClientSession::insertRecord(recordType,
attributes, attributesLength, data, record->tokenHandle());
rRecord = record;
DONE
}
void TokenDatabase::modifyRecord(CSSM_DB_RECORDTYPE recordType, Record *rRecord,
const CssmDbRecordAttributeData *attributes, mach_msg_type_number_t attributesLength,
const CssmData *data, CSSM_DB_MODIFY_MODE modifyMode)
{
Access access(token());
Record *record = safe_cast<Record *>(rRecord);
access.add(*record);
TRY
validate(CSSM_ACL_AUTHORIZATION_DB_MODIFY, openCreds());
record->validate(CSSM_ACL_AUTHORIZATION_DB_MODIFY, openCreds());
GUARD
access().Tokend::ClientSession::modifyRecord(recordType,
record->tokenHandle(), attributes, attributesLength, data, modifyMode);
DONE
}
void TokenDatabase::deleteRecord(Database::Record *rRecord)
{
Access access(token(), *this);
Record *record = safe_cast<Record *>(rRecord);
access.add(*record);
TRY
validate(CSSM_ACL_AUTHORIZATION_DB_DELETE, openCreds());
record->validate(CSSM_ACL_AUTHORIZATION_DB_DELETE, openCreds());
GUARD
access().Tokend::ClientSession::deleteRecord(record->tokenHandle());
DONE
}
TokenDatabase::Search::~Search()
{
if (mHandle)
try {
database().token().tokend().Tokend::ClientSession::releaseSearch(mHandle);
} catch (...) {
secdebug("tokendb", "%p release search handle %u threw (ignored)",
this, mHandle);
}
}
TokenDatabase::Record::~Record()
{
if (mHandle)
try {
database().token().tokend().Tokend::ClientSession::releaseRecord(mHandle);
} catch (...) {
secdebug("tokendb", "%p release record handle %u threw (ignored)",
this, mHandle);
}
}
AclKind TokenDatabase::Record::aclKind() const
{
return objectAcl;
}
Token &TokenDatabase::Record::token()
{
return safer_cast<TokenDatabase &>(database()).token();
}
GenericHandle TokenDatabase::Record::tokenHandle() const
{
return Handler::tokenHandle();
}
void TokenDatabase::InputKey::setup(Key *key)
{
if (TokenKey *myKey = dynamic_cast<TokenKey *>(key)) {
mKeyHandle = myKey->tokenHandle();
mKeyPtr = NULL;
} else if (LocalKey *hisKey = dynamic_cast<LocalKey *>(key)) {
CssmClient::WrapKey wrap(Server::csp(), CSSM_ALGID_NONE);
wrap(hisKey->cssmKey(), mKey);
mKeyHandle = noKey;
mKeyPtr = &mKey;
} else {
mKeyHandle = noKey;
mKeyPtr = NULL;
}
}
TokenDatabase::InputKey::~InputKey()
{
if (mKeyPtr) {
Server::csp()->allocator().free(mKey.keyData());
}
}