#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCK_H_ #include "../../interface/Block.h" #include #include "../../interface/BlockStore.h" #include "messmer/cpp-utils/macros.h" #include #include #include #include #include #include #include namespace blockstore { namespace encrypted { template class EncryptedBlockStore; //TODO Test EncryptedBlock template class EncryptedBlock: public Block { public: BOOST_CONCEPT_ASSERT((cpputils::CipherConcept)); static boost::optional> TryCreateNew(BlockStore *baseBlockStore, const Key &key, cpputils::Data data, const typename Cipher::EncryptionKey &encKey); static boost::optional> TryDecrypt(cpputils::unique_ref baseBlock, const typename Cipher::EncryptionKey &key); //TODO Storing key twice (in parent class and in object pointed to). Once would be enough. EncryptedBlock(cpputils::unique_ref baseBlock, const typename Cipher::EncryptionKey &key, cpputils::Data plaintextWithHeader); virtual ~EncryptedBlock(); const void *data() const override; void write(const void *source, uint64_t offset, uint64_t count) override; void flush() override; size_t size() const override; cpputils::unique_ref releaseBlock(); private: cpputils::unique_ref _baseBlock; cpputils::Data _plaintextWithHeader; typename Cipher::EncryptionKey _encKey; bool _dataChanged; static constexpr unsigned int HEADER_LENGTH = Key::BINARY_LENGTH; void _encryptToBaseBlock(); static cpputils::Data _prependKeyHeaderToData(const Key &key, cpputils::Data data); static bool _keyHeaderIsCorrect(const Key &key, const cpputils::Data &data); std::mutex _mutex; DISALLOW_COPY_AND_ASSIGN(EncryptedBlock); }; template constexpr unsigned int EncryptedBlock::HEADER_LENGTH; template boost::optional>> EncryptedBlock::TryCreateNew(BlockStore *baseBlockStore, const Key &key, cpputils::Data data, const typename Cipher::EncryptionKey &encKey) { cpputils::Data plaintextWithHeader = _prependKeyHeaderToData(key, std::move(data)); cpputils::Data encrypted = Cipher::encrypt((byte*)plaintextWithHeader.data(), plaintextWithHeader.size(), encKey); auto baseBlock = baseBlockStore->tryCreate(key, std::move(encrypted)); if (baseBlock == boost::none) { //TODO Test this code branch return boost::none; } return cpputils::make_unique_ref(std::move(*baseBlock), encKey, std::move(plaintextWithHeader)); } template boost::optional>> EncryptedBlock::TryDecrypt(cpputils::unique_ref baseBlock, const typename Cipher::EncryptionKey &encKey) { //TODO Change BlockStore so we can read their "class Data" objects instead of "void *data()", and then we can change the Cipher interface to take Data objects instead of "byte *" + size boost::optional plaintextWithHeader = Cipher::decrypt((byte*)baseBlock->data(), baseBlock->size(), encKey); if(plaintextWithHeader == boost::none) { //Decryption failed (e.g. an authenticated cipher detected modifications to the ciphertext) cpputils::logging::LOG(cpputils::logging::WARN) << "Decrypting block " << baseBlock->key().ToString() << " failed. Was the block modified by an attacker?"; return boost::none; } if(!_keyHeaderIsCorrect(baseBlock->key(), *plaintextWithHeader)) { //The stored key in the block data is incorrect - an attacker might have exchanged the contents with the encrypted data from a different block cpputils::logging::LOG(cpputils::logging::WARN) << "Decrypting block " << baseBlock->key().ToString() << " failed due to invalid block key. Was the block modified by an attacker?"; return boost::none; } return cpputils::make_unique_ref>(std::move(baseBlock), encKey, std::move(*plaintextWithHeader)); } template cpputils::Data EncryptedBlock::_prependKeyHeaderToData(const Key &key, cpputils::Data data) { static_assert(HEADER_LENGTH >= Key::BINARY_LENGTH, "Key doesn't fit into the header"); cpputils::Data result(data.size() + HEADER_LENGTH); std::memcpy(result.data(), key.data(), Key::BINARY_LENGTH); std::memcpy((uint8_t*)result.data() + Key::BINARY_LENGTH, data.data(), data.size()); return result; } template bool EncryptedBlock::_keyHeaderIsCorrect(const Key &key, const cpputils::Data &data) { return 0 == std::memcmp(key.data(), data.data(), Key::BINARY_LENGTH); } template EncryptedBlock::EncryptedBlock(cpputils::unique_ref baseBlock, const typename Cipher::EncryptionKey &encKey, cpputils::Data plaintextWithHeader) :Block(baseBlock->key()), _baseBlock(std::move(baseBlock)), _plaintextWithHeader(std::move(plaintextWithHeader)), _encKey(encKey), _dataChanged(false), _mutex() { } template EncryptedBlock::~EncryptedBlock() { std::unique_lock lock(_mutex); _encryptToBaseBlock(); } template const void *EncryptedBlock::data() const { return (uint8_t*)_plaintextWithHeader.data() + HEADER_LENGTH; } template void EncryptedBlock::write(const void *source, uint64_t offset, uint64_t count) { ASSERT(offset <= size() && offset + count <= size(), "Write outside of valid area"); //Also check offset < size() because of possible overflow in the addition std::memcpy((uint8_t*)_plaintextWithHeader.data()+HEADER_LENGTH+offset, source, count); _dataChanged = true; } template void EncryptedBlock::flush() { std::unique_lock lock(_mutex); _encryptToBaseBlock(); return _baseBlock->flush(); } template size_t EncryptedBlock::size() const { return _plaintextWithHeader.size() - HEADER_LENGTH; } template void EncryptedBlock::_encryptToBaseBlock() { if (_dataChanged) { cpputils::Data encrypted = Cipher::encrypt((byte*)_plaintextWithHeader.data(), _plaintextWithHeader.size(), _encKey); _baseBlock->write(encrypted.data(), 0, encrypted.size()); _dataChanged = false; } } template cpputils::unique_ref EncryptedBlock::releaseBlock() { std::unique_lock lock(_mutex); _encryptToBaseBlock(); return std::move(_baseBlock); } } } #endif