#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 #include #include #include #include #include #include #include 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 static T Load(const unsigned char* context, std::size_t offset) { T value {}; std::memcpy(&value, context + offset, sizeof(value)); return value; } template 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(memory); } static const LibreSslEvpCipher* EvpCipher(const void* cipher) { return static_cast(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 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(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(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(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(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(context, CipherOffset) == &cipher && Load(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(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; 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(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(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(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(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(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(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(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(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(context, FinalUsedOffset) == 1 && Load(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(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(context, FinalUsedOffset) == 0 && Load(context, BufLenOffset) == 1); Require(EVP_DecryptUpdate_nid_postfix(Context(context), out, &written, encrypted.data() + 17, 15) == 1); Require(written == 0 && Load(context, FinalUsedOffset) == 1 && Load(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(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(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(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(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(context, CipherOffset) == &cipher && Load(context, EngineOffset) == nullptr); Require(Load(context, EncryptOffset) == 1 && Load(context, KeyLenOffset) == 24 && Load(context, BlockMaskOffset) == 15); Require(Load(context, BufLenOffset) == 0 && Load(context, FinalUsedOffset) == 0 && Load(context, FlagsOffset) == 0); Require(Load(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(context, EncryptOffset) == 0); Require(EVP_CipherInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr, -1) == 1); Require(calls.initEncrypt == 0 && Load(context, EncryptOffset) == 0); Require(EVP_CipherInit_ex_nid_postfix(Context(context), nullptr, nullptr, XorKey, nullptr, 7) == 1); Require(calls.initEncrypt == 1 && Load(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(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(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(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(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((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(-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(digit <= '9' ? digit - '0' : digit - 'a' + 10); }; Bytes bytes; for (; text[0] && text[1]; text += 2) bytes.push_back(static_cast(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(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(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(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(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(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(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(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(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(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(aad.size())) == 1 && written == static_cast(aad.size())); Bytes output(input.size(), 0xEE); for (std::size_t offset = 0; offset < input.size(); offset += chunk) { const int length = static_cast(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(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(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(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(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(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(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(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(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(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(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(sequence); aad[8] = 0x17; aad[9] = 0x03; aad[10] = 0x03; aad[11] = static_cast(length >> 8); aad[12] = static_cast(length); return aad; } static void TestAesGcmTls() { const auto* cipher = reinterpret_cast(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(iv.data())) == 0); Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, -1, const_cast(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(iv.data())) == 0); Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, 5, const_cast(iv.data())) == 0); Require(EVP_CIPHER_CTX_ctrl_nid_postfix(Context(context), GcmSetIvFixed, 4, const_cast(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(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; }