Files
AnyPS5/core/libs/tests/GuestLibreSslEvpCipherCtx.cpp
T
Yunus Shaikh 8a9ab924f9 test(libressl): run EVP_aes_128_cbc through the EVP cipher calls
main's EVP_aes_128_cbc descriptor, the one CMAC uses, is a
LibreSslCipher, and this branch's EVP_EncryptInit_ex and
EVP_DecryptInit_ex take a LibreSslEvpCipher of the same layout, but no
test passed the one to the other. The new case does, with the
CBC-AES128 vectors of NIST SP 800-38A F.2.1 and F.2.2: encryption and
decryption without padding, and a round trip with PKCS#7 padding, in
chunks of 1, 7, 16 and 64 bytes. It covers code that already works, so
it has no red.
2026-11-03 07:01:24 -08:00

1154 lines
63 KiB
C++

#include "prx/libSceLibreSsl/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
#include <stdexcept>
#include <vector>
constexpr std::size_t ContextBytes = 168;
constexpr std::size_t CipherOffset = 0;
constexpr std::size_t EngineOffset = 8;
constexpr std::size_t EncryptOffset = 16;
constexpr std::size_t BufLenOffset = 20;
constexpr std::size_t OivOffset = 24;
constexpr std::size_t IvOffset = 40;
constexpr std::size_t BufOffset = 56;
constexpr std::size_t NumOffset = 88;
constexpr std::size_t KeyLenOffset = 104;
constexpr std::size_t FlagsOffset = 112;
constexpr std::size_t CipherDataOffset = 120;
constexpr std::size_t FinalUsedOffset = 128;
constexpr std::size_t BlockMaskOffset = 132;
constexpr std::size_t FinalOffset = 136;
constexpr std::uint64_t ModeCbc = 2;
constexpr std::uint64_t ModeCfb = 3;
constexpr std::uint64_t ModeOfb = 4;
constexpr std::uint64_t ModeCtr = 5;
constexpr std::uint64_t ModeGcm = 6;
constexpr std::uint64_t FlagCustomIv = 0x10;
constexpr std::uint64_t FlagAlwaysCallInit = 0x20;
constexpr std::uint64_t FlagCtrlInit = 0x40;
constexpr std::uint64_t FlagCustomCipher = 0x100000;
struct Cipher {
int nid;
int blockSize;
int keyLength;
int ivLength;
std::uint64_t flags;
int (APS5_VABI* init)(unsigned char*, const unsigned char*, const unsigned char*, int);
int (APS5_VABI* doCipher)(unsigned char*, unsigned char*, const unsigned char*, std::size_t);
int (APS5_VABI* cleanup)(unsigned char*);
int contextSize;
void* setAsn1Parameters;
void* getAsn1Parameters;
int (APS5_VABI* control)(unsigned char*, int, int, void*);
void* applicationData;
};
static_assert(sizeof(Cipher) == 88);
struct Calls {
int inits;
unsigned char* initContext;
const unsigned char* initKey;
const unsigned char* initIv;
int initEncrypt;
int initResult;
int ciphers;
unsigned char* cipherOut;
const unsigned char* cipherIn;
std::size_t cipherLength;
int cipherResult;
int cleanups;
unsigned char* cleanupContext;
int cleanupData;
int cleanupResult;
int controls;
unsigned char* controlContext;
int controlType;
int controlArg;
void* controlPointer;
int controlResult;
};
static Calls calls;
template<typename T> static T Load(const unsigned char* context, std::size_t offset) {
T value {};
std::memcpy(&value, context + offset, sizeof(value));
return value;
}
template<typename T> static void Store(unsigned char* context, std::size_t offset, T value) {
std::memcpy(context + offset, &value, sizeof(value));
}
static std::uint64_t Flags(const unsigned char* context) {
std::uint64_t flags = 0;
std::memcpy(&flags, context + FlagsOffset, sizeof(flags));
return flags;
}
static void SetFlags(unsigned char* context, std::uint64_t flags) {
std::memcpy(context + FlagsOffset, &flags, sizeof(flags));
}
static void InitializeHeap() {
void* const api[10] {};
ApplicationHeapRegister_nid_no_patch(api);
}
static LibreSslEvpCipherCtx* Context(unsigned char* memory) {
return reinterpret_cast<LibreSslEvpCipherCtx*>(memory);
}
static const LibreSslEvpCipher* EvpCipher(const void* cipher) {
return static_cast<const LibreSslEvpCipher*>(cipher);
}
static void Require(bool condition, std::source_location location = std::source_location::current()) {
if (condition) return;
std::fprintf(stderr, "LibreSSL EVP_CIPHER_CTX check failed at line %u\n", location.line());
std::abort();
}
template<typename TAction> static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) {
try { action(); } catch (const std::exception&) { return; }
Require(false, location);
}
static void TestInit() {
unsigned char memory[ContextBytes + 16];
std::memset(memory, 0xAA, sizeof(memory));
EVP_CIPHER_CTX_init_nid_postfix(Context(memory));
for (std::size_t index = 0; index < ContextBytes; ++index) Require(memory[index] == 0);
for (std::size_t index = ContextBytes; index < sizeof(memory); ++index) Require(memory[index] == 0xAA);
RequireThrows([] { EVP_CIPHER_CTX_init_nid_postfix(nullptr); });
}
static void TestNew() {
for (int round = 0; round < 4; ++round) {
auto* context = reinterpret_cast<unsigned char*>(EVP_CIPHER_CTX_new_nid_postfix());
Require(context != nullptr);
for (std::size_t index = 0; index < ContextBytes; ++index) Require(context[index] == 0);
std::memset(context, 0x5A, ContextBytes);
free_nid_postfix(context);
}
}
static void TestSetPadding() {
unsigned char context[ContextBytes] {};
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(Flags(context) == 0x100);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(Flags(context) == 0x100);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 1) == 1);
Require(Flags(context) == 0);
SetFlags(context, UINT64_C(0xFFFFFFFF000000FF));
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(Flags(context) == UINT64_C(0xFFFFFFFF000001FF));
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), -5) == 1);
Require(Flags(context) == UINT64_C(0xFFFFFFFF000000FF));
for (std::size_t index = 0; index < ContextBytes; ++index) {
if (index < FlagsOffset || index >= FlagsOffset + 8) Require(context[index] == 0);
}
RequireThrows([] { EVP_CIPHER_CTX_set_padding_nid_postfix(nullptr, 0); });
}
static int APS5_VABI RecordCleanup(unsigned char* context) {
++calls.cleanups;
calls.cleanupContext = context;
const auto* data = Load<const unsigned char*>(context, CipherDataOffset);
calls.cleanupData = data ? data[0] : -1;
return calls.cleanupResult;
}
static int APS5_VABI RecordControl(unsigned char* context, int type, int arg, void* pointer) {
++calls.controls;
calls.controlContext = context;
calls.controlType = type;
calls.controlArg = arg;
calls.controlPointer = pointer;
return calls.controlResult;
}
static void RequireCleared(const unsigned char* context) {
for (std::size_t index = 0; index < ContextBytes; ++index) Require(context[index] == 0);
}
static unsigned char* FilledData(std::size_t size) {
auto* data = static_cast<unsigned char*>(malloc_nid_postfix(size));
Require(data != nullptr);
std::memset(data, 0x5A, size);
return data;
}
static void PrepareContext(unsigned char* context, const Cipher* cipher, void* data) {
std::memset(context, 0x33, ContextBytes);
Store(context, CipherOffset, cipher);
Store<void*>(context, EngineOffset, nullptr);
Store(context, CipherDataOffset, data);
}
static void TestCleanup() {
Cipher cipher {};
cipher.cleanup = RecordCleanup;
cipher.contextSize = 24;
unsigned char context[ContextBytes];
auto* data = FilledData(24);
PrepareContext(context, &cipher, data);
calls = {};
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 0);
Require(calls.cleanups == 1 && calls.cleanupContext == context && calls.cleanupData == 0x5A);
Require(Load<const Cipher*>(context, CipherOffset) == &cipher && Load<unsigned char*>(context, CipherDataOffset) == data);
Require(context[ContextBytes - 1] == 0x33);
calls.cleanupResult = 1;
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
Require(calls.cleanups == 2 && calls.cleanupData == 0x5A);
RequireCleared(context);
cipher.cleanup = nullptr;
PrepareContext(context, &cipher, FilledData(24));
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
Require(calls.cleanups == 2);
RequireCleared(context);
PrepareContext(context, nullptr, FilledData(8));
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
RequireCleared(context);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
RequireCleared(context);
cipher.contextSize = -1;
data = FilledData(8);
PrepareContext(context, &cipher, data);
RequireThrows([&] { EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)); });
free_nid_postfix(data);
RequireThrows([] { EVP_CIPHER_CTX_cleanup_nid_postfix(nullptr); });
}
static void TestFree() {
EVP_CIPHER_CTX_free_nid_postfix(nullptr);
Cipher cipher {};
cipher.cleanup = RecordCleanup;
cipher.contextSize = 16;
calls = {};
for (int result : {1, 0}) {
calls.cleanupResult = result;
auto* context = reinterpret_cast<unsigned char*>(EVP_CIPHER_CTX_new_nid_postfix());
Require(context != nullptr);
Store(context, CipherOffset, &cipher);
Store(context, CipherDataOffset, FilledData(16));
EVP_CIPHER_CTX_free_nid_postfix(Context(context));
Require(calls.cleanupContext == context && calls.cleanupData == 0x5A);
}
Require(calls.cleanups == 2);
}
static void TestCtrl() {
unsigned char context[ContextBytes] {};
int value = 0;
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), 9, 12, &value) == 0);
Cipher cipher {};
Store(context, CipherOffset, &cipher);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), 9, 12, &value) == 0);
cipher.control = RecordControl;
calls = {};
for (int result : {1, 16, 0, -1, -2}) {
calls.controlResult = result;
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), 0x11, -3, &value) == (result == -1 ? 0 : result));
}
Require(calls.controls == 5 && calls.controlContext == context && calls.controlType == 0x11);
Require(calls.controlArg == -3 && calls.controlPointer == &value);
RequireThrows([&] { EVP_CIPHER_CTX_ctrl_nid_postfix(nullptr, 0, 0, &value); });
}
using Update = int (APS5_VABI*)(LibreSslEvpCipherCtx*, unsigned char*, int*, const unsigned char*, int);
using Final = int (APS5_VABI*)(LibreSslEvpCipherCtx*, unsigned char*, int*);
using Bytes = std::vector<unsigned char>;
constexpr unsigned char XorKey[16] {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
static void ResetCalls() {
calls = {};
calls.initResult = 1;
calls.cipherResult = 1;
calls.cleanupResult = 1;
calls.controlResult = 1;
}
static int APS5_VABI XorInit(unsigned char* context, const unsigned char* key, const unsigned char* iv, int encrypt) {
++calls.inits;
calls.initContext = context;
calls.initKey = key;
calls.initIv = iv;
calls.initEncrypt = encrypt;
auto* data = Load<unsigned char*>(context, CipherDataOffset);
if (key && data) std::memcpy(data, key, sizeof(XorKey));
return calls.initResult;
}
static int APS5_VABI XorCipher(unsigned char* context, unsigned char* out, const unsigned char* in, std::size_t length) {
++calls.ciphers;
calls.cipherOut = out;
calls.cipherIn = in;
calls.cipherLength = length;
const auto* key = Load<const unsigned char*>(context, CipherDataOffset);
for (std::size_t index = 0; index < length; ++index) out[index] = in[index] ^ key[index % sizeof(XorKey)];
return calls.cipherResult;
}
static int APS5_VABI RecordCipher(unsigned char*, unsigned char* out, const unsigned char* in, std::size_t length) {
++calls.ciphers;
calls.cipherOut = out;
calls.cipherIn = in;
calls.cipherLength = length;
return calls.cipherResult;
}
static Cipher XorBlockCipher() {
Cipher cipher {};
cipher.nid = 1000;
cipher.blockSize = 16;
cipher.keyLength = 16;
cipher.flags = 1;
cipher.init = XorInit;
cipher.doCipher = XorCipher;
cipher.cleanup = RecordCleanup;
cipher.contextSize = 16;
return cipher;
}
static Bytes Pattern(std::size_t length, unsigned seed) {
Bytes bytes(length);
for (std::size_t index = 0; index < length; ++index) bytes[index] = static_cast<unsigned char>(seed + 37 * index);
return bytes;
}
static Bytes Xor(Bytes bytes) {
for (std::size_t index = 0; index < bytes.size(); ++index) bytes[index] ^= XorKey[index % sizeof(XorKey)];
return bytes;
}
static Bytes Padded(Bytes bytes) {
const std::size_t pad = 16 - bytes.size() % 16;
bytes.insert(bytes.end(), pad, static_cast<unsigned char>(pad));
return bytes;
}
static int Process(unsigned char* context, Update update, Final final, const Bytes& input, std::size_t chunk, Bytes& output) {
output.assign(input.size() + 32, 0xEE);
int total = 0;
for (std::size_t offset = 0; offset < input.size(); offset += chunk) {
const int length = static_cast<int>(input.size() - offset < chunk ? input.size() - offset : chunk);
int written = -1;
Require(update(Context(context), output.data() + total, &written, input.data() + offset, length) == 1);
Require(written >= 0 && written <= length + 16);
total += written;
}
int written = -1;
const int result = final(Context(context), output.data() + total, &written);
if (result) total += written;
output.resize(static_cast<std::size_t>(total));
return result;
}
static void TestPadding() {
const auto cipher = XorBlockCipher();
for (std::size_t length : {0, 1, 15, 16, 17, 31, 32, 33, 48}) {
const auto input = Pattern(length, static_cast<unsigned>(length));
const auto expected = Xor(Padded(input));
for (std::size_t chunk : {1, 5, 16, 64}) {
for (bool generic : {false, true}) {
ResetCalls();
auto* context = reinterpret_cast<unsigned char*>(EVP_CIPHER_CTX_new_nid_postfix());
Bytes output;
Require(generic ? EVP_CipherInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr, 1) == 1
: EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(calls.inits == 1 && calls.initKey == XorKey && calls.initIv == nullptr && calls.initEncrypt == 1);
Require(Process(context, generic ? EVP_CipherUpdate_nid_postfix : EVP_EncryptUpdate_nid_postfix,
generic ? EVP_CipherFinal_ex_nid_postfix : EVP_EncryptFinal_ex_nid_postfix, input, chunk, output) == 1);
Require(output == expected);
Require(generic ? EVP_CipherInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr, 0) == 1
: EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(calls.cleanups == 1 && calls.inits == 2 && calls.initEncrypt == 0);
Require(Process(context, generic ? EVP_CipherUpdate_nid_postfix : EVP_DecryptUpdate_nid_postfix,
generic ? EVP_CipherFinal_ex_nid_postfix : EVP_DecryptFinal_ex_nid_postfix, expected, chunk, output) == 1);
Require(output == input);
EVP_CIPHER_CTX_free_nid_postfix(Context(context));
Require(calls.cleanups == 2);
}
}
}
}
static void TestHeldBlock() {
const auto cipher = XorBlockCipher();
ResetCalls();
unsigned char context[ContextBytes] {};
const auto plain = Pattern(20, 3);
const auto encrypted = Xor(Padded(plain));
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
unsigned char out[64] {};
int written = -1;
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, encrypted.data(), 16) == 1);
Require(written == 0 && Load<int>(context, FinalUsedOffset) == 1 && Load<int>(context, BufLenOffset) == 0);
Require(std::memcmp(context + FinalOffset, plain.data(), 16) == 0);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, encrypted.data() + 16, 16) == 1);
Require(written == 16 && std::memcmp(out, plain.data(), 16) == 0 && Load<int>(context, FinalUsedOffset) == 1);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out + 16, &written) == 1);
Require(written == 4 && std::memcmp(out + 16, plain.data() + 16, 4) == 0);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, nullptr) == 1);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, encrypted.data(), 17) == 1);
Require(written == 16 && std::memcmp(out, plain.data(), 16) == 0);
Require(Load<int>(context, FinalUsedOffset) == 0 && Load<int>(context, BufLenOffset) == 1);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, encrypted.data() + 17, 15) == 1);
Require(written == 0 && Load<int>(context, FinalUsedOffset) == 1 && Load<int>(context, BufLenOffset) == 0);
Require(std::memcmp(context + FinalOffset, Padded(plain).data() + 16, 16) == 0);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 1);
Require(written == 4 && std::memcmp(out, plain.data() + 16, 4) == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
static void TestBadPadding() {
const auto cipher = XorBlockCipher();
ResetCalls();
unsigned char context[ContextBytes] {};
unsigned char out[32] {};
auto block = Pattern(16, 9);
for (int last : {0, 17, 255}) {
block[15] = static_cast<unsigned char>(last);
const auto encrypted = Xor(block);
int written = -1;
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, encrypted.data(), 16) == 1 && written == 0);
written = -1;
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 0 && written == 0);
}
block[15] = 2;
block[14] = 3;
int written = -1;
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, Xor(block).data(), 16) == 1);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 0 && written == 0);
Bytes full(16, 16);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, Xor(full).data(), 16) == 1);
written = -1;
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 1 && written == 0);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
written = -1;
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 0 && written == 0);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, Xor(full).data(), 15) == 1 && written == 0);
written = -1;
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 0 && written == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
static void TestNoPadding() {
const auto cipher = XorBlockCipher();
ResetCalls();
unsigned char context[ContextBytes] {};
Bytes output;
const auto aligned = Pattern(32, 1);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, aligned, 7, output) == 1);
Require(output == Xor(aligned));
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr) == 1);
Require(Load<std::uint64_t>(context, FlagsOffset) == 0x100);
unsigned char out[64] {};
int written = -1;
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, Xor(aligned).data(), 32) == 1);
Require(written == 32 && std::memcmp(out, aligned.data(), 32) == 0 && Load<int>(context, FinalUsedOffset) == 0);
written = -1;
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 1 && written == 0);
for (bool encrypt : {true, false}) {
Require(EVP_CipherInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr, encrypt) == 1);
Require(EVP_CipherUpdate_nid_postfix(Context(context), out, &written, aligned.data(), 17) == 1 && written == 16);
written = -1;
Require(EVP_CipherFinal_ex_nid_postfix(Context(context), out, &written) == 0);
Require(written == (encrypt ? -1 : 0));
}
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(Load<std::uint64_t>(context, FlagsOffset) == 0);
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, aligned, 32, output) == 1);
Require(output == Xor(Padded(aligned)));
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
static void TestInitFields() {
ResetCalls();
auto cipher = XorBlockCipher();
cipher.flags = ModeCbc;
cipher.ivLength = 16;
cipher.keyLength = 24;
const auto first = Pattern(16, 100);
const auto second = Pattern(16, 200);
unsigned char context[ContextBytes] {};
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, first.data()) == 1);
Require(Load<const Cipher*>(context, CipherOffset) == &cipher && Load<void*>(context, EngineOffset) == nullptr);
Require(Load<int>(context, EncryptOffset) == 1 && Load<int>(context, KeyLenOffset) == 24 && Load<int>(context, BlockMaskOffset) == 15);
Require(Load<int>(context, BufLenOffset) == 0 && Load<int>(context, FinalUsedOffset) == 0 && Load<std::uint64_t>(context, FlagsOffset) == 0);
Require(Load<void*>(context, CipherDataOffset) != nullptr && calls.initIv == first.data() && calls.initContext == context);
Require(std::memcmp(context + OivOffset, first.data(), 16) == 0 && std::memcmp(context + IvOffset, first.data(), 16) == 0);
std::memset(context + IvOffset, 0x44, 16);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, nullptr) == 1);
Require(calls.inits == 1 && std::memcmp(context + IvOffset, first.data(), 16) == 0);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, second.data()) == 1);
Require(calls.inits == 1 && std::memcmp(context + OivOffset, second.data(), 16) == 0 && std::memcmp(context + IvOffset, second.data(), 16) == 0);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr) == 1);
Require(calls.inits == 2 && calls.initEncrypt == 0 && Load<int>(context, EncryptOffset) == 0);
Require(EVP_CipherInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr, -1) == 1);
Require(calls.initEncrypt == 0 && Load<int>(context, EncryptOffset) == 0);
Require(EVP_CipherInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr, 7) == 1);
Require(calls.initEncrypt == 1 && Load<int>(context, EncryptOffset) == 1);
Require(EVP_CipherInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr, -1) == 1);
Require(calls.initEncrypt == 1 && calls.cleanups == 0);
for (std::uint64_t mode : {ModeCfb, ModeOfb, ModeCtr, std::uint64_t {0}, std::uint64_t {1}, ModeCbc | FlagCustomIv}) {
cipher.flags = mode;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
std::memset(context + OivOffset, 0x77, 32);
Store(context, NumOffset, 5);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, first.data()) == 1);
const bool counter = mode == ModeCtr;
const bool chained = mode == ModeCfb || mode == ModeOfb;
Require(Load<int>(context, NumOffset) == (counter || chained ? 0 : 5));
Require(context[OivOffset] == (chained ? first[0] : 0x77) && context[IvOffset] == (counter || chained ? first[0] : 0x77));
std::memset(context + IvOffset, 0x55, 16);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, nullptr) == 1);
Require(context[IvOffset] == (chained ? first[0] : 0x55));
}
cipher.flags = ModeGcm;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, first.data()) == 0);
cipher.flags = ModeCbc;
cipher.ivLength = 17;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, first.data()) == 0);
cipher.flags = ModeCtr;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
RequireThrows([&] { EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, first.data()); });
cipher.ivLength = 16;
cipher.blockSize = 4;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, first.data()) == 0);
Require(Load<const Cipher*>(context, CipherOffset) == &cipher);
for (int block : {1, 8}) {
cipher.blockSize = block;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, first.data()) == 1);
Require(Load<int>(context, BlockMaskOffset) == block - 1);
}
const int inits = calls.inits;
cipher.flags = FlagAlwaysCallInit;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, nullptr, nullptr) == 1);
Require(calls.inits == inits + 1 && calls.initKey == nullptr);
calls.initResult = 0;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr) == 0);
calls.initResult = 1;
cipher.flags = FlagCtrlInit;
cipher.control = RecordControl;
cipher.contextSize = 0;
calls.controls = 0;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, nullptr, nullptr) == 1);
Require(calls.controls == 1 && calls.controlType == 0 && calls.controlArg == 0 && calls.controlPointer == nullptr);
Require(Load<void*>(context, CipherDataOffset) == nullptr);
calls.controlResult = 0;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, nullptr, nullptr) == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr) == 0);
RequireThrows([] { EVP_CipherInit_ex_nid_postfix(nullptr, nullptr, nullptr, nullptr, nullptr, 1); });
}
static void TestStreamCipher() {
auto cipher = XorBlockCipher();
cipher.blockSize = 1;
cipher.flags = 0;
ResetCalls();
unsigned char context[ContextBytes] {};
const auto input = Pattern(37, 5);
Bytes output;
for (std::size_t chunk : {1, 4, 37}) {
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
const int ciphers = calls.ciphers;
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, input, chunk, output) == 1);
Require(output.size() == input.size() && calls.ciphers == ciphers + static_cast<int>((input.size() + chunk - 1) / chunk));
const auto encrypted = output;
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
Require(Process(context, EVP_DecryptUpdate_nid_postfix, EVP_DecryptFinal_ex_nid_postfix, encrypted, chunk, output) == 1);
Require(output == input);
}
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
static void TestCustomCipher() {
Cipher cipher {};
cipher.blockSize = 1;
cipher.flags = FlagCustomCipher | FlagCustomIv;
cipher.doCipher = RecordCipher;
ResetCalls();
unsigned char context[ContextBytes] {};
unsigned char in[8] {};
unsigned char out[8] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, nullptr, nullptr) == 1);
calls.cipherResult = 7;
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, in, 3) == 1);
Require(written == 7 && calls.cipherOut == out && calls.cipherIn == in && calls.cipherLength == 3);
calls.cipherResult = 0;
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, in, 0) == 1 && written == 0 && calls.ciphers == 2);
calls.cipherResult = 2;
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, in, -1) == 1);
Require(written == 2 && calls.cipherLength == static_cast<std::size_t>(-1));
calls.cipherResult = -1;
written = -1;
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, in, 3) == 0 && written == -1);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, in, 3) == 0 && written == 0);
calls.cipherResult = 4;
Require(EVP_EncryptFinal_ex_nid_postfix(Context(context), out, &written) == 1);
Require(written == 4 && calls.cipherIn == nullptr && calls.cipherLength == 0 && calls.cipherOut == out);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 1 && written == 4);
calls.cipherResult = -1;
written = -1;
Require(EVP_EncryptFinal_ex_nid_postfix(Context(context), out, &written) == 0 && written == -1);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 0 && written == 0);
Require(calls.ciphers == 9);
}
static void TestUpdateArguments() {
const auto cipher = XorBlockCipher();
ResetCalls();
unsigned char context[ContextBytes] {};
unsigned char in[16] {};
unsigned char out[32] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(&cipher), nullptr, XorKey, nullptr) == 1);
for (Update update : {EVP_EncryptUpdate_nid_postfix, EVP_DecryptUpdate_nid_postfix, EVP_CipherUpdate_nid_postfix}) {
written = -1;
Require(update(Context(context), out, &written, in, 0) == 1 && written == 0);
written = -1;
Require(update(Context(context), out, &written, in, -1) == 0 && written == 0);
RequireThrows([&] { update(Context(context), out, nullptr, in, 16); });
RequireThrows([&] { update(Context(context), out, &written, nullptr, 3); });
RequireThrows([&] { update(nullptr, out, &written, in, 16); });
}
Require(calls.ciphers == 3);
for (Final final : {EVP_EncryptFinal_ex_nid_postfix, EVP_DecryptFinal_ex_nid_postfix, EVP_CipherFinal_ex_nid_postfix}) {
RequireThrows([&] { final(Context(context), out, nullptr); });
RequireThrows([&] { final(nullptr, out, &written); });
}
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
RequireThrows([&] { EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, in, 16); });
RequireThrows([&] { EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written); });
}
using Descriptor = const LibreSslEvpCipher* (APS5_VABI*)(void);
constexpr const char* Sp80038aKey128 = "2b7e151628aed2a6abf7158809cf4f3c";
constexpr const char* Sp80038aKey192 = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b";
constexpr const char* Sp80038aKey256 = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
static Bytes Hex(const char* text) {
const auto nibble = [](char digit) { return static_cast<unsigned char>(digit <= '9' ? digit - '0' : digit - 'a' + 10); };
Bytes bytes;
for (; text[0] && text[1]; text += 2) bytes.push_back(static_cast<unsigned char>(nibble(text[0]) << 4 | nibble(text[1])));
return bytes;
}
static void RequireDescriptor(const Cipher* cipher, int nid, std::size_t keyLength, int ivLength, std::uint64_t flags, int contextSize) {
Require(cipher != nullptr && cipher->nid == nid && cipher->blockSize == 1 && cipher->keyLength == static_cast<int>(keyLength));
Require(cipher->ivLength == ivLength && cipher->flags == flags && cipher->contextSize == contextSize);
Require(cipher->init != nullptr && cipher->doCipher != nullptr && cipher->applicationData == nullptr);
Require(cipher->setAsn1Parameters == nullptr && cipher->getAsn1Parameters == nullptr);
}
static void RequireThreeForms(const Cipher* cipher, const Bytes& key, const Bytes& iv, const Bytes& plain, const Bytes& encrypted) {
unsigned char context[ContextBytes] {};
Bytes output;
for (std::size_t chunk : {1, 7, 64}) {
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), iv.data()) == 1);
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, plain, chunk, output) == 1);
Require(output == encrypted);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), iv.data()) == 1);
Require(Process(context, EVP_DecryptUpdate_nid_postfix, EVP_DecryptFinal_ex_nid_postfix, encrypted, chunk, output) == 1);
Require(output == plain);
for (int encrypt : {1, 0}) {
Require(EVP_CipherInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), iv.data(), encrypt) == 1);
Require(Process(context, EVP_CipherUpdate_nid_postfix, EVP_CipherFinal_ex_nid_postfix, encrypt ? plain : encrypted, chunk, output) == 1);
Require(output == (encrypt ? encrypted : plain));
}
}
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, key.data(), iv.data()) == 1);
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, plain, 5, output) == 1);
Require(output == encrypted);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
static void TestAesCbc() {
const auto* descriptor = EVP_aes_128_cbc_nid_postfix();
const auto* cipher = reinterpret_cast<const Cipher*>(descriptor);
Require(cipher->nid == 419 && cipher->blockSize == 16 && cipher->keyLength == 16 && cipher->ivLength == 16);
const auto key = Hex(Sp80038aKey128);
const auto iv = Hex("000102030405060708090a0b0c0d0e0f");
const auto plain = Hex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710");
const auto encrypted = Hex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7");
unsigned char context[ContextBytes] {};
Bytes output;
for (std::size_t chunk : {1, 7, 16, 64}) {
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(descriptor), nullptr, key.data(), iv.data()) == 1);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, plain, chunk, output) == 1);
Require(output == encrypted);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(descriptor), nullptr, key.data(), iv.data()) == 1);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1);
Require(Process(context, EVP_DecryptUpdate_nid_postfix, EVP_DecryptFinal_ex_nid_postfix, encrypted, chunk, output) == 1);
Require(output == plain);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(descriptor), nullptr, key.data(), iv.data()) == 1);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 1) == 1);
Require(Process(context, EVP_EncryptUpdate_nid_postfix, EVP_EncryptFinal_ex_nid_postfix, plain, chunk, output) == 1);
Require(output.size() == encrypted.size() + 16 && Bytes(output.begin(), output.begin() + 64) == encrypted);
const auto padded = output;
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(descriptor), nullptr, key.data(), iv.data()) == 1);
Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 1) == 1);
Require(Process(context, EVP_DecryptUpdate_nid_postfix, EVP_DecryptFinal_ex_nid_postfix, padded, chunk, output) == 1);
Require(output == plain);
}
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
static void TestAesCfb8() {
const struct {
Descriptor descriptor;
int nid;
const char* key;
const char* encrypted;
} cases[] {
{EVP_aes_128_cfb8_nid_postfix, 653, Sp80038aKey128, "3b79424c9c0dd436bace9e0ed4586a4f32b9"},
{EVP_aes_192_cfb8_nid_postfix, 654, Sp80038aKey192, "cda2521ef0a905ca44cd057cbf0d47a0678a"},
{EVP_aes_256_cfb8_nid_postfix, 655, Sp80038aKey256, "dc1f1a8520a64db55fcc8ac554844e889700"},
};
const auto iv = Hex("000102030405060708090a0b0c0d0e0f");
const auto plain = Hex("6bc1bee22e409f96e93d7e117393172aae2d");
for (const auto& test : cases) {
const auto* cipher = reinterpret_cast<const Cipher*>(test.descriptor());
const auto key = Hex(test.key);
const auto encrypted = Hex(test.encrypted);
Require(EvpCipher(cipher) == test.descriptor());
RequireDescriptor(cipher, test.nid, key.size(), 16, 0x4003, 264);
Require(cipher->cleanup == nullptr && cipher->control == nullptr);
RequireThreeForms(cipher, key, iv, plain, encrypted);
unsigned char context[ContextBytes] {};
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), iv.data()) == 1);
Store(context, NumOffset, 9);
auto buffer = plain;
int written = -1;
Require(EVP_EncryptUpdate_nid_postfix(Context(context), buffer.data(), &written, buffer.data(), 18) == 1);
Require(written == 18 && buffer == encrypted && Load<int>(context, NumOffset) == 9);
Require(std::memcmp(context + IvOffset, encrypted.data() + 2, 16) == 0);
RequireThrows([&] { EVP_EncryptUpdate_nid_postfix(Context(context), nullptr, &written, buffer.data(), 3); });
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
}
static void TestAesCtr() {
const struct {
Descriptor descriptor;
int nid;
const char* key;
const char* encrypted;
} cases[] {
{EVP_aes_128_ctr_nid_postfix, 904, Sp80038aKey128, "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"},
{EVP_aes_192_ctr_nid_postfix, 905, Sp80038aKey192, "1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050"},
{EVP_aes_256_ctr_nid_postfix, 906, Sp80038aKey256, "601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"},
};
const auto counter = Hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
const auto plain = Hex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710");
for (const auto& test : cases) {
const auto* cipher = reinterpret_cast<const Cipher*>(test.descriptor());
const auto key = Hex(test.key);
const auto encrypted = Hex(test.encrypted);
Require(EvpCipher(cipher) == test.descriptor());
RequireDescriptor(cipher, test.nid, key.size(), 16, 0x4005, 264);
Require(cipher->cleanup == nullptr && cipher->control == nullptr);
RequireThreeForms(cipher, key, counter, plain, encrypted);
unsigned char context[ContextBytes] {};
unsigned char out[64] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), counter.data()) == 1);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 18) == 1 && written == 18);
Require(Load<int>(context, NumOffset) == 2 && context[IvOffset + 15] == 0x01 && context[IvOffset + 14] == 0xff);
for (std::size_t index = 0; index < 16; ++index) Require(context[BufOffset + index] == (plain[16 + index] ^ encrypted[16 + index]));
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, nullptr) == 1);
Require(Load<int>(context, NumOffset) == 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data() + 32, 32) == 1 && written == 32);
Require(std::memcmp(out, encrypted.data() + 32, 32) == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
const auto* cipher = reinterpret_cast<const Cipher*>(EVP_aes_128_ctr_nid_postfix());
const auto key = Hex(Sp80038aKey128);
const Bytes wrap(16, 0xff);
unsigned char context[ContextBytes] {};
unsigned char out[17] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), wrap.data()) == 1);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 17) == 1 && written == 17);
Require(Bytes(out, out + 17) == Hex("e13338e36cb71962e00d020b4cedbd86d3"));
for (std::size_t index = 0; index < 16; ++index) Require(context[IvOffset + index] == (index == 15 ? 1 : 0));
Store(context, KeyLenOffset, 20);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, key.data(), nullptr) == 0);
Require(cipher->init(context, nullptr, nullptr, 1) == 0);
Store(context, NumOffset, 16);
RequireThrows([&] { EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 1); });
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
RequireThrows([&] { cipher->doCipher(context, out, plain.data(), 1); });
}
constexpr int ControlCopy = 0x8;
constexpr int GcmSetIvLength = 0x9;
constexpr int GcmGetTag = 0x10;
constexpr int GcmSetTag = 0x11;
constexpr int GcmSetIvFixed = 0x12;
constexpr int GcmIvGenerate = 0x13;
constexpr int TlsAad = 0x16;
constexpr int GcmSetIvInvocation = 0x18;
constexpr std::size_t GcmContextBytes = 680;
constexpr const char* Gcm3Key = "feffe9928665731c6d6a8f9467308308";
constexpr const char* Gcm3Iv = "cafebabefacedbaddecaf888";
constexpr const char* Gcm3Plain = "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255";
constexpr const char* Gcm4Plain = "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39";
constexpr const char* Gcm4Aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2";
constexpr const char* Gcm6Iv = "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b";
struct GcmVector {
Descriptor descriptor;
const char* key;
const char* iv;
const char* plain;
const char* aad;
const char* encrypted;
const char* tag;
};
const GcmVector GcmVectors[] {
{EVP_aes_128_gcm_nid_postfix, "00000000000000000000000000000000", "000000000000000000000000", "", "", "", "58e2fccefa7e3061367f1d57a4e7455a"},
{EVP_aes_128_gcm_nid_postfix, "00000000000000000000000000000000", "000000000000000000000000", "00000000000000000000000000000000", "",
"0388dace60b6a392f328c2b971b2fe78", "ab6e47d42cec13bdf53a67b21257bddf"},
{EVP_aes_128_gcm_nid_postfix, Gcm3Key, Gcm3Iv, Gcm3Plain, "",
"42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
"4d5c2af327cd64a62cf35abd2ba6fab4"},
{EVP_aes_128_gcm_nid_postfix, Gcm3Key, Gcm3Iv, Gcm4Plain, Gcm4Aad,
"42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
"5bc94fbc3221a5db94fae95ae7121a47"},
{EVP_aes_128_gcm_nid_postfix, Gcm3Key, "cafebabefacedbad", Gcm4Plain, Gcm4Aad,
"61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
"3612d2e79e3b0785561be14aaca2fccb"},
{EVP_aes_128_gcm_nid_postfix, Gcm3Key, Gcm6Iv, Gcm4Plain, Gcm4Aad,
"8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5",
"619cc5aefffe0bfa462af43c1699d050"},
{EVP_aes_192_gcm_nid_postfix, "000000000000000000000000000000000000000000000000", "000000000000000000000000", "", "", "", "cd33b28ac773f74ba00ed1f312572435"},
{EVP_aes_192_gcm_nid_postfix, "000000000000000000000000000000000000000000000000", "000000000000000000000000", "00000000000000000000000000000000", "",
"98e7247c07f0fe411c267e4384b0f600", "2ff58d80033927ab8ef4d4587514f0fb"},
{EVP_aes_256_gcm_nid_postfix, "0000000000000000000000000000000000000000000000000000000000000000", "000000000000000000000000", "", "", "",
"530f8afbc74536b9a963b4f1c4cb738b"},
{EVP_aes_256_gcm_nid_postfix, "0000000000000000000000000000000000000000000000000000000000000000", "000000000000000000000000",
"00000000000000000000000000000000", "", "cea7403d4d606b6e074ec5d3baf39d18", "d0d1c8a799996bf0265b98b5d48ab919"},
};
static bool InitForm(unsigned char* context, const void* cipher, const unsigned char* key, const unsigned char* iv, int encrypt, bool generic) {
if (generic) return EVP_CipherInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key, iv, encrypt) == 1;
if (encrypt) return EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key, iv) == 1;
return EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key, iv) == 1;
}
static Bytes RunGcm(unsigned char* context, const GcmVector& test, int encrypt, bool generic, std::size_t chunk) {
const auto key = Hex(test.key);
const auto iv = Hex(test.iv);
const auto aad = Hex(test.aad);
const auto input = Hex(encrypt ? test.plain : test.encrypted);
const Update update = generic ? EVP_CipherUpdate_nid_postfix : encrypt ? EVP_EncryptUpdate_nid_postfix : EVP_DecryptUpdate_nid_postfix;
Require(InitForm(context, test.descriptor(), nullptr, nullptr, encrypt, generic));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvLength, static_cast<int>(iv.size()), nullptr) == 1);
Require(InitForm(context, nullptr, key.data(), iv.data(), encrypt, generic));
int written = -1;
if (!aad.empty()) Require(update(Context(context), nullptr, &written, aad.data(), static_cast<int>(aad.size())) == 1 && written == static_cast<int>(aad.size()));
Bytes output(input.size(), 0xEE);
for (std::size_t offset = 0; offset < input.size(); offset += chunk) {
const int length = static_cast<int>(input.size() - offset < chunk ? input.size() - offset : chunk);
Require(update(Context(context), output.data() + offset, &written, input.data() + offset, length) == 1 && written == length);
}
return output;
}
static void TestAesGcmVectors() {
for (const auto& test : GcmVectors) {
const auto* cipher = reinterpret_cast<const Cipher*>(test.descriptor());
const auto key = Hex(test.key);
const int nid = key.size() == 16 ? 895 : key.size() == 24 ? 898 : 901;
Require(EvpCipher(cipher) == test.descriptor());
RequireDescriptor(cipher, nid, key.size(), 12, 0x305476, 680);
Require(cipher->cleanup != nullptr && cipher->control != nullptr);
const auto tag = Hex(test.tag);
for (std::size_t chunk : {1, 7, 64}) {
for (bool generic : {false, true}) {
unsigned char context[ContextBytes] {};
unsigned char computed[16] {};
int written = -1;
Require(RunGcm(context, test, 1, generic, chunk) == Hex(test.encrypted));
const Final encryptFinal = generic ? EVP_CipherFinal_ex_nid_postfix : EVP_EncryptFinal_ex_nid_postfix;
Require(encryptFinal(Context(context), computed, &written) == 1 && written == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 16, computed) == 1 && Bytes(computed, computed + 16) == tag);
std::memset(computed, 0, sizeof(computed));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 12, computed) == 1 && std::memcmp(computed, tag.data(), 12) == 0 && computed[12] == 0);
Require(encryptFinal(Context(context), computed, &written) == 0);
Require(RunGcm(context, test, 0, generic, chunk) == Hex(test.plain));
const Final decryptFinal = generic ? EVP_CipherFinal_ex_nid_postfix : EVP_DecryptFinal_ex_nid_postfix;
Require(decryptFinal(Context(context), computed, &written) == 0 && written == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 16, const_cast<unsigned char*>(tag.data())) == 1);
Require(decryptFinal(Context(context), computed, &written) == 1 && written == 0);
Require(RunGcm(context, test, 0, generic, chunk) == Hex(test.plain));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 16, const_cast<unsigned char*>(tag.data())) == 1);
written = -1;
Require(decryptFinal(Context(context), computed, &written) == 1 && written == 0);
auto wrong = tag;
wrong[5] ^= 0x20;
Require(RunGcm(context, test, 0, generic, chunk) == Hex(test.plain));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 16, wrong.data()) == 1);
Require(decryptFinal(Context(context), computed, &written) == 0 && written == 0);
Require(RunGcm(context, test, 0, generic, chunk) == Hex(test.plain));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 12, wrong.data()) == 1);
Require(decryptFinal(Context(context), computed, &written) == 0);
Require(RunGcm(context, test, 0, generic, chunk) == Hex(test.plain));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 5, const_cast<unsigned char*>(tag.data())) == 1);
Require(decryptFinal(Context(context), computed, &written) == 1);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
RequireCleared(context);
}
}
for (std::size_t index = 0; index < tag.size(); ++index) {
unsigned char context[ContextBytes] {};
unsigned char computed[16] {};
int written = -1;
auto wrong = tag;
wrong[index] ^= 0x01;
Require(RunGcm(context, test, 0, false, 64) == Hex(test.plain));
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 16, wrong.data()) == 1);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), computed, &written) == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
}
}
static void TestAesGcmControl() {
const auto* cipher = reinterpret_cast<const Cipher*>(EVP_aes_128_gcm_nid_postfix());
const auto key = Hex(Gcm3Key);
const auto iv = Hex(Gcm3Iv);
const auto plain = Hex(Gcm3Plain);
unsigned char context[ContextBytes] {};
unsigned char tag[17] {};
unsigned char out[64] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, nullptr, nullptr) == 1);
Require(Load<void*>(context, CipherDataOffset) != nullptr);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvLength, 0, nullptr) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvLength, -3, nullptr) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 16, tag) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 16, tag) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), 0x7777, 0, nullptr) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmIvGenerate, 8, tag) == 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 16) == 0 && written == -1);
Require(EVP_EncryptFinal_ex_nid_postfix(Context(context), out, &written) == 0);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, key.data(), nullptr) == 1);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 16) == 0);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, iv.data()) == 1);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 16) == 1 && written == 16);
Require(std::memcmp(out, Hex(GcmVectors[2].encrypted).data(), 16) == 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), nullptr, &written, plain.data(), 4) == 0 && written == 16);
Require(EVP_DecryptUpdate_nid_postfix(Context(context), nullptr, &written, plain.data(), 4) == 0 && written == 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data() + 16, 48) == 1 && written == 48);
Require(EVP_EncryptFinal_ex_nid_postfix(Context(context), out, &written) == 1 && written == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 17, tag) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 0, tag) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 16, tag) == 1);
Require(Bytes(tag, tag + 16) == Hex(GcmVectors[2].tag));
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, key.data(), iv.data()) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmGetTag, 16, tag) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 17, tag) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetTag, 0, tag) == 0);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), iv.data()) == 1);
Require(EVP_DecryptFinal_ex_nid_postfix(Context(context), out, &written) == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
const auto& longIv = GcmVectors[5];
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, nullptr, nullptr) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvLength, 60, nullptr) == 1);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, nullptr, Hex(longIv.iv).data()) == 1);
for (std::size_t index = 0; index < 16; ++index) Require(context[IvOffset + index] == 0);
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), nullptr, nullptr, key.data(), nullptr) == 1);
const auto aad = Hex(longIv.aad);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), nullptr, &written, aad.data(), static_cast<int>(aad.size())) == 1);
unsigned char copy[ContextBytes];
std::memcpy(copy, context, ContextBytes);
auto* data = malloc_nid_postfix(GcmContextBytes);
Require(data != nullptr);
std::memcpy(data, Load<void*>(context, CipherDataOffset), GcmContextBytes);
Store(copy, CipherDataOffset, data);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), ControlCopy, 0, copy) == 1);
for (unsigned char* each : {context, copy}) {
const auto message = Hex(longIv.plain);
Require(EVP_EncryptUpdate_nid_postfix(Context(each), out, &written, message.data(), static_cast<int>(message.size())) == 1);
Require(Bytes(out, out + written) == Hex(longIv.encrypted));
Require(EVP_EncryptFinal_ex_nid_postfix(Context(each), out, &written) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(each), GcmGetTag, 16, tag) == 1 && Bytes(tag, tag + 16) == Hex(longIv.tag));
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(each)) == 1);
}
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, nullptr, nullptr) == 1);
std::memcpy(copy, context, ContextBytes);
data = malloc_nid_postfix(GcmContextBytes);
Require(data != nullptr);
std::memcpy(data, Load<void*>(context, CipherDataOffset), GcmContextBytes);
Store(copy, CipherDataOffset, data);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), ControlCopy, 0, copy) == 1);
Require(EVP_EncryptInit_ex_nid_postfix(Context(copy), nullptr, nullptr, nullptr, iv.data()) == 1);
Require(std::memcmp(copy + IvOffset, iv.data(), 12) == 0);
for (std::size_t index = 0; index < 16; ++index) Require(context[IvOffset + index] == 0);
Require(EVP_EncryptInit_ex_nid_postfix(Context(copy), nullptr, nullptr, key.data(), nullptr) == 1);
Require(EVP_EncryptUpdate_nid_postfix(Context(copy), out, &written, plain.data(), 64) == 1);
Require(Bytes(out, out + 64) == Hex(GcmVectors[2].encrypted));
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(copy)) == 1);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvLength, 12, nullptr) == 0);
RequireThrows([&] { cipher->control(context, GcmSetIvLength, 12, nullptr); });
RequireThrows([&] { cipher->doCipher(context, out, plain.data(), 16); });
}
static Bytes TlsAadFor(int sequence, int length) {
Bytes aad(13, 0);
aad[7] = static_cast<unsigned char>(sequence);
aad[8] = 0x17;
aad[9] = 0x03;
aad[10] = 0x03;
aad[11] = static_cast<unsigned char>(length >> 8);
aad[12] = static_cast<unsigned char>(length);
return aad;
}
static void TestAesGcmTls() {
const auto* cipher = reinterpret_cast<const Cipher*>(EVP_aes_128_gcm_nid_postfix());
const auto key = Hex(Gcm3Key);
const auto iv = Hex(Gcm3Iv);
const auto message = Hex(Gcm3Plain);
const Bytes plain(message.begin(), message.begin() + 37);
const Bytes records[] {
Hex("facedbaddecaf88842831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b2544c778d019b1b98643bb3d0d3467c192e"),
Hex("facedbaddecaf8895c21c68aa9787c7294b2d7a47a6e8e4d8adafeea894bf504323d55f62afe5ba118a028444dd9c8388aa114d2b38f3541e5513447cc"),
};
unsigned char context[ContextBytes] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), nullptr) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvInvocation, 8, const_cast<unsigned char*>(iv.data())) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, -1, const_cast<unsigned char*>(iv.data())) == 1);
for (int sequence : {1, 2}) {
auto aad = TlsAadFor(sequence, 8 + 37);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 16);
Require(context[BufOffset + 11] == 0 && context[BufOffset + 12] == 37);
Bytes record(61, 0xAA);
std::copy(plain.begin(), plain.end(), record.begin() + 8);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 61) == 1 && written == 61);
Require(record == records[sequence - 1]);
}
auto aad = TlsAadFor(3, 7);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 12, aad.data()) == 0);
aad = TlsAadFor(3, 8 + 37);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 16);
Bytes record(61, 0);
Bytes other(61, 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), other.data(), &written, record.data(), 61) == 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 23) == 0);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 61) == 1 && written == 61);
Require(EVP_EncryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 61) == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), nullptr) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, 3, const_cast<unsigned char*>(iv.data())) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, 5, const_cast<unsigned char*>(iv.data())) == 0);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, 4, const_cast<unsigned char*>(iv.data())) == 1);
aad = TlsAadFor(1, 8 + 15);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 0);
for (int sequence : {1, 2}) {
aad = TlsAadFor(sequence, 61);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 16);
Require(context[BufOffset + 11] == 0 && context[BufOffset + 12] == 37);
record = records[sequence - 1];
Require(EVP_DecryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 61) == 1 && written == 37);
Require(Bytes(record.begin() + 8, record.begin() + 45) == plain);
}
aad = TlsAadFor(2, 61);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 16);
record = records[1];
record[60] ^= 1;
Require(EVP_DecryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 61) == 0 && written == 0);
for (std::size_t index = 8; index < 45; ++index) Require(record[index] == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
for (std::size_t index = 45; index < 61; ++index) {
Require(EVP_DecryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), nullptr) == 1);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, 4, const_cast<unsigned char*>(iv.data())) == 1);
aad = TlsAadFor(1, 61);
Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), TlsAad, 13, aad.data()) == 16);
record = records[0];
record[index] ^= 0x01;
Require(EVP_DecryptUpdate_nid_postfix(Context(context), record.data(), &written, record.data(), 61) == 0 && written == 0);
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
}
static void TestEvpCleanup() {
const auto* cipher = EVP_aes_128_ctr_nid_postfix();
EVP_cleanup_nid_postfix();
EVP_cleanup_nid_postfix();
Require(EVP_aes_128_ctr_nid_postfix() == cipher);
const auto key = Hex(Sp80038aKey128);
const auto counter = Hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
const auto plain = Hex("6bc1bee22e409f96e93d7e117393172a");
unsigned char context[ContextBytes] {};
unsigned char out[16] {};
int written = -1;
Require(EVP_EncryptInit_ex_nid_postfix(Context(context), EvpCipher(cipher), nullptr, key.data(), counter.data()) == 1);
EVP_cleanup_nid_postfix();
Require(EVP_EncryptUpdate_nid_postfix(Context(context), out, &written, plain.data(), 16) == 1 && written == 16);
Require(Bytes(out, out + 16) == Hex("874d6191b620e3261bef6864990db6ce"));
Require(EVP_CIPHER_CTX_cleanup_nid_postfix(Context(context)) == 1);
}
int main() {
InitializeHeap();
TestInit();
TestNew();
TestSetPadding();
TestCleanup();
TestFree();
TestCtrl();
TestPadding();
TestHeldBlock();
TestBadPadding();
TestNoPadding();
TestInitFields();
TestStreamCipher();
TestCustomCipher();
TestUpdateArguments();
TestAesCbc();
TestAesCfb8();
TestAesCtr();
TestAesGcmVectors();
TestAesGcmControl();
TestAesGcmTls();
TestEvpCleanup();
return 0;
}