/* * 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); }