FIX: wc_XChaCha20Poly1305_Init issues with null AAD when length 0, aad.as_prt() always passed now; also added mroe tests

This commit is contained in:
JohnE 2026-04-16 16:31:05 -07:00
parent 4609e43cc8
commit cbad181ae6
1 changed files with 287 additions and 10 deletions

View File

@ -343,16 +343,18 @@ fn xchacha20_poly1305_encrypt(
unsafe { unsafe {
// wc_XChaCha20Poly1305_Encrypt(dst, dst_space, src, src_len, // wc_XChaCha20Poly1305_Encrypt(dst, dst_space, src, src_len,
// ad, ad_len, nonce, nonce_len, key, key_len) // ad, ad_len, nonce, nonce_len, key, key_len)
//
// NOTE: wolfSSL's wc_XChaCha20Poly1305_Init unconditionally rejects
// a NULL `ad` pointer (BAD_FUNC_ARG / -173), unlike AES-GCM and
// ChaCha20-Poly1305 which allow NULL when ad_len == 0. Always pass
// aad.as_ptr() — Rust guarantees a non-null dangling pointer for
// empty slices.
let ret = crate::sys::wc_XChaCha20Poly1305_Encrypt( let ret = crate::sys::wc_XChaCha20Poly1305_Encrypt(
dst.as_mut_ptr(), dst.as_mut_ptr(),
dst.len(), dst.len(),
plaintext.as_ptr(), plaintext.as_ptr(),
plaintext.len(), plaintext.len(),
if aad.is_empty() { aad.as_ptr(),
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len(), aad.len(),
nonce.as_ptr(), nonce.as_ptr(),
nonce.len(), nonce.len(),
@ -387,16 +389,14 @@ fn xchacha20_poly1305_decrypt(
unsafe { unsafe {
// wc_XChaCha20Poly1305_Decrypt(dst, dst_space, src, src_len, // wc_XChaCha20Poly1305_Decrypt(dst, dst_space, src, src_len,
// ad, ad_len, nonce, nonce_len, key, key_len) // ad, ad_len, nonce, nonce_len, key, key_len)
//
// NOTE: See encrypt above — wolfSSL rejects NULL ad for XChaCha20.
let ret = crate::sys::wc_XChaCha20Poly1305_Decrypt( let ret = crate::sys::wc_XChaCha20Poly1305_Decrypt(
dst.as_mut_ptr(), dst.as_mut_ptr(),
dst.len(), dst.len(),
ciphertext_and_tag.as_ptr(), ciphertext_and_tag.as_ptr(),
ciphertext_and_tag.len(), ciphertext_and_tag.len(),
if aad.is_empty() { aad.as_ptr(),
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len(), aad.len(),
nonce.as_ptr(), nonce.as_ptr(),
nonce.len(), nonce.len(),
@ -410,3 +410,280 @@ fn xchacha20_poly1305_decrypt(
Ok(dst) Ok(dst)
} }
// ──────────────────────────────────────────────────────────────────────────────
// Tests
// ──────────────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use ccc_crypto_core::algorithms::AeadAlgorithm;
// ── Helpers ──────────────────────────────────────────────────────────
/// Encrypt then decrypt and assert the plaintext round-trips.
fn roundtrip(algo: AeadAlgorithm, key: &[u8], nonce: &[u8], pt: &[u8], aad: &[u8]) {
let ct = encrypt(algo, key, nonce, pt, aad)
.unwrap_or_else(|e| panic!("{} encrypt failed: {e}", algo.name()));
// ciphertext must be plaintext + 16-byte tag
assert_eq!(
ct.len(),
pt.len() + TAG_LEN,
"{}: unexpected ciphertext length",
algo.name()
);
let recovered = decrypt(algo, key, nonce, &ct, aad)
.unwrap_or_else(|e| panic!("{} decrypt failed: {e}", algo.name()));
assert_eq!(recovered, pt, "{}: plaintext mismatch after roundtrip", algo.name());
}
// ── AES-256-GCM ─────────────────────────────────────────────────────
#[test]
fn aes_gcm_roundtrip_with_aad() {
roundtrip(
AeadAlgorithm::AesGcm256,
&[0x01; 32],
&[0x02; 12],
b"aes-gcm payload with associated data",
b"some associated data",
);
}
#[test]
fn aes_gcm_roundtrip_empty_aad() {
roundtrip(
AeadAlgorithm::AesGcm256,
&[0x11; 32],
&[0x22; 12],
b"aes-gcm payload no aad",
&[],
);
}
#[test]
fn aes_gcm_roundtrip_empty_plaintext() {
roundtrip(
AeadAlgorithm::AesGcm256,
&[0x31; 32],
&[0x32; 12],
&[],
b"aad only, no plaintext",
);
}
#[test]
fn aes_gcm_roundtrip_empty_both() {
roundtrip(
AeadAlgorithm::AesGcm256,
&[0x41; 32],
&[0x42; 12],
&[],
&[],
);
}
#[test]
fn aes_gcm_tampered_tag_fails() {
let key = [0x51u8; 32];
let nonce = [0x52u8; 12];
let pt = b"integrity check";
let mut ct = encrypt(AeadAlgorithm::AesGcm256, &key, &nonce, pt, &[]).unwrap();
// Flip last byte of the tag
let last = ct.len() - 1;
ct[last] ^= 0xFF;
assert!(decrypt(AeadAlgorithm::AesGcm256, &key, &nonce, &ct, &[]).is_err());
}
// ── ChaCha20-Poly1305 ───────────────────────────────────────────────
#[test]
fn chacha20_roundtrip_with_aad() {
roundtrip(
AeadAlgorithm::ChaCha20Poly1305,
&[0x61; 32],
&[0x62; 12],
b"chacha20 payload with associated data",
b"chacha20 aad",
);
}
#[test]
fn chacha20_roundtrip_empty_aad() {
roundtrip(
AeadAlgorithm::ChaCha20Poly1305,
&[0x71; 32],
&[0x72; 12],
b"chacha20 payload no aad",
&[],
);
}
#[test]
fn chacha20_roundtrip_empty_plaintext() {
roundtrip(
AeadAlgorithm::ChaCha20Poly1305,
&[0x81; 32],
&[0x82; 12],
&[],
b"chacha20 aad only",
);
}
#[test]
fn chacha20_roundtrip_empty_both() {
roundtrip(
AeadAlgorithm::ChaCha20Poly1305,
&[0x91; 32],
&[0x92; 12],
&[],
&[],
);
}
#[test]
fn chacha20_tampered_tag_fails() {
let key = [0xA1u8; 32];
let nonce = [0xA2u8; 12];
let pt = b"integrity check chacha";
let mut ct = encrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce, pt, &[]).unwrap();
let last = ct.len() - 1;
ct[last] ^= 0xFF;
assert!(decrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce, &ct, &[]).is_err());
}
// ── XChaCha20-Poly1305 ──────────────────────────────────────────────
#[test]
fn xchacha20_roundtrip_with_aad() {
roundtrip(
AeadAlgorithm::XChaCha20Poly1305,
&[0xB1; 32],
&[0xB2; 24],
b"xchacha20 payload with associated data",
b"xchacha20 aad",
);
}
/// Regression: wolfSSL's `wc_XChaCha20Poly1305_Init` unconditionally
/// rejects a NULL `ad` pointer (BAD_FUNC_ARG / -173), even when
/// `ad_len == 0`. Must pass a non-null dangling pointer for empty AAD.
#[test]
fn xchacha20_roundtrip_empty_aad() {
roundtrip(
AeadAlgorithm::XChaCha20Poly1305,
&[0xC1; 32],
&[0xC2; 24],
b"xchacha20 payload no aad",
&[],
);
}
#[test]
fn xchacha20_roundtrip_empty_plaintext() {
roundtrip(
AeadAlgorithm::XChaCha20Poly1305,
&[0xD1; 32],
&[0xD2; 24],
&[],
b"xchacha20 aad only",
);
}
/// Regression: both AAD and plaintext empty — exercises the NULL-pointer
/// code paths simultaneously.
#[test]
fn xchacha20_roundtrip_empty_both() {
roundtrip(
AeadAlgorithm::XChaCha20Poly1305,
&[0xE1; 32],
&[0xE2; 24],
&[],
&[],
);
}
#[test]
fn xchacha20_tampered_tag_fails() {
let key = [0xF1u8; 32];
let nonce = [0xF2u8; 24];
let pt = b"integrity check xchacha";
let mut ct = encrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, pt, &[]).unwrap();
let last = ct.len() - 1;
ct[last] ^= 0xFF;
assert!(decrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct, &[]).is_err());
}
#[test]
fn xchacha20_wrong_aad_fails() {
let key = [0xF3u8; 32];
let nonce = [0xF4u8; 24];
let pt = b"aad mismatch test";
let ct = encrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, pt, b"correct").unwrap();
assert!(decrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct, b"wrong").is_err());
}
// ── Key / nonce validation ──────────────────────────────────────────
#[test]
fn aes_gcm_wrong_key_len_rejected() {
assert!(encrypt(AeadAlgorithm::AesGcm256, &[0; 16], &[0; 12], b"x", &[]).is_err());
}
#[test]
fn aes_gcm_wrong_nonce_len_rejected() {
assert!(encrypt(AeadAlgorithm::AesGcm256, &[0; 32], &[0; 8], b"x", &[]).is_err());
}
#[test]
fn chacha20_wrong_key_len_rejected() {
assert!(encrypt(AeadAlgorithm::ChaCha20Poly1305, &[0; 16], &[0; 12], b"x", &[]).is_err());
}
#[test]
fn chacha20_wrong_nonce_len_rejected() {
assert!(encrypt(AeadAlgorithm::ChaCha20Poly1305, &[0; 32], &[0; 8], b"x", &[]).is_err());
}
#[test]
fn xchacha20_wrong_key_len_rejected() {
assert!(encrypt(AeadAlgorithm::XChaCha20Poly1305, &[0; 16], &[0; 24], b"x", &[]).is_err());
}
#[test]
fn xchacha20_wrong_nonce_len_rejected() {
assert!(encrypt(AeadAlgorithm::XChaCha20Poly1305, &[0; 32], &[0; 12], b"x", &[]).is_err());
}
// ── Large payload ───────────────────────────────────────────────────
#[test]
fn xchacha20_large_payload_roundtrip() {
let key = [0xAAu8; 32];
let nonce = [0xBBu8; 24];
let pt = vec![0x42u8; 64 * 1024]; // 64 KiB — exercises the 16k chunked loop
roundtrip(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &pt, b"large");
}
// ── Cross-algorithm isolation ───────────────────────────────────────
#[test]
fn ciphertext_not_interchangeable_between_algorithms() {
let key = [0xDD; 32];
let nonce_12 = [0xEE; 12];
let nonce_24 = [0xEE; 24];
let pt = b"cross-algo test";
let ct_aes = encrypt(AeadAlgorithm::AesGcm256, &key, &nonce_12, pt, &[]).unwrap();
let ct_chacha = encrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce_12, pt, &[]).unwrap();
let ct_xchacha = encrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce_24, pt, &[]).unwrap();
// Each ciphertext must fail to decrypt under a different algorithm
assert!(decrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce_12, &ct_aes, &[]).is_err());
assert!(decrypt(AeadAlgorithm::AesGcm256, &key, &nonce_12, &ct_chacha, &[]).is_err());
assert!(decrypt(AeadAlgorithm::AesGcm256, &key, &nonce_24, &ct_xchacha, &[]).is_err());
}
}