Fix ARM CE byte ordering, expand C/C++ API, and harden build
ARM backends: fix round key byte-swap on little-endian (vrev32q_u8), rewrite decrypt to pre-process middle keys with InvMixColumns, fix GHASH PMULL reflect and reduction ordering. API: add nonce/IV-generating convenience overloads for CTR, CBC, and GCM (library generates and prepends nonce, appends tag). Add C API for IV/nonce generation. Rename error codes (TINYAES_OK, Result::Ok, Result::AuthenticationFailed, etc.). Build: add MinGW GCC AVX-512 debug alignment fix, harden bench/fuzz CMake targets (warnings-as-errors, linker hardening), align with tinysha CMake conventions. Add README. Tests: expand coverage for nonce-generating API overloads, add NIST GCM test vectors, improve fuzz target differential testing.
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# libFuzzer is only reliably available on Linux Clang.
|
||||
# macOS: Apple Clang lacks the runtime; Homebrew LLVM has ABI mismatches
|
||||
# with macOS system libc++. Windows: CRT mismatches in Debug/Release.
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
return()
|
||||
endif()
|
||||
@@ -19,6 +23,13 @@ foreach(target ${FUZZ_TARGETS})
|
||||
CXX_EXTENSIONS OFF
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/fuzz
|
||||
)
|
||||
target_compile_options(${target} PRIVATE -fsanitize=fuzzer,address)
|
||||
target_compile_options(${target} PRIVATE
|
||||
-Wall -Wextra -Wpedantic -Werror
|
||||
-fsanitize=fuzzer,address
|
||||
)
|
||||
set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fsanitize=fuzzer,address")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS
|
||||
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
@@ -2,34 +2,62 @@
|
||||
// BSD 3-Clause License (see LICENSE)
|
||||
|
||||
#include "tinyaes/cbc.h"
|
||||
#include "internal/aes_impl.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// Differential: verify portable encrypt_block matches dispatched for each block
|
||||
static void diff_encrypt_block(const uint8_t *key, size_t key_len, const uint8_t block[16])
|
||||
{
|
||||
int rounds = tinyaes::internal::aes_rounds(key_len);
|
||||
if (rounds == 0)
|
||||
return;
|
||||
|
||||
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS];
|
||||
tinyaes::internal::aes_key_expand_portable(key, key_len, rk);
|
||||
|
||||
uint8_t out_portable[16], out_dispatch[16];
|
||||
tinyaes::internal::aes_encrypt_block_portable(rk, rounds, block, out_portable);
|
||||
tinyaes::internal::get_encrypt_block()(rk, rounds, block, out_dispatch);
|
||||
assert(std::memcmp(out_portable, out_dispatch, 16) == 0);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < 32)
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
// First 16 bytes = key, next 16 bytes = IV, rest = plaintext
|
||||
std::vector<uint8_t> key(data, data + 16);
|
||||
std::vector<uint8_t> iv(data + 16, data + 32);
|
||||
// First byte selects key size: 16, 24, or 32
|
||||
static const size_t key_sizes[] = {16, 24, 32};
|
||||
size_t key_len = key_sizes[data[0] % 3];
|
||||
data++;
|
||||
size--;
|
||||
|
||||
size_t remaining = size - 32;
|
||||
if (size < key_len + 16 + 1)
|
||||
return 0;
|
||||
|
||||
std::vector<uint8_t> key(data, data + key_len);
|
||||
std::vector<uint8_t> iv(data + key_len, data + key_len + 16);
|
||||
|
||||
size_t remaining = size - key_len - 16;
|
||||
if (remaining == 0)
|
||||
return 0;
|
||||
|
||||
// Use PKCS#7 which accepts any length
|
||||
std::vector<uint8_t> plaintext(data + 32, data + size);
|
||||
std::vector<uint8_t> plaintext(data + key_len + 16, data + size);
|
||||
std::vector<uint8_t> ct, pt;
|
||||
|
||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
||||
if (result != tinyaes::Result::Success)
|
||||
if (result != tinyaes::Result::Ok)
|
||||
return 0;
|
||||
|
||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
||||
assert(result == tinyaes::Result::Success);
|
||||
assert(result == tinyaes::Result::Ok);
|
||||
assert(pt == plaintext);
|
||||
|
||||
// Differential: compare portable vs dispatched on a single block from the input
|
||||
diff_encrypt_block(data, key_len, data + key_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,28 +2,69 @@
|
||||
// BSD 3-Clause License (see LICENSE)
|
||||
|
||||
#include "tinyaes/ctr.h"
|
||||
#include "internal/aes_impl.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// Differential: run CTR pipeline via portable vs dispatched and compare
|
||||
static void diff_ctr_pipeline(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len,
|
||||
const uint8_t iv[16])
|
||||
{
|
||||
int rounds = tinyaes::internal::aes_rounds(key_len);
|
||||
if (rounds == 0)
|
||||
return;
|
||||
|
||||
size_t blocks = data_len / 16;
|
||||
if (blocks == 0)
|
||||
return;
|
||||
|
||||
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS];
|
||||
tinyaes::internal::aes_key_expand_portable(key, key_len, rk);
|
||||
|
||||
std::vector<uint8_t> out_portable(blocks * 16);
|
||||
std::vector<uint8_t> out_dispatch(blocks * 16);
|
||||
uint8_t ctr_p[16], ctr_d[16];
|
||||
std::memcpy(ctr_p, iv, 16);
|
||||
std::memcpy(ctr_d, iv, 16);
|
||||
|
||||
tinyaes::internal::aes_ctr_pipeline_portable(rk, rounds, data, out_portable.data(), blocks, ctr_p);
|
||||
tinyaes::internal::get_ctr_pipeline()(rk, rounds, data, out_dispatch.data(), blocks, ctr_d);
|
||||
|
||||
assert(out_portable == out_dispatch);
|
||||
assert(std::memcmp(ctr_p, ctr_d, 16) == 0);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < 33)
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
// First 16 bytes = key, next 16 bytes = IV, rest = plaintext
|
||||
std::vector<uint8_t> key(data, data + 16);
|
||||
std::vector<uint8_t> iv(data + 16, data + 32);
|
||||
std::vector<uint8_t> plaintext(data + 32, data + size);
|
||||
// First byte selects key size: 16, 24, or 32
|
||||
static const size_t key_sizes[] = {16, 24, 32};
|
||||
size_t key_len = key_sizes[data[0] % 3];
|
||||
data++;
|
||||
size--;
|
||||
|
||||
if (size < key_len + 16 + 1)
|
||||
return 0;
|
||||
|
||||
std::vector<uint8_t> key(data, data + key_len);
|
||||
std::vector<uint8_t> iv(data + key_len, data + key_len + 16);
|
||||
std::vector<uint8_t> plaintext(data + key_len + 16, data + size);
|
||||
std::vector<uint8_t> ct, pt;
|
||||
|
||||
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
|
||||
if (result != tinyaes::Result::Success)
|
||||
if (result != tinyaes::Result::Ok)
|
||||
return 0;
|
||||
|
||||
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
||||
assert(result == tinyaes::Result::Success);
|
||||
assert(result == tinyaes::Result::Ok);
|
||||
assert(pt == plaintext);
|
||||
|
||||
// Differential: portable vs dispatched CTR pipeline
|
||||
diff_ctr_pipeline(data, key_len, data + key_len + 16, size - key_len - 16, data + key_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,31 +2,72 @@
|
||||
// BSD 3-Clause License (see LICENSE)
|
||||
|
||||
#include "tinyaes/ecb.h"
|
||||
#include "internal/aes_impl.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// Differential: compare dispatched encrypt_block against portable
|
||||
static void diff_encrypt_block(const uint8_t *key, size_t key_len, const uint8_t *plaintext, size_t pt_len)
|
||||
{
|
||||
int rounds = tinyaes::internal::aes_rounds(key_len);
|
||||
if (rounds == 0)
|
||||
return;
|
||||
|
||||
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS];
|
||||
|
||||
// Portable key expansion + encrypt
|
||||
tinyaes::internal::aes_key_expand_portable(key, key_len, rk);
|
||||
|
||||
for (size_t off = 0; off + 16 <= pt_len; off += 16)
|
||||
{
|
||||
uint8_t out_portable[16], out_dispatch[16];
|
||||
tinyaes::internal::aes_encrypt_block_portable(rk, rounds, plaintext + off, out_portable);
|
||||
tinyaes::internal::get_encrypt_block()(rk, rounds, plaintext + off, out_dispatch);
|
||||
assert(std::memcmp(out_portable, out_dispatch, 16) == 0);
|
||||
|
||||
// Also verify decrypt roundtrip
|
||||
uint8_t dec_portable[16], dec_dispatch[16];
|
||||
tinyaes::internal::aes_decrypt_block_portable(rk, rounds, out_portable, dec_portable);
|
||||
tinyaes::internal::get_decrypt_block()(rk, rounds, out_dispatch, dec_dispatch);
|
||||
assert(std::memcmp(dec_portable, dec_dispatch, 16) == 0);
|
||||
assert(std::memcmp(dec_portable, plaintext + off, 16) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < 16)
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
// Use first 16 bytes as key, rest as plaintext (block-aligned)
|
||||
std::vector<uint8_t> key(data, data + 16);
|
||||
size_t pt_len = ((size - 16) / 16) * 16;
|
||||
// First byte selects key size: 16, 24, or 32
|
||||
static const size_t key_sizes[] = {16, 24, 32};
|
||||
size_t key_len = key_sizes[data[0] % 3];
|
||||
data++;
|
||||
size--;
|
||||
|
||||
if (size < key_len + 16)
|
||||
return 0;
|
||||
|
||||
std::vector<uint8_t> key(data, data + key_len);
|
||||
size_t pt_len = ((size - key_len) / 16) * 16;
|
||||
if (pt_len == 0)
|
||||
return 0;
|
||||
|
||||
std::vector<uint8_t> plaintext(data + 16, data + 16 + pt_len);
|
||||
std::vector<uint8_t> plaintext(data + key_len, data + key_len + pt_len);
|
||||
std::vector<uint8_t> ct, pt;
|
||||
|
||||
auto result = tinyaes::ecb_encrypt(key, plaintext, ct);
|
||||
if (result != tinyaes::Result::Success)
|
||||
if (result != tinyaes::Result::Ok)
|
||||
return 0;
|
||||
|
||||
result = tinyaes::ecb_decrypt(key, ct, pt);
|
||||
assert(result == tinyaes::Result::Success);
|
||||
assert(result == tinyaes::Result::Ok);
|
||||
assert(pt == plaintext);
|
||||
|
||||
// Differential test: portable vs dispatched
|
||||
diff_encrypt_block(data, key_len, data + key_len, pt_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,34 +2,117 @@
|
||||
// BSD 3-Clause License (see LICENSE)
|
||||
|
||||
#include "tinyaes/gcm.h"
|
||||
#include "internal/aes_impl.h"
|
||||
#include "internal/ghash.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// Differential: compare portable GHASH against dispatched
|
||||
static void diff_ghash(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len)
|
||||
{
|
||||
int rounds = tinyaes::internal::aes_rounds(key_len);
|
||||
if (rounds == 0)
|
||||
return;
|
||||
|
||||
// Compute H = E_K(0^128)
|
||||
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS];
|
||||
tinyaes::internal::aes_key_expand_portable(key, key_len, rk);
|
||||
|
||||
uint8_t H[16] = {0};
|
||||
uint8_t zero[16] = {0};
|
||||
tinyaes::internal::aes_encrypt_block_portable(rk, rounds, zero, H);
|
||||
|
||||
uint8_t Y_portable[16] = {0};
|
||||
uint8_t Y_dispatch[16] = {0};
|
||||
|
||||
tinyaes::internal::ghash_portable(H, data, data_len, Y_portable);
|
||||
tinyaes::internal::get_ghash()(H, data, data_len, Y_dispatch);
|
||||
|
||||
assert(std::memcmp(Y_portable, Y_dispatch, 16) == 0);
|
||||
}
|
||||
|
||||
// Tamper test: flip a bit in ciphertext/tag/aad and verify auth failure
|
||||
static void tamper_test(const std::vector<uint8_t> &key, const std::vector<uint8_t> &iv,
|
||||
const std::vector<uint8_t> &aad, const std::vector<uint8_t> &ct,
|
||||
const std::vector<uint8_t> &tag, uint8_t tamper_byte)
|
||||
{
|
||||
std::vector<uint8_t> pt;
|
||||
|
||||
// Tamper ciphertext (if non-empty)
|
||||
if (!ct.empty())
|
||||
{
|
||||
std::vector<uint8_t> bad_ct = ct;
|
||||
bad_ct[tamper_byte % bad_ct.size()] ^= 0x01;
|
||||
auto result = tinyaes::gcm_decrypt(key, iv, aad, bad_ct, tag, pt);
|
||||
assert(result == tinyaes::Result::AuthenticationFailed);
|
||||
}
|
||||
|
||||
// Tamper tag
|
||||
{
|
||||
std::vector<uint8_t> bad_tag = tag;
|
||||
bad_tag[tamper_byte % 16] ^= 0x01;
|
||||
auto result = tinyaes::gcm_decrypt(key, iv, aad, ct, bad_tag, pt);
|
||||
assert(result == tinyaes::Result::AuthenticationFailed);
|
||||
}
|
||||
|
||||
// Tamper AAD (if non-empty)
|
||||
if (!aad.empty())
|
||||
{
|
||||
std::vector<uint8_t> bad_aad = aad;
|
||||
bad_aad[tamper_byte % bad_aad.size()] ^= 0x01;
|
||||
auto result = tinyaes::gcm_decrypt(key, iv, bad_aad, ct, tag, pt);
|
||||
assert(result == tinyaes::Result::AuthenticationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < 29)
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
// First 16 bytes = key, next 12 bytes = IV, 1 byte = AAD length, rest split
|
||||
std::vector<uint8_t> key(data, data + 16);
|
||||
std::vector<uint8_t> iv(data + 16, data + 28);
|
||||
size_t aad_len = data[28] % (size - 29 + 1);
|
||||
if (29 + aad_len > size)
|
||||
// First byte selects key size: 16, 24, or 32
|
||||
static const size_t key_sizes[] = {16, 24, 32};
|
||||
size_t key_len = key_sizes[data[0] % 3];
|
||||
data++;
|
||||
size--;
|
||||
|
||||
// key_len bytes key + 12 bytes IV + 1 byte AAD length selector
|
||||
if (size < key_len + 13)
|
||||
return 0;
|
||||
|
||||
std::vector<uint8_t> key(data, data + key_len);
|
||||
std::vector<uint8_t> iv(data + key_len, data + key_len + 12);
|
||||
size_t header = key_len + 12 + 1;
|
||||
uint8_t aad_selector = data[key_len + 12];
|
||||
size_t remaining = size - header;
|
||||
size_t aad_len = aad_selector % (remaining + 1);
|
||||
if (aad_len > remaining)
|
||||
aad_len = 0;
|
||||
|
||||
std::vector<uint8_t> aad(data + 29, data + 29 + aad_len);
|
||||
std::vector<uint8_t> plaintext(data + 29 + aad_len, data + size);
|
||||
std::vector<uint8_t> aad(data + header, data + header + aad_len);
|
||||
std::vector<uint8_t> plaintext(data + header + aad_len, data + size);
|
||||
|
||||
std::vector<uint8_t> ct, tag, pt;
|
||||
|
||||
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
|
||||
if (result != tinyaes::Result::Success)
|
||||
if (result != tinyaes::Result::Ok)
|
||||
return 0;
|
||||
|
||||
result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt);
|
||||
assert(result == tinyaes::Result::Success);
|
||||
assert(result == tinyaes::Result::Ok);
|
||||
assert(pt == plaintext);
|
||||
|
||||
// Differential: portable GHASH vs dispatched
|
||||
size_t total_data = aad_len + plaintext.size();
|
||||
if (total_data > 0)
|
||||
{
|
||||
diff_ghash(data, key_len, data + header, total_data);
|
||||
}
|
||||
|
||||
// Tamper test: verify authentication catches single-bit corruption
|
||||
tamper_test(key, iv, aad, ct, tag, aad_selector);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user