X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/libsecurity_cdsa_client/lib/securestorage.cpp diff --git a/OSX/libsecurity_cdsa_client/lib/securestorage.cpp b/OSX/libsecurity_cdsa_client/lib/securestorage.cpp new file mode 100644 index 00000000..6fb56f0d --- /dev/null +++ b/OSX/libsecurity_cdsa_client/lib/securestorage.cpp @@ -0,0 +1,650 @@ +/* + * Copyright (c) 2000-2001,2011-2014 Apple Inc. All Rights Reserved. + * + * The contents of this file constitute Original Code as defined in and are + * subject to the Apple Public Source License Version 1.2 (the 'License'). + * You may not use this file except in compliance with the License. Please obtain + * a copy of the License at http://www.apple.com/publicsource and read it before + * using this file. + * + * This Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS + * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT + * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the + * specific language governing rights and limitations under the License. + */ + + +#include "securestorage.h" +#include +//#include //@@@CONV +#include +#include + +using namespace CssmClient; +//using namespace KeychainCore; + +// +// Manage CSPDL attachments +// +CSPDLImpl::CSPDLImpl(const Guid &guid) +: CSPImpl(Cssm::standard()->autoModule(guid)), +DLImpl(CSPImpl::module()) +{ +} + +CSPDLImpl::CSPDLImpl(const Module &module) +: CSPImpl(module), +DLImpl(module) +{ +} + +CSPDLImpl::~CSPDLImpl() +try +{ +} +catch (...) +{ +} + +Allocator &CSPDLImpl::allocator() const +{ + DLImpl::allocator(); return CSPImpl::allocator(); +} + +void CSPDLImpl::allocator(Allocator &alloc) +{ + CSPImpl::allocator(alloc); DLImpl::allocator(alloc); +} + +bool CSPDLImpl::operator <(const CSPDLImpl &other) const +{ + return (static_cast(*this) < static_cast(other) || + (!(static_cast(other) < static_cast(*this)) + && static_cast(*this) < static_cast(other))); +} + +bool CSPDLImpl::operator ==(const CSPDLImpl &other) const +{ + return (static_cast(*this) == static_cast(other) + && static_cast(*this) == static_cast(other)); +} + +CSSM_SERVICE_MASK CSPDLImpl::subserviceMask() const +{ + return CSPImpl::subserviceType() | DLImpl::subserviceType(); +} + +void CSPDLImpl::subserviceId(uint32 id) +{ + CSPImpl::subserviceId(id); DLImpl::subserviceId(id); +} + + +// +// Secure storage +// +SSCSPDLImpl::SSCSPDLImpl(const Guid &guid) : CSPDLImpl::CSPDLImpl(guid) +{ +} + +SSCSPDLImpl::SSCSPDLImpl(const Module &module) : CSPDLImpl::CSPDLImpl(module) +{ +} + +SSCSPDLImpl::~SSCSPDLImpl() +{ +} + +DbImpl * +SSCSPDLImpl::newDb(const char *inDbName, const CSSM_NET_ADDRESS *inDbLocation) +{ + return new SSDbImpl(SSCSPDL(this), inDbName, inDbLocation); +} + + +// +// SSDbImpl -- Secure Storage Database Implementation +// +SSDbImpl::SSDbImpl(const SSCSPDL &cspdl, const char *inDbName, + const CSSM_NET_ADDRESS *inDbLocation) +: DbImpl(cspdl, inDbName, inDbLocation) +{ +} + +SSDbImpl::~SSDbImpl() +{ +} + +void +SSDbImpl::create() +{ + DbImpl::create(); +} + +void +SSDbImpl::open() +{ + DbImpl::open(); +} + +SSDbUniqueRecord +SSDbImpl::insert(CSSM_DB_RECORDTYPE recordType, + const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, + const CSSM_DATA *data, + const CSSM_RESOURCE_CONTROL_CONTEXT *rc) +{ + // Get the handle of the DL underlying this CSPDL. + CSSM_DL_DB_HANDLE dldbh; + passThrough(CSSM_APPLECSPDL_DB_GET_HANDLE, NULL, + reinterpret_cast(&dldbh)); + + // Turn off autocommit on the underlying DL and remember the old state. + CSSM_BOOL autoCommit = CSSM_TRUE; + check(CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, + 0, reinterpret_cast(&autoCommit))); + SSGroup group(SSDb(this), rc); + const CSSM_ACCESS_CREDENTIALS *cred = rc ? rc->AccessCred : NULL; + try + { + return insert(recordType, attributes, data, group, cred); + if (autoCommit) + { + // autoCommit was on so commit now that we are done and turn + // it back on. + check(CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_COMMIT, NULL, NULL)); + CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, + reinterpret_cast(autoCommit), NULL); + } + } + catch(...) + { + try { group->deleteKey(cred); } catch (...) {} + if (autoCommit) + { + // autoCommit was off so rollback since we failed and turn + // autoCommit back on. + CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_ROLLBACK, NULL, NULL); + CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, + reinterpret_cast(autoCommit), NULL); + } + throw; + } + + // keep the compiler happy -- this path is NEVER taken + CssmError::throwMe(0); +} + +SSDbUniqueRecord +SSDbImpl::insert(CSSM_DB_RECORDTYPE recordType, + const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, + const CSSM_DATA *data, const SSGroup &group, + const CSSM_ACCESS_CREDENTIALS *cred) +{ + // Create an encoded dataBlob for this item. + CssmDataContainer dataBlob(allocator()); + group->encodeDataBlob(data, cred, dataBlob); + + // Insert the record with the new juicy dataBlob. + return SSDbUniqueRecord(safe_cast + (&(*DbImpl::insert(recordType, attributes, &dataBlob)))); +} + + +// DbCursorMaker +DbCursorImpl * +SSDbImpl::newDbCursor(const CSSM_QUERY &query, Allocator &allocator) +{ + return new SSDbCursorImpl(Db(this), query, allocator); +} + +DbCursorImpl * +SSDbImpl::newDbCursor(uint32 capacity, Allocator &allocator) +{ + return new SSDbCursorImpl(Db(this), capacity, allocator); +} + + +// SSDbUniqueRecordMaker +DbUniqueRecordImpl * +SSDbImpl::newDbUniqueRecord() +{ + return new SSDbUniqueRecordImpl(Db(this)); +} + + +// +// SSGroup -- Group key with acl, used to protect a group of items. +// +// @@@ Get this from a shared spot. +CSSM_DB_NAME_ATTR(SSGroupImpl::kLabel, 6, (char*) "Label", 0, NULL, BLOB); + +// Create a new group. +SSGroupImpl::SSGroupImpl(const SSDb &ssDb, + const CSSM_RESOURCE_CONTROL_CONTEXT *credAndAclEntry) +: KeyImpl(ssDb->csp()), mLabel(ssDb->allocator()) +{ + mLabel.Length = kLabelSize; + mLabel.Data = reinterpret_cast + (mLabel.mAllocator.malloc(mLabel.Length)); + + // Get our csp and set up a random number generation context. + CSP csp(this->csp()); + Random random(csp, CSSM_ALGID_APPLE_YARROW); + + // Generate a kLabelSize byte random number that will be the label of + // the key which we store in the dataBlob. + random.generate(mLabel, (uint32)mLabel.Length); + + // Overwrite the first 4 bytes with the magic cookie for a group. + reinterpret_cast(mLabel.Data)[0] = h2n(uint32(kGroupMagic)); + + // @@@ Ensure that the label is unique (Chance of collision is 2^80 -- + // birthday paradox). + + // Generate a permanent 3DES key that we will use to encrypt the data. + GenerateKey genKey(csp, CSSM_ALGID_3DES_3KEY, 192); + genKey.database(ssDb); + + // Set the acl of the key correctly here + genKey.rcc(credAndAclEntry); + + // Generate the key + genKey(*this, KeySpec(CSSM_KEYUSE_ENCRYPT|CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_PERMANENT|CSSM_KEYATTR_SENSITIVE, + mLabel)); + + // Activate ourself so CSSM_FreeKey will get called when we go out of + // scope. + activate(); +} + +// Lookup an existing group based on a dataBlob. +SSGroupImpl::SSGroupImpl(const SSDb &ssDb, const CSSM_DATA &dataBlob) +: KeyImpl(ssDb->csp()), mLabel(ssDb->allocator()) +{ + if (dataBlob.Length < kLabelSize + kIVSize) + CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // Not a SS record + + mLabel = CssmData(dataBlob.Data, kLabelSize); + if (*reinterpret_cast(mLabel.Data) != h2n (uint32(kGroupMagic))) + CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // Not a SS record + + // Look up the symmetric key with that label. + DbCursor cursor(new DbDbCursorImpl(ssDb, 0, Allocator::standard())); + cursor->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY); + cursor->add(CSSM_DB_EQUAL, kLabel, mLabel); + + DbUniqueRecord keyId; + CssmDataContainer keyData(ssDb->allocator()); + if (!cursor->next(NULL, &keyData, keyId)) + CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // The key can't be found + + // Set the key part of ourself. + static_cast(*this) = + *reinterpret_cast(keyData.Data); + + // Activate ourself so CSSM_FreeKey will get called when we go out of + // scope. + activate(); +} + +bool +SSGroupImpl::isGroup(const CSSM_DATA &dataBlob) +{ + return dataBlob.Length >= kLabelSize + kIVSize + && *reinterpret_cast(dataBlob.Data) == h2n(uint32(kGroupMagic)); +} + +const CssmData +SSGroupImpl::label() const +{ + return mLabel; +} + +void +SSGroupImpl::decodeDataBlob(const CSSM_DATA &dataBlob, + const CSSM_ACCESS_CREDENTIALS *cred, + Allocator &allocator, CSSM_DATA &data) +{ + // First get the IV and the cipherText from the blob. + CssmData iv(&dataBlob.Data[kLabelSize], kIVSize); + CssmData cipherText(&dataBlob.Data[kLabelSize + kIVSize], + dataBlob.Length - (kLabelSize + kIVSize)); + + CssmDataContainer plainText1(allocator); + CssmDataContainer plainText2(allocator); + // Decrypt the data + // @@@ Don't use staged decrypt once the AppleCSPDL can do combo + // encryption. + // Setup decryption context + Decrypt decrypt(csp(), algorithm()); + decrypt.mode(CSSM_ALGMODE_CBCPadIV8); + decrypt.padding(CSSM_PADDING_PKCS1); + decrypt.initVector(iv); + decrypt.key(Key(this)); + decrypt.cred(AccessCredentials::overlay(cred)); + decrypt.decrypt(&cipherText, 1, &plainText1, 1); + decrypt.final(plainText2); + + // Use DL allocator for allocating memory for data. + CSSM_SIZE length = plainText1.Length + plainText2.Length; + data.Data = allocator.alloc((UInt32)length); + data.Length = length; + memcpy(data.Data, plainText1.Data, plainText1.Length); + memcpy(&data.Data[plainText1.Length], plainText2.Data, plainText2.Length); +} + +void +SSGroupImpl::encodeDataBlob(const CSSM_DATA *data, + const CSSM_ACCESS_CREDENTIALS *cred, + CssmDataContainer &dataBlob) +{ + // Get our csp and set up a random number generation context. + CSP csp(this->csp()); + Random random(csp, CSSM_ALGID_APPLE_YARROW); + + // Encrypt data using key and encode it in a dataBlob. + + // First calculate a random IV. + uint8 ivBuf[kIVSize]; + CssmData iv(ivBuf, kIVSize); + random.generate(iv, kIVSize); + + // Setup encryption context + Encrypt encrypt(csp, algorithm()); + encrypt.mode(CSSM_ALGMODE_CBCPadIV8); + encrypt.padding(CSSM_PADDING_PKCS1); + encrypt.initVector(iv); + encrypt.key(Key(this)); + encrypt.cred(AccessCredentials::overlay(cred)); + + // Encrypt the data + const CssmData nothing; + const CssmData *plainText = data ? CssmData::overlay(data) : ¬hing; + // @@@ Don't use staged encrypt once the AppleCSPDL can do combo + // encryption. + CssmDataContainer cipherText1, cipherText2; + encrypt.encrypt(plainText, 1, &cipherText1, 1); + encrypt.final(cipherText2); + + // Create a dataBlob containing the label followed by the IV followed + // by the cipherText. + CSSM_SIZE length = (kLabelSize + kIVSize + + cipherText1.Length + cipherText2.Length); + dataBlob.Data = dataBlob.mAllocator.alloc((UInt32)length); + dataBlob.Length = length; + memcpy(dataBlob.Data, mLabel.Data, kLabelSize); + memcpy(&dataBlob.Data[kLabelSize], iv.Data, kIVSize); + memcpy(&dataBlob.Data[kLabelSize + kIVSize], + cipherText1.Data, cipherText1.Length); + memcpy(&dataBlob.Data[kLabelSize + kIVSize + cipherText1.Length], + cipherText2.Data, cipherText2.Length); +} + + +// +// SSDbCursorImpl -- Secure Storage Database Cursor Implementation. +// +SSDbCursorImpl::SSDbCursorImpl(const Db &db, const CSSM_QUERY &query, + Allocator &allocator) +: DbDbCursorImpl(db, query, allocator) +{ +} + +SSDbCursorImpl::SSDbCursorImpl(const Db &db, uint32 capacity, + Allocator &allocator) +: DbDbCursorImpl(db, capacity, allocator) +{ +} + +bool +SSDbCursorImpl::next(DbAttributes *attributes, ::CssmDataContainer *data, + DbUniqueRecord &uniqueId) +{ + return next(attributes, data, uniqueId, NULL); +} + +bool +SSDbCursorImpl::next(DbAttributes *attributes, ::CssmDataContainer *data, + DbUniqueRecord &uniqueId, + const CSSM_ACCESS_CREDENTIALS *cred) +{ + if (!data) + return DbDbCursorImpl::next(attributes, data, uniqueId); + + DbAttributes noAttrs, *attrs; + attrs = attributes ? attributes : &noAttrs; + + // Get the datablob for this record + CssmDataContainer dataBlob(allocator()); + for (;;) + { + if (!DbDbCursorImpl::next(attrs, &dataBlob, uniqueId)) + return false; + + // Keep going until we find a non key type record. + CSSM_DB_RECORDTYPE rt = attrs->recordType(); + if (rt != CSSM_DL_DB_RECORD_SYMMETRIC_KEY + && rt != CSSM_DL_DB_RECORD_PRIVATE_KEY + && rt != CSSM_DL_DB_RECORD_PUBLIC_KEY) + { + // @@@ Check the label and if it doesn't start with the magic for a SSKey return the key. + break; + } + else + { + // Free the key we just retrieved + database()->csp()->freeKey(*reinterpret_cast(dataBlob.Data)); + } + } + + if (!SSGroupImpl::isGroup(dataBlob)) + { + data->Data = dataBlob.Data; + data->Length = dataBlob.Length; + dataBlob.Data = NULL; + dataBlob.Length = 0; + return true; + } + + // Get the group for dataBlob + SSGroup group(database(), dataBlob); + + // Decode the dataBlob, pass in the DL allocator. + group->decodeDataBlob(dataBlob, cred, database()->allocator(), *data); + return true; +} + +bool +SSDbCursorImpl::nextKey(DbAttributes *attributes, Key &key, + DbUniqueRecord &uniqueId) +{ + DbAttributes noAttrs, *attrs; + attrs = attributes ? attributes : &noAttrs; + CssmDataContainer keyData(database()->allocator()); + for (;;) + { + if (!DbDbCursorImpl::next(attrs, &keyData, uniqueId)) + return false; + // Keep going until we find a key type record. + CSSM_DB_RECORDTYPE rt = attrs->recordType(); + if (rt == CSSM_DL_DB_RECORD_SYMMETRIC_KEY + || rt == CSSM_DL_DB_RECORD_PRIVATE_KEY + || rt == CSSM_DL_DB_RECORD_PUBLIC_KEY) + break; + } + + key = Key(database()->csp(), *reinterpret_cast(keyData.Data)); + return true; +} + +void +SSDbCursorImpl::activate() +{ + return DbDbCursorImpl::activate(); +} + +void +SSDbCursorImpl::deactivate() +{ + return DbDbCursorImpl::deactivate(); +} + + +// +// SSDbUniqueRecordImpl -- Secure Storage UniqueRecord Implementation. +// +SSDbUniqueRecordImpl::SSDbUniqueRecordImpl(const Db &db) +: DbUniqueRecordImpl(db) +{ +} + +SSDbUniqueRecordImpl::~SSDbUniqueRecordImpl() +{ +} + +void +SSDbUniqueRecordImpl::deleteRecord() +{ + deleteRecord(NULL); +} + +void +SSDbUniqueRecordImpl::deleteRecord(const CSSM_ACCESS_CREDENTIALS *cred) +{ + // Get the datablob for this record + // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get + CssmDataContainer dataBlob(allocator()); + DbAttributes attributes; + + DbUniqueRecordImpl::get(&attributes, &dataBlob); + CSSM_KEY_PTR keyPtr = (CSSM_KEY_PTR) dataBlob.data(); + + // delete data part first: + // (1) don't leave data without keys around + // (2) delete orphaned data anyway + DbUniqueRecordImpl::deleteRecord(); + + // @@@ Use transactions? + if (SSGroupImpl::isGroup(dataBlob)) + try { + // Get the group for dataBlob + SSGroup group(database(), dataBlob); + // Delete the group (key) + group->deleteKey(cred); + } catch (const CssmError &err) { + switch (err.error) { + case CSSMERR_DL_RECORD_NOT_FOUND: + // Zombie item (no group key). Finally at peace! No error + break; + default: + + if (attributes.recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY || + attributes.recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY || + attributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) + { + allocator().free(keyPtr->KeyData.Data); + } + + throw; + } + } + + if (attributes.recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY || + attributes.recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY || + attributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) + { + allocator().free(keyPtr->KeyData.Data); + } +} + +void +SSDbUniqueRecordImpl::modify(CSSM_DB_RECORDTYPE recordType, + const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, + const CSSM_DATA *data, + CSSM_DB_MODIFY_MODE modifyMode) +{ + modify(recordType, attributes, data, modifyMode, NULL); +} + +void +SSDbUniqueRecordImpl::modify(CSSM_DB_RECORDTYPE recordType, + const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, + const CSSM_DATA *data, + CSSM_DB_MODIFY_MODE modifyMode, + const CSSM_ACCESS_CREDENTIALS *cred) +{ + if (!data) + { + DbUniqueRecordImpl::modify(recordType, attributes, NULL, modifyMode); + return; + } + + // Get the datablob for this record + // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get + CssmDataContainer oldDataBlob(allocator()); + DbUniqueRecordImpl::get(NULL, &oldDataBlob); + + if (!SSGroupImpl::isGroup(oldDataBlob)) + { + DbUniqueRecordImpl::modify(recordType, attributes, data, modifyMode); + return; + } + + // Get the group for oldDataBlob + SSGroup group(database(), oldDataBlob); + + // Create a new dataBlob. + CssmDataContainer dataBlob(allocator()); + group->encodeDataBlob(data, cred, dataBlob); + DbUniqueRecordImpl::modify(recordType, attributes, &dataBlob, modifyMode); +} + +void +SSDbUniqueRecordImpl::get(DbAttributes *attributes, ::CssmDataContainer *data) +{ + get(attributes, data, NULL); +} + +void +SSDbUniqueRecordImpl::get(DbAttributes *attributes, ::CssmDataContainer *data, + const CSSM_ACCESS_CREDENTIALS *cred) +{ + if (!data) + { + DbUniqueRecordImpl::get(attributes, NULL); + return; + } + + // Get the datablob for this record + // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get + CssmDataContainer dataBlob(allocator()); + DbUniqueRecordImpl::get(attributes, &dataBlob); + + if (!SSGroupImpl::isGroup(dataBlob)) + { + data->Data = dataBlob.Data; + data->Length = dataBlob.Length; + dataBlob.Data = NULL; + dataBlob.Length = 0; + return; + } + + // Get the group for dataBlob + SSGroup group(database(), dataBlob); + + // Decode the dataBlob, pass in the DL allocator. + group->decodeDataBlob(dataBlob, cred, allocator(), *data); +} + +SSGroup +SSDbUniqueRecordImpl::group() +{ + // Get the datablob for this record + // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get + CssmDataContainer dataBlob(allocator()); + DbUniqueRecordImpl::get(NULL, &dataBlob); + return SSGroup(database(), dataBlob); +}