// Copyright (c) 2025-2026, Brandon Lehmann // BSD 3-Clause License (see LICENSE) #include "tinyaes/gcm.h" #include "internal/aes_impl.h" #include "internal/ghash.h" #include #include #include #include // 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 &key, const std::vector &iv, const std::vector &aad, const std::vector &ct, const std::vector &tag, uint8_t tamper_byte) { std::vector pt; // Tamper ciphertext (if non-empty) if (!ct.empty()) { std::vector 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 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 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 < 2) return 0; // 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 key(data, data + key_len); std::vector 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 aad(data + header, data + header + aad_len); std::vector plaintext(data + header + aad_len, data + size); std::vector ct, tag, pt; auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag); if (result != tinyaes::Result::Ok) return 0; result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt); 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; }