Use cpputils::Serializer and cpputils::Deserializer instead of std::memcpy
This commit is contained in:
parent
cde1a3b7b9
commit
7cc2205bc4
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user