0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 12:02:18 +02:00
libsignal/rust/crypto/tests/aes_gcm.rs
Jordan Rose 77b5cf6b59 crypto: Remove Result type from infallible operations
And in the bridge layer, use panics instead of Result for the
programmer error of using an encryption/decryption stream after
finalization, eliminating the need for the InvalidState error.

(By "infallible operations", I mean operations which have no possible
failure according to the specification of their associated algorithm:
AES-CTR and AES-GCM are stream ciphers that can always operate on
arbitrary buffers.)
2024-02-08 15:43:35 -08:00

170 lines
5.2 KiB
Rust

//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use hex_literal::hex;
use rand::Rng;
use serde::Deserialize;
use std::collections::HashMap;
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct WycheproofTest {
#[serde(rename = "tcId")]
tc_id: usize,
comment: String,
key: String,
#[serde(rename = "iv")]
nonce: String,
aad: String,
#[serde(rename = "msg")]
pt: String,
ct: String,
tag: String,
result: String,
flags: Vec<String>,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct WycheproofTestGroup {
#[serde(rename = "ivSize")]
iv_size: usize,
#[serde(rename = "keySize")]
key_size: usize,
#[serde(rename = "tagSize")]
tag_size: usize,
#[serde(rename = "type")]
typ: String,
tests: Vec<WycheproofTest>,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct WycheproofTestSet {
algorithm: String,
#[serde(rename = "generatorVersion")]
generator_version: String,
#[serde(rename = "numberOfTests")]
number_of_tests: usize,
header: Vec<String>,
notes: HashMap<String, String>,
schema: String,
#[serde(rename = "testGroups")]
test_groups: Vec<WycheproofTestGroup>,
}
fn test_kat(kat: WycheproofTest) -> Result<(), signal_crypto::Error> {
let mut rng = rand::rngs::OsRng;
let key = hex::decode(kat.key).expect("valid hex");
let aad = hex::decode(kat.aad).expect("valid hex");
let nonce = hex::decode(kat.nonce).expect("valid hex");
let tag = hex::decode(kat.tag).expect("valid hex");
let pt = hex::decode(kat.pt).expect("valid hex");
let ct = hex::decode(kat.ct).expect("valid hex");
let valid = match kat.result.as_ref() {
"valid" => true,
"invalid" => false,
wut => panic!("unknown result field {}", wut),
};
let mut gcm_enc = signal_crypto::Aes256GcmEncryption::new(&key, &nonce, &aad)?;
let mut buf = pt.clone();
gcm_enc.encrypt(&mut buf);
let generated_tag = gcm_enc.compute_tag();
let mut gcm_dec = signal_crypto::Aes256GcmDecryption::new(&key, &nonce, &aad)?;
if valid {
assert_eq!(hex::encode(generated_tag), hex::encode(&tag));
assert_eq!(hex::encode(&buf), hex::encode(&ct));
gcm_dec.decrypt(&mut buf);
assert!(gcm_dec.verify_tag(&tag).is_ok());
assert_eq!(hex::encode(&buf), hex::encode(&pt));
for i in 2..32 {
println!("Test {}", i);
// Do it again but with split inputs:
let mut gcm_enc = signal_crypto::Aes256GcmEncryption::new(&key, &nonce, &aad)?;
let mut gcm_dec = signal_crypto::Aes256GcmDecryption::new(&key, &nonce, &aad)?;
let mut enc_buf = pt.clone();
let mut dec_buf = ct.clone();
let mut processed = 0;
while processed != buf.len() {
let remaining = buf.len() - processed;
let this_time = if remaining > 1 {
rng.gen_range(1..remaining)
} else {
remaining
};
assert!(this_time > 0);
gcm_enc.encrypt(&mut enc_buf[processed..processed + this_time]);
gcm_dec.decrypt(&mut dec_buf[processed..processed + this_time]);
processed += this_time;
}
assert_eq!(hex::encode(gcm_enc.compute_tag()), hex::encode(&tag));
assert!(gcm_dec.verify_tag(&tag).is_ok());
assert_eq!(hex::encode(enc_buf), hex::encode(&ct));
assert_eq!(hex::encode(dec_buf), hex::encode(&pt));
}
} else {
assert_ne!(hex::encode(generated_tag), hex::encode(&tag));
gcm_dec.decrypt(&mut buf);
assert!(matches!(
gcm_dec.verify_tag(&tag),
Err(signal_crypto::Error::InvalidTag)
));
}
Ok(())
}
#[test]
fn aes_gcm_wycheproof_kats() -> Result<(), signal_crypto::Error> {
let kat_data = include_bytes!("data/aes_gcm_test.json");
let kats: WycheproofTestSet = serde_json::from_slice(kat_data).expect("Valid JSON");
assert_eq!(kats.algorithm, "AES-GCM");
for group in kats.test_groups {
if group.iv_size == 96 && group.key_size == 256 && group.tag_size == 128 {
for test in group.tests {
test_kat(test)?
}
}
}
Ok(())
}
#[test]
fn aes_gcm_smoke_test() -> Result<(), signal_crypto::Error> {
let key = hex!("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
let nonce = hex!("cafebabefacedbaddecaf888");
let input = hex!("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39");
let ad = hex!("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let output = hex!("522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f66276fc6ece0f4e1768cddf8853bb2d551b");
let mut aes_gcm = signal_crypto::Aes256GcmEncryption::new(&key, &nonce, &ad)?;
let mut buf = input.to_vec();
aes_gcm.encrypt(&mut buf);
let tag = aes_gcm.compute_tag();
buf.extend_from_slice(&tag);
assert_eq!(hex::encode(buf), hex::encode(output));
Ok(())
}