- 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:
Sebastian Messmer 2015-05-06 00:09:11 +02:00
parent a36ab8e2d7
commit 0042ae1cef
8 changed files with 221 additions and 61 deletions

View File

@ -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) { 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 *ciphertextIV = ciphertext;
const byte *ciphertextData = ciphertext + IV_SIZE; const byte *ciphertextData = ciphertext + IV_SIZE;
auto decryption = CFB_Mode<AES>::Decryption((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV); auto decryption = CFB_Mode<AES>::Decryption((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV);

View File

@ -4,6 +4,7 @@
#include <boost/concept_check.hpp> #include <boost/concept_check.hpp>
#include <cstdint> #include <cstdint>
#include <messmer/cpp-utils/data/Data.h>
namespace blockstore { namespace blockstore {
namespace encrypted { namespace encrypted {
@ -15,8 +16,8 @@ public:
same_type(UINT32_C(0), X::ciphertextSize(UINT32_C(5))); same_type(UINT32_C(0), X::ciphertextSize(UINT32_C(5)));
same_type(UINT32_C(0), X::plaintextSize(UINT32_C(5))); same_type(UINT32_C(0), X::plaintextSize(UINT32_C(5)));
typename X::EncryptionKey key = X::EncryptionKey::CreateRandom(); typename X::EncryptionKey key = X::EncryptionKey::CreateRandom();
same_type(cpputils::Data(0), X::encrypt((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((byte*)nullptr, UINT32_C(0), key)); same_type(boost::optional<cpputils::Data>(cpputils::Data(0)), X::decrypt((uint8_t*)nullptr, UINT32_C(0), key));
} }
private: private:

View File

@ -1,7 +1,8 @@
#include <google/gtest/gtest.h> #include <google/gtest/gtest.h>
#include "../../../implementations/encrypted/ciphers/Cipher.h"
#include "../../../implementations/encrypted/ciphers/AES256_CFB.h" #include "../../../implementations/encrypted/ciphers/AES256_CFB.h"
#include "../../../implementations/encrypted/ciphers/AES256_GCM.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/DataFixture.h>
#include <messmer/cpp-utils/data/Data.h> #include <messmer/cpp-utils/data/Data.h>
@ -16,9 +17,9 @@ template<class Cipher>
class CipherTest: public ::testing::Test { class CipherTest: public ::testing::Test {
public: public:
BOOST_CONCEPT_ASSERT((CipherConcept<Cipher>)); 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); Data data = DataFixture::generate(Cipher::EncryptionKey::BINARY_LENGTH, seed);
return Cipher::EncryptionKey::FromBinary(data.data()); return Cipher::EncryptionKey::FromBinary(data.data());
} }
@ -40,6 +41,11 @@ public:
EXPECT_EQ(Cipher::ciphertextSize(plaintext.size()), ciphertext.size()); 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) { Data Encrypt(const Data &plaintext) {
return Cipher::encrypt((byte*)plaintext.data(), plaintext.size(), this->encKey); 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, REGISTER_TYPED_TEST_CASE_P(CipherTest,
Size, Size,
EncryptThenDecrypt_Zeroes, EncryptThenDecrypt_Zeroes,
EncryptThenDecrypt_Data, EncryptThenDecrypt_Data,
EncryptIsIndeterministic_Zeroes, EncryptIsIndeterministic_Zeroes,
EncryptIsIndeterministic_Data, EncryptIsIndeterministic_Data,
EncryptedSize EncryptedSize,
TryDecryptDataThatIsTooSmall,
TryDecryptDataThatIsMuchTooSmall_0,
TryDecryptDataThatIsMuchTooSmall_1
); );
template<class Cipher> template<class Cipher>
class AuthenticatedCipherTest: public CipherTest<Cipher> { class AuthenticatedCipherTest: public CipherTest<Cipher> {
public: 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 zeroes1 = CipherTest<Cipher>::CreateZeroes(1);
Data plaintext1 = CipherTest<Cipher>::CreateData(1); Data plaintext1 = CipherTest<Cipher>::CreateData(1);
Data zeroes2 = CipherTest<Cipher>::CreateZeroes(100 * 1024); Data zeroes2 = CipherTest<Cipher>::CreateZeroes(100 * 1024);
@ -184,23 +205,6 @@ TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptRandomData) {
this->ExpectDoesntDecrypt(this->plaintext2); 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, REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest,
ModifyFirstByte_Zeroes_Size1, ModifyFirstByte_Zeroes_Size1,
ModifyFirstByte_Zeroes, ModifyFirstByte_Zeroes,
@ -211,13 +215,12 @@ REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest,
ModifyMiddleByte_Zeroes, ModifyMiddleByte_Zeroes,
ModifyMiddleByte_Data, ModifyMiddleByte_Data,
TryDecryptZeroesData, TryDecryptZeroesData,
TryDecryptRandomData, TryDecryptRandomData
TryDecryptDataThatIsTooSmall,
TryDecryptDataThatIsMuchTooSmall_0,
TryDecryptDataThatIsMuchTooSmall_1
); );
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, CipherTest, AES256_GCM);
INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM);

View File

@ -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

View File

@ -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>);

View File

@ -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)
}

View File

@ -0,0 +1,3 @@
#include "FakeAuthenticatedCipher.h"
constexpr unsigned int FakeKey::BINARY_LENGTH;

View File

@ -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