diff --git a/rustls-wolfcrypt-provider/Cargo.toml b/rustls-wolfcrypt-provider/Cargo.toml index 9c2063d..f580240 100644 --- a/rustls-wolfcrypt-provider/Cargo.toml +++ b/rustls-wolfcrypt-provider/Cargo.toml @@ -22,7 +22,10 @@ env_logger = { version = "0.11.5", default-features = false } wolfcrypt-rs = { path = "../wolfcrypt-rs" } rustls-pemfile = { version = "2.2.0", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"]} -wycheproof = "0.6.0" +wycheproof = { version = "0.6.0", default-features = false, features = [ + "aead", + "hkdf", +] } rayon = "1.10.0" anyhow = "1.0.95" num_cpus = "1.16.0" diff --git a/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs b/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs index e72604a..1220923 100644 --- a/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs +++ b/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs @@ -401,6 +401,7 @@ impl MessageDecrypter for WCTls13Cipher { #[cfg(test)] mod tests { use super::*; + use wycheproof::{aead::TestFlag, TestResult}; #[test] fn test_aesgcm128() { @@ -486,4 +487,139 @@ mod tests { assert_eq!(result_decrypted, plain); } } + + #[test] + fn test_aesgcm128_wycheproof() { + let test_name = wycheproof::aead::TestName::AesGcm; + let test_set = wycheproof::aead::TestSet::load(test_name).unwrap(); + let mut counter = 0; + + for group in test_set + .test_groups + .into_iter() + .filter(|group| group.key_size == 128) + .filter(|group| group.nonce_size == 96) + { + for test in group.tests { + counter += 1; + + let mut aes_c_type: Aes = unsafe { mem::zeroed() }; + let aes_object = unsafe { AesObject::from_ptr(&mut aes_c_type) }; + + unsafe { + let ret = wc_AesInit(aes_object.as_ptr(), ptr::null_mut(), INVALID_DEVID); + check_if_zero(ret).unwrap(); + + let ret = wc_AesGcmSetKey( + aes_object.as_ptr(), + test.key.as_ptr(), + test.key.len() as word32, + ); + check_if_zero(ret).unwrap(); + } + + let mut actual_ciphertext = test.pt.to_vec(); + let mut actual_tag = [0u8; GCM_TAG_LENGTH]; + + let encrypt_result = unsafe { + wc_AesGcmEncrypt( + aes_object.as_ptr(), + actual_ciphertext.as_mut_ptr(), + test.pt.as_ptr(), + test.pt.len() as word32, + test.nonce.as_ptr(), + test.nonce.len() as word32, + actual_tag.as_mut_ptr(), + actual_tag.len() as word32, + test.aad.as_ptr(), + test.aad.len() as word32, + ) + }; + + match &test.result { + TestResult::Invalid => { + if test.flags.iter().any(|flag| *flag == TestFlag::ModifiedTag) { + assert_ne!( + actual_tag[..], + test.tag[..], + "Expected incorrect tag. Id {}: {}", + test.tc_id, + test.comment + ); + } + } + TestResult::Valid | TestResult::Acceptable => { + assert_eq!( + encrypt_result, 0, + "Encryption failed for test case {}: {}", + test.tc_id, test.comment + ); + + assert_eq!( + actual_ciphertext[..], + test.ct[..], + "Encryption failed for test case {}: {}", + test.tc_id, + test.comment + ); + + assert_eq!( + actual_tag[..], + test.tag[..], + "Tag mismatch in test case {}: {}", + test.tc_id, + test.comment + ); + } + } + + let mut decrypted_data = test.ct.to_vec(); + let decrypt_result = unsafe { + wc_AesGcmDecrypt( + aes_object.as_ptr(), + decrypted_data.as_mut_ptr(), + test.ct.as_ptr(), + test.ct.len() as word32, + test.nonce.as_ptr(), + test.nonce.len() as word32, + test.tag.as_ptr(), + test.tag.len() as word32, + test.aad.as_ptr(), + test.aad.len() as word32, + ) + }; + + match &test.result { + TestResult::Invalid => { + assert!( + decrypt_result != 0, + "Decryption should have failed for invalid test case {}: {}", + test.tc_id, + test.comment + ); + } + TestResult::Valid | TestResult::Acceptable => { + assert_eq!( + decrypt_result, 0, + "Decryption failed for test case {}: {}", + test.tc_id, test.comment + ); + assert_eq!( + decrypted_data[..], + test.pt[..], + "Decryption failed for test case {}: {}", + test.tc_id, + test.comment + ); + } + } + } + } + + assert!( + counter > 50, + "Insufficient number of tests run: {}", + counter + ); + } } diff --git a/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs b/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs index a9c9113..2867ef0 100644 --- a/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs +++ b/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs @@ -401,6 +401,7 @@ impl MessageDecrypter for WCTls13Cipher { #[cfg(test)] mod tests { use super::*; + use wycheproof::{aead::TestFlag, TestResult}; #[test] fn test_aesgcm256() { @@ -491,4 +492,139 @@ mod tests { assert_eq!(result_decrypted, plain); } } + + #[test] + fn test_aesgcm256_wycheproof() { + let test_name = wycheproof::aead::TestName::AesGcm; + let test_set = wycheproof::aead::TestSet::load(test_name).unwrap(); + let mut counter = 0; + + for group in test_set + .test_groups + .into_iter() + .filter(|group| group.key_size == 256) + .filter(|group| group.nonce_size == 96) + { + for test in group.tests { + counter += 1; + + let mut aes_c_type: Aes = unsafe { mem::zeroed() }; + let aes_object = unsafe { AesObject::from_ptr(&mut aes_c_type) }; + + unsafe { + let ret = wc_AesInit(aes_object.as_ptr(), ptr::null_mut(), INVALID_DEVID); + check_if_zero(ret).unwrap(); + + let ret = wc_AesGcmSetKey( + aes_object.as_ptr(), + test.key.as_ptr(), + test.key.len() as word32, + ); + check_if_zero(ret).unwrap(); + } + + let mut actual_ciphertext = test.pt.to_vec(); + let mut actual_tag = [0u8; GCM_TAG_LENGTH]; + + let encrypt_result = unsafe { + wc_AesGcmEncrypt( + aes_object.as_ptr(), + actual_ciphertext.as_mut_ptr(), + test.pt.as_ptr(), + test.pt.len() as word32, + test.nonce.as_ptr(), + test.nonce.len() as word32, + actual_tag.as_mut_ptr(), + actual_tag.len() as word32, + test.aad.as_ptr(), + test.aad.len() as word32, + ) + }; + + match &test.result { + TestResult::Invalid => { + if test.flags.iter().any(|flag| *flag == TestFlag::ModifiedTag) { + assert_ne!( + actual_tag[..], + test.tag[..], + "Expected incorrect tag. Id {}: {}", + test.tc_id, + test.comment + ); + } + } + TestResult::Valid | TestResult::Acceptable => { + assert_eq!( + encrypt_result, 0, + "Encryption failed for test case {}: {}", + test.tc_id, test.comment + ); + + assert_eq!( + actual_ciphertext[..], + test.ct[..], + "Encryption failed for test case {}: {}", + test.tc_id, + test.comment + ); + + assert_eq!( + actual_tag[..], + test.tag[..], + "Tag mismatch in test case {}: {}", + test.tc_id, + test.comment + ); + } + } + + let mut decrypted_data = test.ct.to_vec(); + let decrypt_result = unsafe { + wc_AesGcmDecrypt( + aes_object.as_ptr(), + decrypted_data.as_mut_ptr(), + test.ct.as_ptr(), + test.ct.len() as word32, + test.nonce.as_ptr(), + test.nonce.len() as word32, + test.tag.as_ptr(), + test.tag.len() as word32, + test.aad.as_ptr(), + test.aad.len() as word32, + ) + }; + + match &test.result { + TestResult::Invalid => { + assert!( + decrypt_result != 0, + "Decryption should have failed for invalid test case {}: {}", + test.tc_id, + test.comment + ); + } + TestResult::Valid | TestResult::Acceptable => { + assert_eq!( + decrypt_result, 0, + "Decryption failed for test case {}: {}", + test.tc_id, test.comment + ); + assert_eq!( + decrypted_data[..], + test.pt[..], + "Decryption failed for test case {}: {}", + test.tc_id, + test.comment + ); + } + } + } + } + + assert!( + counter > 50, + "Insufficient number of tests run: {}", + counter + ); + } } diff --git a/rustls-wolfcrypt-provider/src/aead/chacha20.rs b/rustls-wolfcrypt-provider/src/aead/chacha20.rs index effccea..6aacee7 100644 --- a/rustls-wolfcrypt-provider/src/aead/chacha20.rs +++ b/rustls-wolfcrypt-provider/src/aead/chacha20.rs @@ -313,8 +313,8 @@ impl MessageDecrypter for WCTls13Cipher { #[cfg(test)] mod tests { - use super::*; + use wycheproof::{aead::TestFlag, TestResult}; #[test] fn test_chacha() { @@ -396,4 +396,122 @@ mod tests { assert_eq!(generated_plain_text, plain_text); } + + #[test] + fn test_chacha20poly1305_wycheproof() { + let test_name = wycheproof::aead::TestName::ChaCha20Poly1305; + let test_set = wycheproof::aead::TestSet::load(test_name).unwrap(); + let mut counter = 0; + + for group in test_set + .test_groups + .into_iter() + .filter(|group| group.key_size == 256) + .filter(|group| group.nonce_size == 96) + { + for test in group.tests { + counter += 1; + + let mut actual_ciphertext = test.pt.to_vec(); + let mut actual_tag = [0u8; CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE as usize]; + + let encrypt_result = unsafe { + wc_ChaCha20Poly1305_Encrypt( + test.key.as_ptr(), + test.nonce.as_ptr(), + test.aad.as_ptr(), + test.aad.len() as word32, + test.pt.as_ptr(), + test.pt.len() as word32, + actual_ciphertext.as_mut_ptr(), + actual_tag.as_mut_ptr(), + ) + }; + + match &test.result { + TestResult::Invalid => { + if test.flags.iter().any(|flag| *flag == TestFlag::ModifiedTag) { + assert_ne!( + actual_tag[..], + test.tag[..], + "Expected incorrect tag. Id {}: {}", + test.tc_id, + test.comment + ); + } + } + TestResult::Valid | TestResult::Acceptable => { + assert_eq!( + encrypt_result, 0, + "Encryption failed for test case {}: {}", + test.tc_id, test.comment + ); + + assert_eq!( + actual_ciphertext[..], + test.ct[..], + "Encryption failed for test case {}: {}", + test.tc_id, + test.comment + ); + + assert_eq!( + actual_tag[..], + test.tag[..], + "Tag mismatch in test case {}: {}", + test.tc_id, + test.comment + ); + } + } + + let mut decrypted_data = test.ct.to_vec(); + let decrypt_result = unsafe { + wc_ChaCha20Poly1305_Decrypt( + test.key.as_ptr(), + test.nonce.as_ptr(), + test.aad.as_ptr(), + test.aad.len() as word32, + test.ct.as_ptr(), + test.ct.len() as word32, + test.tag.as_ptr(), + decrypted_data.as_mut_ptr(), + ) + }; + + match &test.result { + TestResult::Invalid => { + assert!( + decrypt_result != 0, + "Decryption should have failed for invalid test case {}: {}", + test.tc_id, + test.comment + ); + } + TestResult::Valid | TestResult::Acceptable => { + assert_eq!( + decrypt_result, 0, + "Decryption failed for test case {}: {}", + test.tc_id, test.comment + ); + assert_eq!( + decrypted_data[..], + test.pt[..], + "Decryption failed for test case {}: {}", + test.tc_id, + test.comment + ); + } + } + } + } + + assert!( + counter > 50, + "Insufficient number of tests run: {}", + counter + ); + + log::info!("Counter: {}", counter); + } } diff --git a/rustls-wolfcrypt-provider/src/hkdf.rs b/rustls-wolfcrypt-provider/src/hkdf.rs index 21b6c77..a1efe96 100644 --- a/rustls-wolfcrypt-provider/src/hkdf.rs +++ b/rustls-wolfcrypt-provider/src/hkdf.rs @@ -150,7 +150,11 @@ impl tls13::HkdfExpander for WolfHkdfExpander { #[cfg(test)] mod tests { use super::*; + use crate::{ + TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256, + }; use hex_literal::hex; + use wycheproof::{hkdf::TestName, TestResult}; /// Tests the HKDF implementation against RFC 5869 test vector A.1 /// This is the primary compliance test using SHA-256 @@ -262,4 +266,87 @@ mod tests { // Results should be identical assert_eq!(okm1, okm2); } + + #[test] + fn test_hkdf_wycheproof_sha256() { + let suites: &[rustls::SupportedCipherSuite] = + &[TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256]; + + let test_name: TestName = TestName::HkdfSha256; + + let test_set = wycheproof::hkdf::TestSet::load(test_name).unwrap(); + + let test_groups = &test_set.test_groups; + + for suite in suites { + let hkdf_provider = suite.tls13().unwrap().hkdf_provider; + + for test_group in test_groups { + let tests = &test_group.tests; + for test in tests { + let pseudorandom_key_expander = + hkdf_provider.extract_from_secret(Some(&test.salt), &test.ikm); + let mut outputkey_material = vec![0; test.size]; + let result = pseudorandom_key_expander + .expand_slice(&[&test.info], &mut outputkey_material); + + match &test.result { + TestResult::Acceptable | TestResult::Valid => { + assert!(result.is_ok()); + assert_eq!( + outputkey_material[..], + test.okm[..], + "Failed test: {}", + test.comment + ); + } + TestResult::Invalid => { + assert!(result.is_err(), "Failed test: {}", test.comment) + } + } + } + } + } + } + + #[test] + fn test_hkdf_wycheproof_sha384() { + let suites: &[rustls::SupportedCipherSuite] = &[TLS13_AES_256_GCM_SHA384]; + + let test_name: TestName = TestName::HkdfSha384; + + let test_set = wycheproof::hkdf::TestSet::load(test_name).unwrap(); + + let test_groups = &test_set.test_groups; + + for suite in suites { + let hkdf_provider = suite.tls13().unwrap().hkdf_provider; + + for test_group in test_groups { + let tests = &test_group.tests; + for test in tests { + let pseudorandom_key_expander = + hkdf_provider.extract_from_secret(Some(&test.salt), &test.ikm); + let mut outputkey_material = vec![0; test.size]; + let result = pseudorandom_key_expander + .expand_slice(&[&test.info], &mut outputkey_material); + + match &test.result { + TestResult::Acceptable | TestResult::Valid => { + assert!(result.is_ok()); + assert_eq!( + outputkey_material[..], + test.okm[..], + "Failed test: {}", + test.comment + ); + } + TestResult::Invalid => { + assert!(result.is_err(), "Failed test: {}", test.comment) + } + } + } + } + } + } }