Use cpputils::Serializer and cpputils::Deserializer instead of std::memcpy

This commit is contained in:
Sebastian Messmer 2015-10-25 13:37:28 +01:00
parent cde1a3b7b9
commit 7cc2205bc4
2 changed files with 92 additions and 91 deletions

View File

@ -2,6 +2,8 @@
#ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGENCRYPTOR_H
#define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGENCRYPTOR_H
#include <messmer/cpp-utils/data/Serializer.h>
#include <messmer/cpp-utils/data/Deserializer.h>
#include <messmer/cpp-utils/data/Data.h>
#include <messmer/cpp-utils/random/Random.h>
#include <messmer/cpp-utils/logging/logging.h>
@ -21,25 +23,22 @@ namespace cryfs {
using ConfigEncryptionKey = DerivedKey<Cipher::EncryptionKey::BINARY_LENGTH>;
static constexpr size_t CONFIG_SIZE = 1024; // Config data is grown to this size before encryption to hide its actual size
//TODO Test that encrypted config data always has the same size, no matter how big the plaintext config data
static cpputils::Data _addPadding(const cpputils::Data &data);
static boost::optional<cpputils::Data> _removePadding(const cpputils::Data &data);
static ConfigEncryptionKey deriveKey(const std::string &password);
static boost::optional<std::pair<ConfigEncryptionKey, cpputils::Data>> decrypt(const cpputils::Data &ciphertext, const std::string &password);
static cpputils::Data encrypt(const cpputils::Data &plaintext, const ConfigEncryptionKey &key);
private:
static boost::optional<std::pair<ConfigEncryptionKey, cpputils::Data>> _decrypt(const std::string &header, const cpputils::Data &serializedKeyConfig, const cpputils::Data &ciphertext, const std::string &password);
static bool _checkHeader(const std::string &header);
static boost::optional<ConfigEncryptionKey> _loadKey(const cpputils::Data &serializedKeyConfig, const std::string &password);
static boost::optional<cpputils::Data> _loadAndDecryptConfigData(const cpputils::Data &ciphertext, const typename Cipher::EncryptionKey &key);
//TODO Test that encrypted config data always has the same size, no matter how big the plaintext config data
static cpputils::Data _addPadding(const cpputils::Data &data);
static boost::optional<cpputils::Data> _removePadding(const cpputils::Data &data);
static boost::optional<std::pair<ConfigEncryptionKey, cpputils::Data>> _decrypt(std::stringstream &stream, const std::string &password);
static bool _checkHeader(std::stringstream &stream);
static boost::optional<ConfigEncryptionKey> _loadKey(std::stringstream &stream, const std::string &password);
static std::streampos _getStreamSize(std::istream &stream);
static boost::optional<cpputils::Data> _loadAndDecryptData(std::stringstream &stream, const typename Cipher::EncryptionKey &key);
static const std::string header;
static const std::string HEADER;
};
template<class Cipher> const std::string CryConfigEncryptor<Cipher>::header = "scrypt";
template<class Cipher> const std::string CryConfigEncryptor<Cipher>::HEADER = "scrypt";
template<class Cipher>
typename CryConfigEncryptor<Cipher>::ConfigEncryptionKey CryConfigEncryptor<Cipher>::deriveKey(const std::string &password) {
@ -52,57 +51,59 @@ namespace cryfs {
template<class Cipher>
boost::optional<std::pair<typename CryConfigEncryptor<Cipher>::ConfigEncryptionKey, cpputils::Data>>
CryConfigEncryptor<Cipher>::decrypt(const cpputils::Data &ciphertext, const std::string &password) {
std::stringstream stream;
ciphertext.StoreToStream(stream);
return _decrypt(stream, password);
CryConfigEncryptor<Cipher>::decrypt(const cpputils::Data &data, const std::string &password) {
try {
cpputils::Deserializer deserializer(&data);
std::string header = deserializer.readString();
cpputils::Data serializedKeyConfig = deserializer.readData();
cpputils::Data ciphertext = deserializer.readData();
deserializer.finished();
return _decrypt(header, serializedKeyConfig, ciphertext, password);
} catch (const std::exception &e) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Error deserializing CryConfigEncryptor: " << e.what();
return boost::none; // This can be caused by bad loaded data and is not necessarily a programming logic error. Don't throw exception.
}
};
template<class Cipher>
boost::optional<std::pair<typename CryConfigEncryptor<Cipher>::ConfigEncryptionKey, cpputils::Data>>
CryConfigEncryptor<Cipher>::_decrypt(std::stringstream &stream, const std::string &password) {
if(!_checkHeader(stream)) {
CryConfigEncryptor<Cipher>::_decrypt(const std::string &header, const cpputils::Data &serializedKeyConfig, const cpputils::Data &ciphertext, const std::string &password) {
if (!_checkHeader(header)) {
return boost::none;
}
auto key = _loadKey(stream, password);
auto key = _loadKey(serializedKeyConfig, password);
if (key == boost::none) {
return boost::none;
}
auto plaintext = _loadAndDecryptData(stream, key->key());
if (plaintext == boost::none) {
auto configData = _loadAndDecryptConfigData(ciphertext, key->key());
if (configData == boost::none) {
return boost::none;
}
return std::make_pair(std::move(*key), std::move(*plaintext));
return std::make_pair(std::move(*key), std::move(*configData));
};
template<class Cipher>
bool CryConfigEncryptor<Cipher>::_checkHeader(std::stringstream &stream) {
char readHeader[header.size()+1];
stream.read(readHeader, header.size()+1);
bool headerOk = readHeader[header.size()] == '\0' && header == std::string(readHeader);
if (!headerOk) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Wrong config file format";
bool CryConfigEncryptor<Cipher>::_checkHeader(const std::string &header) {
if (header != HEADER) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Error deserializing CryConfigEncryptor: Invalid header.";
return false;
}
return headerOk;
return true;
}
template<class Cipher>
boost::optional<typename CryConfigEncryptor<Cipher>::ConfigEncryptionKey> CryConfigEncryptor<Cipher>::_loadKey(std::stringstream &stream, const std::string &password) {
uint32_t serializedKeyConfigSize;
uint32_t streamSize = _getStreamSize(stream);
if(streamSize < sizeof(serializedKeyConfigSize)) {
return boost::none;
}
stream.read(reinterpret_cast<char*>(&serializedKeyConfigSize), sizeof(serializedKeyConfigSize));
if((streamSize-sizeof(serializedKeyConfigSize)) < serializedKeyConfigSize) {
return boost::none;
}
cpputils::Data serializedKeyConfig = cpputils::Data::LoadFromStream(stream, serializedKeyConfigSize);
boost::optional<typename CryConfigEncryptor<Cipher>::ConfigEncryptionKey> CryConfigEncryptor<Cipher>::_loadKey(const cpputils::Data &serializedKeyConfig, const std::string &password) {
auto keyConfig = DerivedKeyConfig::load(serializedKeyConfig);
if (keyConfig == boost::none) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Error deserializing CryConfigEncryptor: Invalid key configuration.";
return boost::none;
}
//TODO This is only kept here to recognize when this is run in tests. After tests are faster, replace this with something in main(), saying something like "Loading configuration file..."
std::cout << "Deriving secure key for config file..." << std::flush;
auto key = SCrypt().generateKeyFromConfig<Cipher::EncryptionKey::BINARY_LENGTH>(password, *keyConfig);
std::cout << "done" << std::endl;
@ -110,42 +111,37 @@ namespace cryfs {
}
template<class Cipher>
std::streampos CryConfigEncryptor<Cipher>::_getStreamSize(std::istream &stream) {
auto current_pos = stream.tellg();
//Retrieve length
stream.seekg(0, stream.end);
auto endpos = stream.tellg();
//Restore old position
stream.seekg(current_pos, stream.beg);
return endpos - current_pos;
}
template<class Cipher>
boost::optional<cpputils::Data> CryConfigEncryptor<Cipher>::_loadAndDecryptData(std::stringstream &stream, const typename Cipher::EncryptionKey &key) {
auto data = cpputils::Data::LoadFromStream(stream);
auto decrypted = Cipher::decrypt(static_cast<const uint8_t*>(data.data()), data.size(), key);
boost::optional<cpputils::Data> CryConfigEncryptor<Cipher>::_loadAndDecryptConfigData(const cpputils::Data &ciphertext, const typename Cipher::EncryptionKey &key) {
auto decrypted = Cipher::decrypt(static_cast<const uint8_t*>(ciphertext.data()), ciphertext.size(), key);
if (decrypted == boost::none) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Couldn't load config file. Wrong password?";
cpputils::logging::LOG(cpputils::logging::ERROR) << "Couldn't decrypt config file. Wrong password?";
return boost::none;
}
return _removePadding(*decrypted);
auto configData = _removePadding(*decrypted);
if (configData == boost::none) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Couldn't decrypt config file because of wrong padding.";
return boost::none;
}
return configData;
}
template<class Cipher>
cpputils::Data CryConfigEncryptor<Cipher>::encrypt(const cpputils::Data &plaintext, const ConfigEncryptionKey &key) {
std::stringstream stream;
stream.write(header.c_str(), header.size()+1);
cpputils::Data serializedKeyConfig = key.config().save();
uint32_t serializedKeyConfigSize = serializedKeyConfig.size();
stream.write(reinterpret_cast<const char*>(&serializedKeyConfigSize), sizeof(serializedKeyConfigSize));
serializedKeyConfig.StoreToStream(stream);
auto paddedPlaintext = _addPadding(plaintext);
auto ciphertext = Cipher::encrypt(static_cast<const uint8_t*>(paddedPlaintext.data()), paddedPlaintext.size(), key.key());
ciphertext.StoreToStream(stream);
return cpputils::Data::LoadFromStream(stream);
try {
cpputils::Serializer serializer(cpputils::Serializer::StringSize(HEADER)
+ cpputils::Serializer::DataSize(serializedKeyConfig)
+ cpputils::Serializer::DataSize(ciphertext));
serializer.writeString(HEADER);
serializer.writeData(serializedKeyConfig);
serializer.writeData(ciphertext);
return serializer.finished();
} catch (const std::exception &e) {
cpputils::logging::LOG(cpputils::logging::ERROR) << "Error serializing CryConfigEncryptor: " << e.what();
throw; // This is a programming logic error. Pass through exception.
}
}
template<class Cipher>

View File

@ -1,42 +1,47 @@
#include "DerivedKeyConfig.h"
#include <messmer/cpp-utils/assert/assert.h>
#include <messmer/cpp-utils/logging/logging.h>
#include <messmer/cpp-utils/data/Serializer.h>
#include <messmer/cpp-utils/data/Deserializer.h>
using std::istream;
using std::ostream;
using cpputils::Data;
using cpputils::Serializer;
using cpputils::Deserializer;
using boost::optional;
using boost::none;
using namespace cpputils::logging;
namespace cryfs {
Data DerivedKeyConfig::save() const {
uint8_t saltSize = _salt.size();
ASSERT(saltSize < std::numeric_limits<uint8_t>::max(), "We don't support salts bigger than 256 byte");
Data result(sizeof(saltSize) + saltSize + sizeof(_N) + sizeof(_r) + sizeof(_p));
//TODO Write and use a DataSerializer class with DataSerializer::write<uint8_t> and so on instead of all these memcpy(reinterpret_cast).
std::memcpy(reinterpret_cast<char*>(result.data()), reinterpret_cast<const char*>(&saltSize), sizeof(saltSize));
std::memcpy(reinterpret_cast<char*>(result.dataOffset(sizeof(saltSize))), reinterpret_cast<const char*>(_salt.data()), saltSize);
std::memcpy(reinterpret_cast<char*>(result.dataOffset(sizeof(saltSize)+saltSize)), reinterpret_cast<const char*>(&_N), sizeof(_N));
std::memcpy(reinterpret_cast<char*>(result.dataOffset(sizeof(saltSize)+saltSize+sizeof(_N))), reinterpret_cast<const char*>(&_r), sizeof(_r));
std::memcpy(reinterpret_cast<char*>(result.dataOffset(sizeof(saltSize)+saltSize+sizeof(_N)+sizeof(_r))), reinterpret_cast<const char*>(&_p), sizeof(_p));
return result;
Serializer serializer(Serializer::DataSize(_salt) + sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t));
try {
serializer.writeData(_salt);
serializer.writeUint64(_N);
serializer.writeUint32(_r);
serializer.writeUint32(_p);
return serializer.finished();
} catch (const std::exception &e) {
LOG(ERROR) << "Error when trying to serialize DerivedKeyConfig: " << e.what();
//This is a programming logic error. Pass-through exception.
throw;
}
}
boost::optional<DerivedKeyConfig> DerivedKeyConfig::load(const Data &data) {
uint8_t saltSize;
//TODO Write and use a DataDeserializer class instead of all these memcpy(reinterpret_cast).
std::memcpy(reinterpret_cast<char*>(&saltSize), reinterpret_cast<const char*>(data.data()), sizeof(saltSize));
Data salt(saltSize);
if (sizeof(saltSize) + saltSize + sizeof(_N) + sizeof(_p) + sizeof(_r) != data.size()) {
LOG(ERROR) << "Could not load DerivedKeyConfig. Wrong size.";
return boost::none;
Deserializer deserializer(&data);
try {
Data salt = deserializer.readData();
uint64_t N = deserializer.readUint64();
uint32_t r = deserializer.readUint32();
uint32_t p = deserializer.readUint32();
deserializer.finished();
return DerivedKeyConfig(std::move(salt), N, r, p);
} catch (const std::exception &e) {
LOG(ERROR) << "Error when trying to deserialize DerivedKeyConfig: " << e.what();
//This might be caused by invalid data loaded and does not have to be a programming logic error. Don't throw exception.
return none;
}
std::memcpy(reinterpret_cast<char*>(salt.data()), reinterpret_cast<const char*>(data.dataOffset(sizeof(saltSize))), saltSize);
decltype(_N) N;
std::memcpy(reinterpret_cast<char*>(&N), reinterpret_cast<const char*>(data.dataOffset(sizeof(saltSize)+saltSize)), sizeof(_N));
decltype(_r) r;
std::memcpy(reinterpret_cast<char*>(&r), reinterpret_cast<const char*>(data.dataOffset(sizeof(saltSize)+saltSize+sizeof(_N))), sizeof(_r));
decltype(_p) p;
std::memcpy(reinterpret_cast<char*>(&p), reinterpret_cast<const char*>(data.dataOffset(sizeof(saltSize)+saltSize+sizeof(_N)+sizeof(_r))), sizeof(_p));
return DerivedKeyConfig(std::move(salt), N, r, p);
}
}