- Run test cases for EncryptedBlockStore with different ciphers
- Implement FakeAuthenticatedCipher for use with specific EncryptedBlockStoreTest - Write skeleton for specific EncryptedBlockStoreTest - Fix behavior of AES256_CFB when called with too small input - Add testcase that all ciphers (also non-authenticating ones) have to handle too small input correctly
This commit is contained in:
parent
a36ab8e2d7
commit
0042ae1cef
@ -21,6 +21,10 @@ Data AES256_CFB::encrypt(const byte *plaintext, unsigned int plaintextSize, cons
|
||||
}
|
||||
|
||||
boost::optional<Data> AES256_CFB::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
|
||||
if (ciphertextSize < IV_SIZE) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
const byte *ciphertextIV = ciphertext;
|
||||
const byte *ciphertextData = ciphertext + IV_SIZE;
|
||||
auto decryption = CFB_Mode<AES>::Decryption((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <boost/concept_check.hpp>
|
||||
#include <cstdint>
|
||||
#include <messmer/cpp-utils/data/Data.h>
|
||||
|
||||
namespace blockstore {
|
||||
namespace encrypted {
|
||||
@ -15,8 +16,8 @@ public:
|
||||
same_type(UINT32_C(0), X::ciphertextSize(UINT32_C(5)));
|
||||
same_type(UINT32_C(0), X::plaintextSize(UINT32_C(5)));
|
||||
typename X::EncryptionKey key = X::EncryptionKey::CreateRandom();
|
||||
same_type(cpputils::Data(0), X::encrypt((byte*)nullptr, UINT32_C(0), key));
|
||||
same_type(boost::optional<cpputils::Data>(cpputils::Data(0)), X::decrypt((byte*)nullptr, UINT32_C(0), key));
|
||||
same_type(cpputils::Data(0), X::encrypt((uint8_t*)nullptr, UINT32_C(0), key));
|
||||
same_type(boost::optional<cpputils::Data>(cpputils::Data(0)), X::decrypt((uint8_t*)nullptr, UINT32_C(0), key));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1,7 +1,8 @@
|
||||
#include <google/gtest/gtest.h>
|
||||
#include "../../../implementations/encrypted/ciphers/Cipher.h"
|
||||
#include "../../../implementations/encrypted/ciphers/AES256_CFB.h"
|
||||
#include "../../../implementations/encrypted/ciphers/AES256_GCM.h"
|
||||
#include "../../../implementations/encrypted/ciphers/Cipher.h"
|
||||
#include "testutils/FakeAuthenticatedCipher.h"
|
||||
|
||||
#include <messmer/cpp-utils/data/DataFixture.h>
|
||||
#include <messmer/cpp-utils/data/Data.h>
|
||||
@ -16,9 +17,9 @@ template<class Cipher>
|
||||
class CipherTest: public ::testing::Test {
|
||||
public:
|
||||
BOOST_CONCEPT_ASSERT((CipherConcept<Cipher>));
|
||||
typename Cipher::EncryptionKey encKey = createRandomKey();
|
||||
typename Cipher::EncryptionKey encKey = createKeyFixture();
|
||||
|
||||
static typename Cipher::EncryptionKey createRandomKey(int seed = 0) {
|
||||
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
|
||||
Data data = DataFixture::generate(Cipher::EncryptionKey::BINARY_LENGTH, seed);
|
||||
return Cipher::EncryptionKey::FromBinary(data.data());
|
||||
}
|
||||
@ -40,6 +41,11 @@ public:
|
||||
EXPECT_EQ(Cipher::ciphertextSize(plaintext.size()), ciphertext.size());
|
||||
}
|
||||
|
||||
void ExpectDoesntDecrypt(const Data &ciphertext) {
|
||||
auto decrypted = Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey);
|
||||
EXPECT_FALSE(decrypted);
|
||||
}
|
||||
|
||||
Data Encrypt(const Data &plaintext) {
|
||||
return Cipher::encrypt((byte*)plaintext.data(), plaintext.size(), this->encKey);
|
||||
}
|
||||
@ -103,23 +109,38 @@ TYPED_TEST_P(CipherTest, EncryptedSize) {
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsTooSmall) {
|
||||
Data tooSmallCiphertext(TypeParam::ciphertextSize(0) - 1);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_0) {
|
||||
static_assert(TypeParam::ciphertextSize(0) > 0, "If this fails, the test case doesn't make sense.");
|
||||
Data tooSmallCiphertext(0);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_1) {
|
||||
static_assert(TypeParam::ciphertextSize(0) > 1, "If this fails, the test case doesn't make sense.");
|
||||
Data tooSmallCiphertext(1);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_CASE_P(CipherTest,
|
||||
Size,
|
||||
EncryptThenDecrypt_Zeroes,
|
||||
EncryptThenDecrypt_Data,
|
||||
EncryptIsIndeterministic_Zeroes,
|
||||
EncryptIsIndeterministic_Data,
|
||||
EncryptedSize
|
||||
EncryptedSize,
|
||||
TryDecryptDataThatIsTooSmall,
|
||||
TryDecryptDataThatIsMuchTooSmall_0,
|
||||
TryDecryptDataThatIsMuchTooSmall_1
|
||||
);
|
||||
|
||||
template<class Cipher>
|
||||
class AuthenticatedCipherTest: public CipherTest<Cipher> {
|
||||
public:
|
||||
void ExpectDoesntDecrypt(const Data &ciphertext) {
|
||||
auto decrypted = Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey);
|
||||
EXPECT_FALSE(decrypted);
|
||||
}
|
||||
|
||||
Data zeroes1 = CipherTest<Cipher>::CreateZeroes(1);
|
||||
Data plaintext1 = CipherTest<Cipher>::CreateData(1);
|
||||
Data zeroes2 = CipherTest<Cipher>::CreateZeroes(100 * 1024);
|
||||
@ -184,23 +205,6 @@ TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptRandomData) {
|
||||
this->ExpectDoesntDecrypt(this->plaintext2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptDataThatIsTooSmall) {
|
||||
Data tooSmallCiphertext(TypeParam::ciphertextSize(0) - 1);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptDataThatIsMuchTooSmall_0) {
|
||||
static_assert(TypeParam::ciphertextSize(0) > 0, "If this fails, the test case doesn't make sense.");
|
||||
Data tooSmallCiphertext(0);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptDataThatIsMuchTooSmall_1) {
|
||||
static_assert(TypeParam::ciphertextSize(0) > 1, "If this fails, the test case doesn't make sense.");
|
||||
Data tooSmallCiphertext(1);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest,
|
||||
ModifyFirstByte_Zeroes_Size1,
|
||||
ModifyFirstByte_Zeroes,
|
||||
@ -211,13 +215,12 @@ REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest,
|
||||
ModifyMiddleByte_Zeroes,
|
||||
ModifyMiddleByte_Data,
|
||||
TryDecryptZeroesData,
|
||||
TryDecryptRandomData,
|
||||
TryDecryptDataThatIsTooSmall,
|
||||
TryDecryptDataThatIsMuchTooSmall_0,
|
||||
TryDecryptDataThatIsMuchTooSmall_1
|
||||
TryDecryptRandomData
|
||||
);
|
||||
|
||||
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB);
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Fake, CipherTest, FakeAuthenticatedCipher);
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher);
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, CipherTest, AES256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM);
|
||||
|
@ -1,28 +0,0 @@
|
||||
#include "../../../implementations/encrypted/ciphers/AES256_GCM.h"
|
||||
#include "../../../implementations/encrypted/EncryptedBlockStore.h"
|
||||
#include "../../../implementations/testfake/FakeBlockStore.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "google/gtest/gtest.h"
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::encrypted::EncryptedBlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using blockstore::encrypted::AES256_GCM;
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
|
||||
class EncryptedBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ptr<BlockStore> createBlockStore() override {
|
||||
return make_unique<EncryptedBlockStore<AES256_GCM>>(make_unique<FakeBlockStore>(), AES256_GCM::EncryptionKey::FromString("1491BB4932A389EE14BC7090A272EE5517627CFA147A971A8E6E747E0C772972"));
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted, BlockStoreTest, EncryptedBlockStoreTestFixture);
|
||||
|
||||
//TODO Add specific tests, for example
|
||||
// - loading it with a different encKey doesn't work
|
||||
// - loading it with a different blockstore::Key will fail (because it stores its key in a header)
|
||||
// - when using an authenticated cipher, loading a modified block will fail
|
@ -0,0 +1,40 @@
|
||||
#include "../../../implementations/encrypted/ciphers/AES256_GCM.h"
|
||||
#include "../../../implementations/encrypted/ciphers/AES256_CFB.h"
|
||||
#include "../../../implementations/encrypted/ciphers/Cipher.h"
|
||||
#include "../../../implementations/encrypted/EncryptedBlockStore.h"
|
||||
#include "../../../implementations/testfake/FakeBlockStore.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "testutils/FakeAuthenticatedCipher.h"
|
||||
#include "google/gtest/gtest.h"
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::encrypted::EncryptedBlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using blockstore::encrypted::AES256_GCM;
|
||||
using blockstore::encrypted::AES256_CFB;
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
|
||||
template<class Cipher>
|
||||
class EncryptedBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ptr<BlockStore> createBlockStore() override {
|
||||
return make_unique<EncryptedBlockStore<Cipher>>(make_unique<FakeBlockStore>(), createKeyFixture());
|
||||
}
|
||||
|
||||
private:
|
||||
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
|
||||
Data data = DataFixture::generate(Cipher::EncryptionKey::BINARY_LENGTH, seed);
|
||||
return Cipher::EncryptionKey::FromBinary(data.data());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_FakeCipher, BlockStoreTest, EncryptedBlockStoreTestFixture<FakeAuthenticatedCipher>);
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_GCM, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_GCM>);
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_CFB, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_CFB>);
|
@ -0,0 +1,35 @@
|
||||
#include <google/gtest/gtest.h>
|
||||
#include "testutils/FakeAuthenticatedCipher.h"
|
||||
#include "../../../implementations/encrypted/EncryptedBlockStore.h"
|
||||
#include "../../../implementations/testfake/FakeBlockStore.h"
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
|
||||
using namespace blockstore::encrypted;
|
||||
|
||||
class EncryptedBlockStoreTest: public Test {
|
||||
public:
|
||||
EncryptedBlockStoreTest(): blockStore(make_unique<EncryptedBlockStore<FakeAuthenticatedCipher>>(make_unique<FakeBlockStore>(), FakeAuthenticatedCipher::Key1())) {}
|
||||
unique_ptr<EncryptedBlockStore<FakeAuthenticatedCipher>> blockStore;
|
||||
};
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks) {
|
||||
//TODO implement
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork) {
|
||||
//TODO Implement
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails) {
|
||||
//TODO Implement
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentBlockIdFails) {
|
||||
//TODO loading it with a different blockstore::Key will fail (because it stores its key in a header)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
#include "FakeAuthenticatedCipher.h"
|
||||
|
||||
constexpr unsigned int FakeKey::BINARY_LENGTH;
|
@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_ENCRYPTED_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_ENCRYPTED_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_
|
||||
|
||||
#include "../../../../implementations/encrypted/ciphers/Cipher.h"
|
||||
#include <messmer/cpp-utils/data/FixedSizeData.h>
|
||||
|
||||
struct FakeKey {
|
||||
FakeKey(uint8_t value_):value(value_) {}
|
||||
static FakeKey CreateRandom() {
|
||||
return FakeKey(rand());
|
||||
}
|
||||
static FakeKey FromBinary(const void *data) {
|
||||
return FakeKey(*(uint8_t*)data);
|
||||
}
|
||||
static constexpr unsigned int BINARY_LENGTH = 1;
|
||||
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
// This is a fake cipher that uses an indeterministic caesar chiffre and a 4-byte parity for a simple authentication mechanism
|
||||
class FakeAuthenticatedCipher {
|
||||
public:
|
||||
BOOST_CONCEPT_ASSERT((blockstore::encrypted::CipherConcept<FakeAuthenticatedCipher>));
|
||||
|
||||
using EncryptionKey = FakeKey;
|
||||
|
||||
static EncryptionKey Key1() {
|
||||
return FakeKey(5);
|
||||
}
|
||||
static EncryptionKey Key2() {
|
||||
return FakeKey(63);
|
||||
}
|
||||
|
||||
static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) {
|
||||
return plaintextBlockSize + 5;
|
||||
}
|
||||
|
||||
static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) {
|
||||
return ciphertextBlockSize - 5;
|
||||
}
|
||||
|
||||
static cpputils::Data encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) {
|
||||
cpputils::Data result(ciphertextSize(plaintextSize));
|
||||
|
||||
//Add a random IV
|
||||
uint8_t iv = rand();
|
||||
std::memcpy(result.data(), &iv, 1);
|
||||
|
||||
//Use caesar chiffre on plaintext
|
||||
_caesar((byte*)result.data() + 1, plaintext, plaintextSize, encKey.value + iv);
|
||||
|
||||
//Add parity information
|
||||
int32_t parity = _parity((byte*)result.data(), plaintextSize + 1);
|
||||
std::memcpy((byte*)result.data() + plaintextSize + 1, &parity, 4);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static boost::optional<cpputils::Data> decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
|
||||
//We need at least 5 bytes (iv + parity)
|
||||
if (ciphertextSize < 5) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
//Check parity
|
||||
int32_t expectedParity = _parity(ciphertext, plaintextSize(ciphertextSize) + 1);
|
||||
int32_t actualParity = *(int32_t*)(ciphertext + plaintextSize(ciphertextSize) + 1);
|
||||
if (expectedParity != actualParity) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
//Decrypt caesar chiffre from ciphertext
|
||||
int32_t iv = *(int32_t*)ciphertext;
|
||||
cpputils::Data result(plaintextSize(ciphertextSize));
|
||||
_caesar((byte*)result.data(), ciphertext + 1, plaintextSize(ciphertextSize), -(encKey.value+iv));
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
private:
|
||||
static int32_t _parity(const byte *data, unsigned int size) {
|
||||
int32_t parity = 34343435; // some init value
|
||||
int32_t *intData = (int32_t*)data;
|
||||
unsigned int intSize = size / sizeof(int32_t);
|
||||
for (unsigned int i = 0; i < intSize; ++i) {
|
||||
parity += intData[i];
|
||||
}
|
||||
unsigned int remainingBytes = size - 4 * intSize;
|
||||
for (unsigned int i = 0; i < remainingBytes; ++i) {
|
||||
parity += (data[4*intSize + i] << (24 - 8*i));
|
||||
}
|
||||
return parity;
|
||||
}
|
||||
|
||||
static void _caesar(byte *dst, const byte *src, unsigned int size, uint8_t key) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
dst[i] = src[i] + key;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user