- Added test cases for authenticated ciphers
- Fixed corner case for AES256_GCM when decrypt is called on data that can't hold IV and TAG
This commit is contained in:
parent
0335b243fb
commit
e056a65b48
@ -8,6 +8,7 @@ using CryptoPP::AuthenticatedDecryptionFilter;
|
|||||||
using CryptoPP::ArraySource;
|
using CryptoPP::ArraySource;
|
||||||
using CryptoPP::ArraySink;
|
using CryptoPP::ArraySink;
|
||||||
using CryptoPP::GCM_64K_Tables;
|
using CryptoPP::GCM_64K_Tables;
|
||||||
|
using CryptoPP::HashVerificationFilter;
|
||||||
|
|
||||||
namespace blockstore {
|
namespace blockstore {
|
||||||
namespace encrypted {
|
namespace encrypted {
|
||||||
@ -31,18 +32,26 @@ Data AES256_GCM::encrypt(const byte *plaintext, unsigned int plaintextSize, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<Data> AES256_GCM::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
|
boost::optional<Data> AES256_GCM::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
|
||||||
|
if (ciphertextSize < IV_SIZE + TAG_SIZE) {
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
const byte *ciphertextIV = ciphertext;
|
const byte *ciphertextIV = ciphertext;
|
||||||
const byte *ciphertextData = ciphertext + IV_SIZE;
|
const byte *ciphertextData = ciphertext + IV_SIZE;
|
||||||
GCM<AES, GCM_64K_Tables>::Decryption decryption;
|
GCM<AES, GCM_64K_Tables>::Decryption decryption;
|
||||||
decryption.SetKeyWithIV((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV, IV_SIZE);
|
decryption.SetKeyWithIV((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV, IV_SIZE);
|
||||||
Data plaintext(plaintextSize(ciphertextSize));
|
Data plaintext(plaintextSize(ciphertextSize));
|
||||||
|
|
||||||
ArraySource((byte*)ciphertextData, ciphertextSize - IV_SIZE, true,
|
try {
|
||||||
new AuthenticatedDecryptionFilter(decryption,
|
ArraySource((byte*)ciphertextData, ciphertextSize - IV_SIZE, true,
|
||||||
new ArraySink((byte*)plaintext.data(), plaintext.size())
|
new AuthenticatedDecryptionFilter(decryption,
|
||||||
)
|
new ArraySink((byte*)plaintext.data(), plaintext.size())
|
||||||
);
|
)
|
||||||
return std::move(plaintext);
|
);
|
||||||
|
return std::move(plaintext);
|
||||||
|
} catch (const HashVerificationFilter::HashVerificationFailed &e) {
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include "../../testutils/DataBlockFixture.h"
|
#include "../../testutils/DataBlockFixture.h"
|
||||||
#include "../../../utils/Data.h"
|
#include "../../../utils/Data.h"
|
||||||
|
|
||||||
|
#include <boost/optional/optional_io.hpp>
|
||||||
|
|
||||||
using namespace blockstore::encrypted;
|
using namespace blockstore::encrypted;
|
||||||
using blockstore::Data;
|
using blockstore::Data;
|
||||||
|
|
||||||
@ -46,13 +48,13 @@ public:
|
|||||||
return Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey).value();
|
return Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
Data CreateZeroes(unsigned int size) {
|
static Data CreateZeroes(unsigned int size) {
|
||||||
Data zeroes(size);
|
Data zeroes(size);
|
||||||
zeroes.FillWithZeroes();
|
zeroes.FillWithZeroes();
|
||||||
return zeroes;
|
return zeroes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Data CreateData(unsigned int size, unsigned int seed = 0) {
|
static Data CreateData(unsigned int size, unsigned int seed = 0) {
|
||||||
DataBlockFixture data(size, seed);
|
DataBlockFixture data(size, seed);
|
||||||
Data result(size);
|
Data result(size);
|
||||||
std::memcpy(result.data(), data.data(), size);
|
std::memcpy(result.data(), data.data(), size);
|
||||||
@ -115,7 +117,112 @@ REGISTER_TYPED_TEST_CASE_P(CipherTest,
|
|||||||
EncryptedSize
|
EncryptedSize
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO For authenticated ciphers, we need test cases checking that authentication fails on manipulations
|
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);
|
||||||
|
Data plaintext2 = CipherTest<Cipher>::CreateData(100 * 1024);
|
||||||
|
};
|
||||||
|
|
||||||
|
TYPED_TEST_CASE_P(AuthenticatedCipherTest);
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes_Size1) {
|
||||||
|
Data ciphertext = this->Encrypt(this->zeroes1);
|
||||||
|
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data_Size1) {
|
||||||
|
Data ciphertext = this->Encrypt(this->plaintext1);
|
||||||
|
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes) {
|
||||||
|
Data ciphertext = this->Encrypt(this->zeroes2);
|
||||||
|
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data) {
|
||||||
|
Data ciphertext = this->Encrypt(this->plaintext2);
|
||||||
|
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Zeroes) {
|
||||||
|
Data ciphertext = this->Encrypt(this->zeroes2);
|
||||||
|
((byte*)ciphertext.data())[ciphertext.size() - 1] = ((byte*)ciphertext.data())[ciphertext.size() - 1] + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Data) {
|
||||||
|
Data ciphertext = this->Encrypt(this->plaintext2);
|
||||||
|
((byte*)ciphertext.data())[ciphertext.size() - 1] = ((byte*)ciphertext.data())[ciphertext.size() - 1] + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Zeroes) {
|
||||||
|
Data ciphertext = this->Encrypt(this->zeroes2);
|
||||||
|
((byte*)ciphertext.data())[ciphertext.size()/2] = ((byte*)ciphertext.data())[ciphertext.size()/2] + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Data) {
|
||||||
|
Data ciphertext = this->Encrypt(this->plaintext2);
|
||||||
|
((byte*)ciphertext.data())[ciphertext.size()/2] = ((byte*)ciphertext.data())[ciphertext.size()/2] + 1;
|
||||||
|
this->ExpectDoesntDecrypt(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptZeroesData) {
|
||||||
|
this->ExpectDoesntDecrypt(this->zeroes2);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
ModifyFirstByte_Data_Size1,
|
||||||
|
ModifyFirstByte_Data,
|
||||||
|
ModifyLastByte_Zeroes,
|
||||||
|
ModifyLastByte_Data,
|
||||||
|
ModifyMiddleByte_Zeroes,
|
||||||
|
ModifyMiddleByte_Data,
|
||||||
|
TryDecryptZeroesData,
|
||||||
|
TryDecryptRandomData,
|
||||||
|
TryDecryptDataThatIsTooSmall,
|
||||||
|
TryDecryptDataThatIsMuchTooSmall_0,
|
||||||
|
TryDecryptDataThatIsMuchTooSmall_1
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB);
|
INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB);
|
||||||
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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user