use cryptoxide::aes_gcm::{AesGcm256, DecryptionResult, Tag};
use serde::Deserialize;
#[cfg(doc)]
use super::Cipher;
const VECTORS: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/vectors/wycheproof/aes_gcm_test.json"
));
const KEY_SIZE: u32 = 256;
const IV_SIZE: u32 = 96;
const TAG_SIZE: u32 = 128;
const EXPECTED: (u32, u32, u32) = (66, 39, 27);
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct VectorFile {
algorithm: String,
number_of_tests: u32,
test_groups: Vec<TestGroup>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct TestGroup {
key_size: u32,
iv_size: u32,
tag_size: u32,
tests: Vec<TestCase>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct TestCase {
tc_id: u32,
key: String,
iv: String,
aad: String,
msg: String,
ct: String,
tag: String,
result: String,
}
fn decode(hex_str: &str) -> Vec<u8> {
hex::decode(hex_str).expect("hex")
}
#[test]
fn wycheproof_aes_gcm_256_96_128() {
let file: VectorFile = serde_json::from_str(VECTORS).expect("valid wycheproof json");
assert_eq!(file.algorithm, "AES-GCM", "wrong Wycheproof corpus");
assert_eq!(file.number_of_tests, 316, "corpus size moved — re-pin");
let (mut ran, mut valid, mut invalid) = (0u32, 0u32, 0u32);
for group in &file.test_groups {
if group.key_size != KEY_SIZE || group.iv_size != IV_SIZE {
continue;
}
assert_eq!(
group.tag_size, TAG_SIZE,
"unexpected truncated-tag group at this pin"
);
for case in &group.tests {
ran += 1;
let key: [u8; 32] = decode(&case.key).try_into().expect("256-bit key");
let iv: [u8; 12] = decode(&case.iv).try_into().expect("96-bit iv");
let (aad, msg, ct) = (decode(&case.aad), decode(&case.msg), decode(&case.ct));
let tag: [u8; 16] = decode(&case.tag).try_into().expect("128-bit tag");
let cipher = AesGcm256::new(&key);
match case.result.as_str() {
"valid" => {
valid += 1;
let mut got_ct = vec![0u8; msg.len()];
let mut got_tag = Tag([0u8; 16]);
cipher.encrypt(&iv, &aad, &msg, &mut got_ct, &mut got_tag);
assert_eq!(got_ct, ct, "tcId {} ciphertext", case.tc_id);
assert_eq!(got_tag.0, tag, "tcId {} tag", case.tc_id);
let mut got_pt = vec![0u8; ct.len()];
assert_eq!(
cipher.decrypt(&iv, &aad, &ct, &mut got_pt, &Tag(tag)),
DecryptionResult::Match,
"tcId {} must verify",
case.tc_id
);
assert_eq!(got_pt, msg, "tcId {} plaintext", case.tc_id);
}
"invalid" => {
invalid += 1;
let mut got_pt = vec![0u8; ct.len()];
assert_eq!(
cipher.decrypt(&iv, &aad, &ct, &mut got_pt, &Tag(tag)),
DecryptionResult::MisMatch,
"tcId {} must be REJECTED",
case.tc_id
);
}
other => panic!("tcId {}: unexpected result class {other}", case.tc_id),
}
}
}
assert_eq!(
(ran, valid, invalid),
EXPECTED,
"applicable subset moved — re-derive the census in PROVENANCE.md"
);
}