MOD: formatting

This commit is contained in:
JohnE 2026-03-11 16:12:12 -07:00
parent 971f1aeaaa
commit 68836cfff4
13 changed files with 963 additions and 537 deletions

View File

@ -42,30 +42,30 @@ impl AeadAlgorithm {
/// Human-readable name, suitable for logs and diagnostics.
pub fn name(self) -> &'static str {
match self {
Self::AesGcm256 => "AES-256-GCM",
Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
Self::AesGcm256 => "AES-256-GCM",
Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
Self::XChaCha20Poly1305 => "XChaCha20-Poly1305",
Self::Ascon128a => "Ascon-AEAD-128a",
Self::Ascon128a => "Ascon-AEAD-128a",
}
}
/// Expected nonce length in bytes for this algorithm.
pub fn nonce_len(self) -> usize {
match self {
Self::AesGcm256 => 12,
Self::ChaCha20Poly1305 => 12,
Self::AesGcm256 => 12,
Self::ChaCha20Poly1305 => 12,
Self::XChaCha20Poly1305 => 24,
Self::Ascon128a => 16,
Self::Ascon128a => 16,
}
}
/// Expected key length in bytes for this algorithm.
pub fn key_len(self) -> usize {
match self {
Self::AesGcm256 => 32,
Self::ChaCha20Poly1305 => 32,
Self::AesGcm256 => 32,
Self::ChaCha20Poly1305 => 32,
Self::XChaCha20Poly1305 => 32,
Self::Ascon128a => 16,
Self::Ascon128a => 16,
}
}
}
@ -112,12 +112,12 @@ impl KdfAlgorithm {
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::Sha256 => "HKDF-SHA-256",
Self::Sha384 => "HKDF-SHA-384",
Self::Sha512 => "HKDF-SHA-512",
Self::Sha256 => "HKDF-SHA-256",
Self::Sha384 => "HKDF-SHA-384",
Self::Sha512 => "HKDF-SHA-512",
Self::Blake2b512 => "HKDF-BLAKE2b-512",
Self::Argon2id => "Argon2id",
Self::Kmac256 => "KMAC256",
Self::Argon2id => "Argon2id",
Self::Kmac256 => "KMAC256",
}
}
}
@ -223,24 +223,24 @@ impl HashAlgorithm {
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::Sha256 => "SHA-256",
Self::Sha384 => "SHA-384",
Self::Sha512 => "SHA-512",
Self::Sha256 => "SHA-256",
Self::Sha384 => "SHA-384",
Self::Sha512 => "SHA-512",
Self::Blake2b512 => "BLAKE2b-512",
Self::Sha3_256 => "SHA3-256",
Self::Sha3_512 => "SHA3-512",
Self::Sha3_256 => "SHA3-256",
Self::Sha3_512 => "SHA3-512",
}
}
/// Digest output length in bytes.
pub fn digest_len(self) -> usize {
match self {
Self::Sha256 => 32,
Self::Sha384 => 48,
Self::Sha512 => 64,
Self::Sha256 => 32,
Self::Sha384 => 48,
Self::Sha512 => 64,
Self::Blake2b512 => 64,
Self::Sha3_256 => 32,
Self::Sha3_512 => 64,
Self::Sha3_256 => 32,
Self::Sha3_512 => 64,
}
}
}
@ -285,10 +285,10 @@ impl KemAlgorithm {
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::X25519 => "X25519",
Self::X448 => "X448",
Self::MlKem768 => "ML-KEM-768",
Self::MlKem1024 => "ML-KEM-1024",
Self::X25519 => "X25519",
Self::X448 => "X448",
Self::MlKem768 => "ML-KEM-768",
Self::MlKem1024 => "ML-KEM-1024",
Self::ClassicMcEliece460896 => "Classic-McEliece-460896",
}
}

View File

@ -110,11 +110,7 @@ pub trait MacProvider {
/// Cryptographic hash operations.
pub trait HashProvider {
/// Compute a digest of `data` using the specified algorithm.
fn hash(
&self,
algo: HashAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
fn hash(&self, algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError>;
}
/// Key encapsulation mechanism operations.
@ -124,10 +120,7 @@ pub trait HashProvider {
/// functions separately and looks up a `KemProvider` from the registry.
pub trait KemProvider {
/// Generate a new KEM key pair.
fn generate_keypair(
&self,
algo: KemAlgorithm,
) -> Result<KemKeyPair, CryptoError>;
fn generate_keypair(&self, algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError>;
/// Encapsulate a shared secret for the holder of `public_key`.
///
@ -170,7 +163,9 @@ pub trait KemProvider {
/// `ProviderRegistry::global().register(name, Box::new(provider))`.
/// 5. Call `ccc_crypto_<name>::init()` from `ccc-flutter-bridge/src/lib.rs`.
/// 6. Add NIST test vectors for every algorithm you expose.
pub trait CryptoProvider: AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync {
pub trait CryptoProvider:
AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync
{
/// The provider's canonical name (e.g. `"wolfssl"`).
///
/// Must be lowercase, no spaces, match the `CccCryptoProvider` enum value

View File

@ -118,12 +118,22 @@ mod tests {
impl AeadProvider for StubProvider {
fn encrypt_aead(
&self, _a: AeadAlgorithm, _k: &[u8], _n: &[u8], pt: &[u8], _aad: &[u8],
&self,
_a: AeadAlgorithm,
_k: &[u8],
_n: &[u8],
pt: &[u8],
_aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(pt.to_vec())
}
fn decrypt_aead(
&self, _a: AeadAlgorithm, _k: &[u8], _n: &[u8], ct: &[u8], _aad: &[u8],
&self,
_a: AeadAlgorithm,
_k: &[u8],
_n: &[u8],
ct: &[u8],
_aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(ct.to_vec())
}
@ -131,7 +141,12 @@ mod tests {
impl KdfProvider for StubProvider {
fn derive_key(
&self, _a: KdfAlgorithm, _ikm: &[u8], _salt: &[u8], _info: &[u8], length: usize,
&self,
_a: KdfAlgorithm,
_ikm: &[u8],
_salt: &[u8],
_info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
Ok(Zeroizing::new(vec![0u8; length]))
}
@ -139,41 +154,56 @@ mod tests {
impl MacProvider for StubProvider {
fn compute_mac(
&self, _a: MacAlgorithm, _k: &[u8], _data: &[u8],
&self,
_a: MacAlgorithm,
_k: &[u8],
_data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(vec![0u8; 32])
}
fn verify_mac(
&self, _a: MacAlgorithm, _k: &[u8], _data: &[u8], _mac: &[u8],
&self,
_a: MacAlgorithm,
_k: &[u8],
_data: &[u8],
_mac: &[u8],
) -> Result<bool, CryptoError> {
Ok(true)
}
}
impl HashProvider for StubProvider {
fn hash(
&self, _a: HashAlgorithm, data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
fn hash(&self, _a: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
Ok(data.to_vec())
}
}
impl CryptoProvider for StubProvider {
fn provider_name(&self) -> &'static str { "stub" }
fn provider_name(&self) -> &'static str {
"stub"
}
fn capabilities(&self) -> ProviderCapabilities {
ProviderCapabilities::empty("stub")
}
fn self_test(&self) -> SelfTestReport {
SelfTestReport::finalise("stub", vec![AlgoTestResult {
algo_id: 12, algo_name: "stub".into(), passed: true, error_message: None,
}])
SelfTestReport::finalise(
"stub",
vec![AlgoTestResult {
algo_id: 12,
algo_name: "stub".into(),
passed: true,
error_message: None,
}],
)
}
fn benchmark(&self) -> BenchmarkReport {
BenchmarkReport {
provider_name: "stub".into(),
results: vec![AlgoBenchResult {
algo_id: 12, algo_name: "stub".into(),
throughput_mbps: 999.0, efficiency_score: 100,
algo_id: 12,
algo_name: "stub".into(),
throughput_mbps: 999.0,
efficiency_score: 100,
}],
}
}
@ -182,7 +212,9 @@ mod tests {
// ── Tests ──────────────────────────────────────────────────────────────────
fn fresh_registry() -> ProviderRegistry {
ProviderRegistry { inner: Mutex::new(HashMap::new()) }
ProviderRegistry {
inner: Mutex::new(HashMap::new()),
}
}
#[test]
@ -216,7 +248,7 @@ mod tests {
fn register_replaces_existing() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
reg.register("stub", Box::new(StubProvider)); // replace
reg.register("stub", Box::new(StubProvider)); // replace
assert_eq!(reg.list().len(), 1);
}
@ -227,7 +259,13 @@ mod tests {
let provider = reg.get("stub").unwrap();
let plaintext = b"hello world";
let ct = provider
.encrypt_aead(AeadAlgorithm::AesGcm256, &[0u8; 32], &[0u8; 12], plaintext, b"")
.encrypt_aead(
AeadAlgorithm::AesGcm256,
&[0u8; 32],
&[0u8; 12],
plaintext,
b"",
)
.unwrap();
let pt = provider
.decrypt_aead(AeadAlgorithm::AesGcm256, &[0u8; 32], &[0u8; 12], &ct, b"")

View File

@ -14,7 +14,10 @@
//! a pre-installed wolfSSL at this path. The path must contain
//! `include/wolfssl/` and `lib/libwolfssl.a`.
use std::{env, path::{Path, PathBuf}};
use std::{
env,
path::{Path, PathBuf},
};
fn main() {
// When the `stub_ffi` feature is enabled (e.g. for type-checking without
@ -34,29 +37,31 @@ fn main() {
// ── 1. Locate wolfSSL ────────────────────────────────────────────────────
let (include_dir, lib_dir) =
if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
// Use a pre-installed wolfSSL — skip the CMake build.
let p = PathBuf::from(&install_dir);
println!("cargo:warning=Using pre-installed wolfSSL at {}", install_dir);
(p.join("include"), p.join("lib"))
} else {
// Build from source (default — uses our git submodule).
let source_dir = env::var("WOLFSSL_SOURCE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_root.join("vendors/wolfssl"));
let (include_dir, lib_dir) = if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
// Use a pre-installed wolfSSL — skip the CMake build.
let p = PathBuf::from(&install_dir);
println!(
"cargo:warning=Using pre-installed wolfSSL at {}",
install_dir
);
(p.join("include"), p.join("lib"))
} else {
// Build from source (default — uses our git submodule).
let source_dir = env::var("WOLFSSL_SOURCE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_root.join("vendors/wolfssl"));
if !source_dir.join("CMakeLists.txt").exists() {
panic!(
"\n\nwolfSSL source not found at {}.\n\
if !source_dir.join("CMakeLists.txt").exists() {
panic!(
"\n\nwolfSSL source not found at {}.\n\
Run `git submodule update --init --recursive` to fetch it,\n\
or set WOLFSSL_SOURCE_DIR to an alternative path.\n",
source_dir.display()
);
}
source_dir.display()
);
}
build_wolfssl_cmake(&source_dir, &out_dir)
};
build_wolfssl_cmake(&source_dir, &out_dir)
};
// ── 2. Tell Cargo how to link ────────────────────────────────────────────
@ -153,15 +158,15 @@ fn build_wolfssl_cmake(source_dir: &Path, _out_dir: &Path) -> (PathBuf, PathBuf)
.cflag("-DWOLFCRYPT_ONLY")
// Key & certificate generation.
// Adds X.509 cert/CSR/key generation capability on top of wolfCrypt.
.define("WOLFSSL_KEYGEN", "yes") // -DWOLFSSL_KEY_GEN
.define("WOLFSSL_CERTGEN", "yes") // -DWOLFSSL_CERT_GEN
.define("WOLFSSL_CERTREQ", "yes") // -DWOLFSSL_CERT_REQ
.define("WOLFSSL_CERTEXT", "yes"); // -DWOLFSSL_CERT_EXT
.define("WOLFSSL_KEYGEN", "yes") // -DWOLFSSL_KEY_GEN
.define("WOLFSSL_CERTGEN", "yes") // -DWOLFSSL_CERT_GEN
.define("WOLFSSL_CERTREQ", "yes") // -DWOLFSSL_CERT_REQ
.define("WOLFSSL_CERTEXT", "yes"); // -DWOLFSSL_CERT_EXT
let install_path = cfg.build();
let include_dir = install_path.join("include");
let lib_dir = install_path.join("lib");
let lib_dir = install_path.join("lib");
(include_dir, lib_dir)
}
@ -302,7 +307,7 @@ fn generate_bindings(include_dir: &Path, out_dir: &Path) {
.allowlist_type("WC_RNG")
.allowlist_type("OS_Seed")
.allowlist_type("wc_Sha256")
.allowlist_type("wc_Sha512") // also used as wc_Sha384 (typedef)
.allowlist_type("wc_Sha512") // also used as wc_Sha384 (typedef)
.allowlist_type("wc_Sha3")
.allowlist_type("Blake2b")
.allowlist_type("curve25519_key")

View File

@ -26,11 +26,10 @@ pub fn encrypt(
) -> Result<Vec<u8>, CryptoError> {
validate_key_nonce(algo, key, nonce)?;
match algo {
AeadAlgorithm::AesGcm256 => aes_gcm_256_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::AesGcm256 => aes_gcm_256_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::Ascon128a =>
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
AeadAlgorithm::Ascon128a => Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
}
}
@ -47,11 +46,14 @@ pub fn decrypt(
) -> Result<Vec<u8>, CryptoError> {
validate_key_nonce(algo, key, nonce)?;
match algo {
AeadAlgorithm::AesGcm256 => aes_gcm_256_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::Ascon128a =>
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
AeadAlgorithm::AesGcm256 => aes_gcm_256_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::ChaCha20Poly1305 => {
chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad)
}
AeadAlgorithm::XChaCha20Poly1305 => {
xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad)
}
AeadAlgorithm::Ascon128a => Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
}
}
@ -60,18 +62,22 @@ pub fn decrypt(
// ──────────────────────────────────────────────────────────────────────────────
fn validate_key_nonce(algo: AeadAlgorithm, key: &[u8], nonce: &[u8]) -> Result<(), CryptoError> {
let expected_key = algo.key_len();
let expected_key = algo.key_len();
let expected_nonce = algo.nonce_len();
if key.len() != expected_key {
return Err(CryptoError::InvalidKey(format!(
"{}: expected {}-byte key, got {}",
algo.name(), expected_key, key.len()
algo.name(),
expected_key,
key.len()
)));
}
if nonce.len() != expected_nonce {
return Err(CryptoError::InvalidNonce(format!(
"{}: expected {}-byte nonce, got {}",
algo.name(), expected_nonce, nonce.len()
algo.name(),
expected_nonce,
nonce.len()
)));
}
Ok(())
@ -85,7 +91,7 @@ const TAG_LEN: usize = 16;
fn aes_gcm_256_encrypt(
key: &[u8],
nonce: &[u8], // 12 bytes
nonce: &[u8], // 12 bytes
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
@ -98,11 +104,8 @@ fn aes_gcm_256_encrypt(
use std::alloc::{alloc_zeroed, dealloc, Layout};
// sizeof(Aes) = 288 bytes; alignment = 16.
let layout = Layout::from_size_align(
std::mem::size_of::<crate::sys::Aes>().max(288),
16,
)
.expect("layout must be valid");
let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Aes>().max(288), 16)
.expect("layout must be valid");
let aes_ptr = alloc_zeroed(layout) as *mut crate::sys::Aes;
if aes_ptr.is_null() {
@ -116,18 +119,20 @@ fn aes_gcm_256_encrypt(
);
if ret != 0 {
dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_AesInit returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_AesInit returned {}",
ret
)));
}
let ret = crate::sys::wc_AesGcmSetKey(
aes_ptr,
key.as_ptr(),
key.len() as u32,
);
let ret = crate::sys::wc_AesGcmSetKey(aes_ptr, key.as_ptr(), key.len() as u32);
if ret != 0 {
crate::sys::wc_AesFree(aes_ptr);
dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret)));
return Err(CryptoError::InvalidKey(format!(
"wc_AesGcmSetKey returned {}",
ret
)));
}
let ret = crate::sys::wc_AesGcmEncrypt(
@ -139,13 +144,20 @@ fn aes_gcm_256_encrypt(
nonce.len() as u32,
tag_buf.as_mut_ptr(),
TAG_LEN as u32,
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32,
);
crate::sys::wc_AesFree(aes_ptr);
dealloc(aes_ptr as *mut u8, layout);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_AesGcmEncrypt returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_AesGcmEncrypt returned {}",
ret
)));
}
}
@ -160,7 +172,7 @@ fn aes_gcm_256_decrypt(
) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput(
"AES-256-GCM: ciphertext too short (no room for tag)".into()
"AES-256-GCM: ciphertext too short (no room for tag)".into(),
));
}
let ct_len = ciphertext_and_tag.len() - TAG_LEN;
@ -172,11 +184,8 @@ fn aes_gcm_256_decrypt(
// alignment so the C code's SIMD operations work correctly on ARM64.
use std::alloc::{alloc_zeroed, dealloc, Layout};
let layout = Layout::from_size_align(
std::mem::size_of::<crate::sys::Aes>().max(288),
16,
)
.expect("layout must be valid");
let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Aes>().max(288), 16)
.expect("layout must be valid");
let aes_ptr = alloc_zeroed(layout) as *mut crate::sys::Aes;
if aes_ptr.is_null() {
@ -190,18 +199,20 @@ fn aes_gcm_256_decrypt(
);
if ret != 0 {
dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_AesInit returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_AesInit returned {}",
ret
)));
}
let ret = crate::sys::wc_AesGcmSetKey(
aes_ptr,
key.as_ptr(),
key.len() as u32,
);
let ret = crate::sys::wc_AesGcmSetKey(aes_ptr, key.as_ptr(), key.len() as u32);
if ret != 0 {
crate::sys::wc_AesFree(aes_ptr);
dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret)));
return Err(CryptoError::InvalidKey(format!(
"wc_AesGcmSetKey returned {}",
ret
)));
}
let ret = crate::sys::wc_AesGcmDecrypt(
@ -213,7 +224,11 @@ fn aes_gcm_256_decrypt(
nonce.len() as u32,
tag.as_ptr(),
TAG_LEN as u32,
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32,
);
crate::sys::wc_AesFree(aes_ptr);
@ -233,7 +248,7 @@ fn aes_gcm_256_decrypt(
fn chacha20_poly1305_encrypt(
key: &[u8],
nonce: &[u8], // 12 bytes
nonce: &[u8], // 12 bytes
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
@ -245,7 +260,11 @@ fn chacha20_poly1305_encrypt(
let ret = crate::sys::wc_ChaCha20Poly1305_Encrypt(
key.as_ptr(),
nonce.as_ptr(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32,
plaintext.as_ptr(),
plaintext.len() as u32,
@ -253,9 +272,10 @@ fn chacha20_poly1305_encrypt(
tag_buf.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_ChaCha20Poly1305_Encrypt returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_ChaCha20Poly1305_Encrypt returned {}",
ret
)));
}
}
@ -270,7 +290,7 @@ fn chacha20_poly1305_decrypt(
) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput(
"ChaCha20-Poly1305: ciphertext too short".into()
"ChaCha20-Poly1305: ciphertext too short".into(),
));
}
let ct_len = ciphertext_and_tag.len() - TAG_LEN;
@ -282,7 +302,11 @@ fn chacha20_poly1305_decrypt(
let ret = crate::sys::wc_ChaCha20Poly1305_Decrypt(
key.as_ptr(),
nonce.as_ptr(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32,
ciphertext.as_ptr(),
ct_len as u32,
@ -310,7 +334,7 @@ fn chacha20_poly1305_decrypt(
fn xchacha20_poly1305_encrypt(
key: &[u8],
nonce: &[u8], // 24 bytes
nonce: &[u8], // 24 bytes
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
@ -325,7 +349,11 @@ fn xchacha20_poly1305_encrypt(
dst.len(),
plaintext.as_ptr(),
plaintext.len(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len(),
nonce.as_ptr(),
nonce.len(),
@ -333,9 +361,10 @@ fn xchacha20_poly1305_encrypt(
key.len(),
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_XChaCha20Poly1305_Encrypt returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_XChaCha20Poly1305_Encrypt returned {}",
ret
)));
}
}
@ -344,13 +373,13 @@ fn xchacha20_poly1305_encrypt(
fn xchacha20_poly1305_decrypt(
key: &[u8],
nonce: &[u8], // 24 bytes
nonce: &[u8], // 24 bytes
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput(
"XChaCha20-Poly1305: ciphertext too short".into()
"XChaCha20-Poly1305: ciphertext too short".into(),
));
}
let pt_len = ciphertext_and_tag.len() - TAG_LEN;
@ -364,7 +393,11 @@ fn xchacha20_poly1305_decrypt(
dst.len(),
ciphertext_and_tag.as_ptr(),
ciphertext_and_tag.len(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len(),
nonce.as_ptr(),
nonce.len(),

View File

@ -43,12 +43,15 @@ pub fn probe_capabilities(benchmarks: Option<&BenchmarkReport>) -> ProviderCapab
.map(|r| r.efficiency_score)
.unwrap_or(0);
caps = caps.with_aead(algo, AlgorithmCapability {
available,
deterministic_io: true, // wolfSSL output is deterministic for same key/nonce
efficiency_score: efficiency,
reliability_score: if available { 100 } else { 0 },
});
caps = caps.with_aead(
algo,
AlgorithmCapability {
available,
deterministic_io: true, // wolfSSL output is deterministic for same key/nonce
efficiency_score: efficiency,
reliability_score: if available { 100 } else { 0 },
},
);
}
// Ascon-128a is not in this wolfSSL build.
@ -57,72 +60,105 @@ pub fn probe_capabilities(benchmarks: Option<&BenchmarkReport>) -> ProviderCapab
// ── KDF ──────────────────────────────────────────────────────────────────
for algo in [KdfAlgorithm::Sha256, KdfAlgorithm::Sha384, KdfAlgorithm::Sha512] {
let available = crate::kdf::derive_key(algo, b"key", b"salt", b"info", 32).is_ok();
caps = caps.with_kdf(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 80, // HKDF is fast; benchmark not run for KDF
reliability_score: if available { 100 } else { 0 },
});
caps = caps.with_kdf(
algo,
AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 80, // HKDF is fast; benchmark not run for KDF
reliability_score: if available { 100 } else { 0 },
},
);
}
for algo in [KdfAlgorithm::Blake2b512, KdfAlgorithm::Argon2id] {
let salt: &[u8] = if algo == KdfAlgorithm::Argon2id { b"saltsalt" } else { b"" };
let salt: &[u8] = if algo == KdfAlgorithm::Argon2id {
b"saltsalt"
} else {
b""
};
let available = crate::kdf::derive_key(algo, b"key", salt, b"", 32).is_ok();
caps = caps.with_kdf(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: if algo == KdfAlgorithm::Argon2id { 10 } else { 70 },
reliability_score: if available { 100 } else { 0 },
});
caps = caps.with_kdf(
algo,
AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: if algo == KdfAlgorithm::Argon2id {
10
} else {
70
},
reliability_score: if available { 100 } else { 0 },
},
);
}
caps = caps.with_kdf(KdfAlgorithm::Kmac256, AlgorithmCapability::unavailable());
// ── MAC ──────────────────────────────────────────────────────────────────
for algo in [MacAlgorithm::HmacSha256, MacAlgorithm::HmacSha384, MacAlgorithm::HmacSha512] {
let available = crate::mac::compute_mac(algo, b"key", b"data").is_ok();
caps = caps.with_mac(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 85,
reliability_score: if available { 100 } else { 0 },
});
caps = caps.with_mac(
algo,
AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 85,
reliability_score: if available { 100 } else { 0 },
},
);
}
{
let available = crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data").is_ok();
caps = caps.with_mac(MacAlgorithm::Blake2bMac, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 90,
reliability_score: if available { 100 } else { 0 },
});
let available =
crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data")
.is_ok();
caps = caps.with_mac(
MacAlgorithm::Blake2bMac,
AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 90,
reliability_score: if available { 100 } else { 0 },
},
);
}
caps = caps.with_mac(MacAlgorithm::Poly1305, AlgorithmCapability::unavailable());
// ── Hash ─────────────────────────────────────────────────────────────────
for algo in [
HashAlgorithm::Sha256, HashAlgorithm::Sha384, HashAlgorithm::Sha512,
HashAlgorithm::Blake2b512, HashAlgorithm::Sha3_256, HashAlgorithm::Sha3_512,
HashAlgorithm::Sha256,
HashAlgorithm::Sha384,
HashAlgorithm::Sha512,
HashAlgorithm::Blake2b512,
HashAlgorithm::Sha3_256,
HashAlgorithm::Sha3_512,
] {
let available = crate::hash::hash(algo, b"probe").is_ok();
caps = caps.with_hash(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 90,
reliability_score: if available { 100 } else { 0 },
});
caps = caps.with_hash(
algo,
AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 90,
reliability_score: if available { 100 } else { 0 },
},
);
}
// ── KEM ──────────────────────────────────────────────────────────────────
for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] {
let available = crate::kem::generate_keypair(algo).is_ok();
caps = caps.with_kem(algo, AlgorithmCapability {
available,
deterministic_io: false, // key generation is randomised
efficiency_score: 80,
reliability_score: if available { 100 } else { 0 },
});
caps = caps.with_kem(
algo,
AlgorithmCapability {
available,
deterministic_io: false, // key generation is randomised
efficiency_score: 80,
reliability_score: if available { 100 } else { 0 },
},
);
}
for algo in [
KemAlgorithm::MlKem768, KemAlgorithm::MlKem1024,
KemAlgorithm::MlKem768,
KemAlgorithm::MlKem1024,
KemAlgorithm::ClassicMcEliece460896,
] {
caps = caps.with_kem(algo, AlgorithmCapability::unavailable());
@ -153,7 +189,7 @@ pub fn run_benchmark() -> BenchmarkReport {
let mut raw_results: Vec<(AeadAlgorithm, f64)> = Vec::new();
for algo in algos {
let key = vec![0u8; algo.key_len()];
let key = vec![0u8; algo.key_len()];
let nonce = vec![0u8; algo.nonce_len()];
// Probe first; skip unavailable algorithms.

View File

@ -14,12 +14,12 @@ use ccc_crypto_core::{algorithms::HashAlgorithm, error::CryptoError};
/// [`HashAlgorithm::digest_len`] for output lengths).
pub fn hash(algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
match algo {
HashAlgorithm::Sha256 => sha256(data),
HashAlgorithm::Sha384 => sha384(data),
HashAlgorithm::Sha512 => sha512(data),
HashAlgorithm::Sha256 => sha256(data),
HashAlgorithm::Sha384 => sha384(data),
HashAlgorithm::Sha512 => sha512(data),
HashAlgorithm::Blake2b512 => blake2b_512(data),
HashAlgorithm::Sha3_256 => sha3_256(data),
HashAlgorithm::Sha3_512 => sha3_512(data),
HashAlgorithm::Sha3_256 => sha3_256(data),
HashAlgorithm::Sha3_512 => sha3_512(data),
}
}
@ -42,11 +42,9 @@ fn sha256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
use std::alloc::{alloc_zeroed, dealloc, Layout};
// Size reported by C's sizeof(wc_Sha256) = 120 bytes; alignment = 16.
let layout = Layout::from_size_align(
std::mem::size_of::<crate::sys::wc_Sha256>().max(120),
16,
)
.expect("layout must be valid");
let layout =
Layout::from_size_align(std::mem::size_of::<crate::sys::wc_Sha256>().max(120), 16)
.expect("layout must be valid");
let sha_ptr = alloc_zeroed(layout) as *mut crate::sys::wc_Sha256;
if sha_ptr.is_null() {
@ -56,23 +54,33 @@ fn sha256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let ret = crate::sys::wc_InitSha256(sha_ptr);
if ret != 0 {
dealloc(sha_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_InitSha256 returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_InitSha256 returned {ret}"
)));
}
let ret = crate::sys::wc_Sha256Update(
sha_ptr,
if data.is_empty() { std::ptr::null() } else { data.as_ptr() },
if data.is_empty() {
std::ptr::null()
} else {
data.as_ptr()
},
data.len() as u32,
);
if ret != 0 {
crate::sys::wc_Sha256Free(sha_ptr);
dealloc(sha_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_Sha256Update returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha256Update returned {ret}"
)));
}
let ret = crate::sys::wc_Sha256Final(sha_ptr, out.as_mut_ptr());
crate::sys::wc_Sha256Free(sha_ptr);
dealloc(sha_ptr as *mut u8, layout);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha256Final returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha256Final returned {ret}"
)));
}
}
Ok(out)
@ -85,17 +93,23 @@ fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha384(&mut sha);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha384 returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_InitSha384 returned {ret}"
)));
}
let ret = crate::sys::wc_Sha384Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 {
crate::sys::wc_Sha384Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha384Update returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha384Update returned {ret}"
)));
}
let ret = crate::sys::wc_Sha384Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha384Free(&mut sha);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha384Final returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha384Final returned {ret}"
)));
}
}
Ok(out)
@ -107,17 +121,23 @@ fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha512(&mut sha);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha512 returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_InitSha512 returned {ret}"
)));
}
let ret = crate::sys::wc_Sha512Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 {
crate::sys::wc_Sha512Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha512Update returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha512Update returned {ret}"
)));
}
let ret = crate::sys::wc_Sha512Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha512Free(&mut sha);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha512Final returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha512Final returned {ret}"
)));
}
}
Ok(out)
@ -135,17 +155,23 @@ fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha3_256(&mut sha, std::ptr::null_mut(), -2);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha3_256 returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_InitSha3_256 returned {ret}"
)));
}
let ret = crate::sys::wc_Sha3_256_Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 {
crate::sys::wc_Sha3_256_Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha3_256_Update returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha3_256_Update returned {ret}"
)));
}
let ret = crate::sys::wc_Sha3_256_Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha3_256_Free(&mut sha);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha3_256_Final returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha3_256_Final returned {ret}"
)));
}
}
Ok(out)
@ -157,17 +183,23 @@ fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha3_512(&mut sha, std::ptr::null_mut(), -2);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha3_512 returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_InitSha3_512 returned {ret}"
)));
}
let ret = crate::sys::wc_Sha3_512_Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 {
crate::sys::wc_Sha3_512_Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha3_512_Update returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha3_512_Update returned {ret}"
)));
}
let ret = crate::sys::wc_Sha3_512_Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha3_512_Free(&mut sha);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha3_512_Final returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Sha3_512_Final returned {ret}"
)));
}
}
Ok(out)
@ -187,16 +219,22 @@ pub fn blake2b_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut b2b: crate::sys::Blake2b = std::mem::zeroed();
let ret = crate::sys::wc_InitBlake2b(&mut b2b, OUT_LEN);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitBlake2b returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_InitBlake2b returned {ret}"
)));
}
let ret = crate::sys::wc_Blake2bUpdate(&mut b2b, data.as_ptr(), data.len() as u32);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Blake2bUpdate returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Blake2bUpdate returned {ret}"
)));
}
// requestSz = OUT_LEN → produce the full 64-byte digest.
let ret = crate::sys::wc_Blake2bFinal(&mut b2b, out.as_mut_ptr(), OUT_LEN);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Blake2bFinal returned {ret}")));
return Err(CryptoError::InternalError(format!(
"wc_Blake2bFinal returned {ret}"
)));
}
}
Ok(out)

View File

@ -54,8 +54,7 @@ pub fn derive_key(
KdfAlgorithm::Sha512 => hkdf(WC_HASH_TYPE_SHA512, ikm, salt, info, length),
KdfAlgorithm::Blake2b512 => blake2b_kdf(ikm, salt, info, length),
KdfAlgorithm::Argon2id => argon2id(ikm, salt, length),
KdfAlgorithm::Kmac256 =>
Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
KdfAlgorithm::Kmac256 => Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
}
}
@ -103,16 +102,20 @@ fn hkdf_extract(
unsafe {
let ret = crate::sys::wc_Tls13_HKDF_Extract(
prk.as_mut_ptr(),
if salt.is_empty() { std::ptr::null() } else { salt.as_ptr() },
if salt.is_empty() {
std::ptr::null()
} else {
salt.as_ptr()
},
salt.len() as u32,
ikm_buf.as_mut_ptr(),
ikm_buf.len() as u32,
hash_type,
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_Tls13_HKDF_Extract returned {ret}")
));
return Err(CryptoError::InternalError(format!(
"wc_Tls13_HKDF_Extract returned {ret}"
)));
}
}
Ok(prk)
@ -133,14 +136,14 @@ fn hkdf_expand(
// Maximum HKDF output is 255 * HashLen (RFC 5869 §2.3).
let max_len = 255 * hash_len;
if length > max_len {
return Err(CryptoError::InvalidInput(
format!("HKDF: requested length {length} exceeds maximum {max_len}")
));
return Err(CryptoError::InvalidInput(format!(
"HKDF: requested length {length} exceeds maximum {max_len}"
)));
}
let mut okm = Zeroizing::new(vec![0u8; length]);
let mut t = Vec::new(); // T(i-1); starts empty per spec
let mut pos = 0usize;
let mut okm = Zeroizing::new(vec![0u8; length]);
let mut t = Vec::new(); // T(i-1); starts empty per spec
let mut pos = 0usize;
let mut counter = 1u8;
while pos < length {
@ -156,9 +159,9 @@ fn hkdf_expand(
let copy_len = (length - pos).min(hash_len);
okm[pos..pos + copy_len].copy_from_slice(&t[..copy_len]);
pos += copy_len;
counter = counter.checked_add(1).ok_or_else(|| {
CryptoError::InternalError("HKDF counter overflow".into())
})?;
counter = counter
.checked_add(1)
.ok_or_else(|| CryptoError::InternalError("HKDF counter overflow".into()))?;
}
Ok(okm)
@ -194,7 +197,7 @@ fn blake2b_kdf(
// callers should use HKDF-SHA512 instead.
if length > digest.len() {
return Err(CryptoError::InvalidInput(
"BLAKE2b-512 KDF: requested length exceeds 64 bytes; use HKDF-SHA512".into()
"BLAKE2b-512 KDF: requested length exceeds 64 bytes; use HKDF-SHA512".into(),
));
}
@ -221,7 +224,7 @@ fn argon2id(
// Argon2 requires at least 8 bytes of salt.
if salt.len() < 8 {
return Err(CryptoError::InvalidInput(
"Argon2id requires at least 8-byte salt".into()
"Argon2id requires at least 8-byte salt".into(),
));
}
@ -232,10 +235,10 @@ fn argon2id(
// argon2_iterations: 3
// argon2_parallelism: 4
let params = Params::new(
65_536, // m_cost: 64 MiB
3, // t_cost: iterations
4, // p_cost: parallelism (lanes)
Some(length), // output length
65_536, // m_cost: 64 MiB
3, // t_cost: iterations
4, // p_cost: parallelism (lanes)
Some(length), // output length
)
.map_err(|e| CryptoError::InternalError(format!("Argon2 params error: {e}")))?;

View File

@ -20,15 +20,13 @@ use ccc_crypto_core::{
pub fn generate_keypair(algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError> {
match algo {
KemAlgorithm::X25519 => x25519_generate(),
KemAlgorithm::X448 => x448_generate(),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5 (requires wolfSSL PQ build)".into()
)),
KemAlgorithm::ClassicMcEliece460896 =>
Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into()
)),
KemAlgorithm::X448 => x448_generate(),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5 (requires wolfSSL PQ build)".into(),
)),
KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into(),
)),
}
}
@ -44,15 +42,13 @@ pub fn encapsulate(
) -> Result<KemEncapResult, CryptoError> {
match algo {
KemAlgorithm::X25519 => x25519_encapsulate(recipient_public_key),
KemAlgorithm::X448 => x448_encapsulate(recipient_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5".into()
)),
KemAlgorithm::ClassicMcEliece460896 =>
Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into()
)),
KemAlgorithm::X448 => x448_encapsulate(recipient_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5".into(),
)),
KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into(),
)),
}
}
@ -65,15 +61,13 @@ pub fn decapsulate(
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
match algo {
KemAlgorithm::X25519 => x25519_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::X448 => x448_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5".into()
)),
KemAlgorithm::ClassicMcEliece460896 =>
Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into()
)),
KemAlgorithm::X448 => x448_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5".into(),
)),
KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into(),
)),
}
}
@ -96,41 +90,51 @@ fn x25519_generate() -> Result<KemKeyPair, CryptoError> {
let ret = crate::sys::wc_InitRng(&mut rng);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_InitRng returned {}",
ret
)));
}
let ret = crate::sys::wc_curve25519_make_key(&mut rng, 32, &mut key);
if ret != 0 {
crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError(
format!("wc_curve25519_make_key returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve25519_make_key returned {}",
ret
)));
}
let mut pub_sz = public_key.len() as u32;
let mut pub_sz = public_key.len() as u32;
let mut priv_sz = private_key.len() as u32;
// Export in little-endian (EC25519_LITTLE_ENDIAN=0) so all key
// Export in little-endian (EC25519_LITTLE_ENDIAN=0) so all key
// material is in RFC 7748 canonical format.
let ret = crate::sys::wc_curve25519_export_key_raw_ex(
&mut key,
private_key.as_mut_ptr(), &mut priv_sz,
public_key.as_mut_ptr(), &mut pub_sz,
private_key.as_mut_ptr(),
&mut priv_sz,
public_key.as_mut_ptr(),
&mut pub_sz,
X25519_LE,
);
if ret != 0 {
crate::sys::wc_curve25519_free(&mut key);
crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError(
format!("wc_curve25519_export_key_raw_ex returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve25519_export_key_raw_ex returned {}",
ret
)));
}
crate::sys::wc_curve25519_free(&mut key);
crate::sys::wc_FreeRng(&mut rng);
}
Ok(KemKeyPair { public_key, private_key })
Ok(KemKeyPair {
public_key,
private_key,
})
}
fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
@ -140,7 +144,7 @@ fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoErro
let shared = x25519_dh(&ephemeral.private_key, recipient_pub)?;
Ok(KemEncapResult {
// KemKeyPair implements ZeroizeOnDrop (adds Drop) so fields must be cloned.
ciphertext: ephemeral.public_key.clone(),
ciphertext: ephemeral.public_key.clone(),
shared_secret: (*shared).clone(),
})
}
@ -163,57 +167,63 @@ fn x25519_decapsulate(
fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
if private_key.len() != 32 || public_key.len() != 32 {
return Err(CryptoError::InvalidKey(
"X25519: keys must be 32 bytes each".into()
"X25519: keys must be 32 bytes each".into(),
));
}
let mut shared = Zeroizing::new(vec![0u8; 32]);
unsafe {
let mut local_key: crate::sys::curve25519_key = std::mem::zeroed();
let mut local_key: crate::sys::curve25519_key = std::mem::zeroed();
let mut remote_key: crate::sys::curve25519_key = std::mem::zeroed();
// Initialise both key structs before use (sets dp, calls fe_init, etc.).
let ret = crate::sys::wc_curve25519_init(&mut local_key);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_curve25519_init (local) returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve25519_init (local) returned {}",
ret
)));
}
let ret = crate::sys::wc_curve25519_init(&mut remote_key);
if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key);
return Err(CryptoError::InternalError(
format!("wc_curve25519_init (remote) returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve25519_init (remote) returned {}",
ret
)));
}
// Import private key in little-endian (EC25519_LITTLE_ENDIAN = 0).
let ret = crate::sys::wc_curve25519_import_private_ex(
private_key.as_ptr(), 32,
private_key.as_ptr(),
32,
&mut local_key,
X25519_LE,
);
if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key);
crate::sys::wc_curve25519_free(&mut remote_key);
return Err(CryptoError::InvalidKey(
format!("wc_curve25519_import_private_ex returned {}", ret)
));
return Err(CryptoError::InvalidKey(format!(
"wc_curve25519_import_private_ex returned {}",
ret
)));
}
// Import remote public key in little-endian.
let ret = crate::sys::wc_curve25519_import_public_ex(
public_key.as_ptr(), 32,
public_key.as_ptr(),
32,
&mut remote_key,
X25519_LE,
);
if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key);
crate::sys::wc_curve25519_free(&mut remote_key);
return Err(CryptoError::InvalidKey(
format!("wc_curve25519_import_public_ex returned {}", ret)
));
return Err(CryptoError::InvalidKey(format!(
"wc_curve25519_import_public_ex returned {}",
ret
)));
}
let mut shared_sz = 32u32;
@ -231,9 +241,10 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
crate::sys::wc_curve25519_free(&mut remote_key);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_curve25519_shared_secret_ex returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve25519_shared_secret_ex returned {}",
ret
)));
}
}
@ -250,56 +261,66 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
const X448_LE: i32 = 0;
fn x448_generate() -> Result<KemKeyPair, CryptoError> {
let mut public_key = vec![0u8; 56];
let mut public_key = vec![0u8; 56];
let mut private_key = vec![0u8; 56];
unsafe {
let mut key: crate::sys::curve448_key = std::mem::zeroed();
let mut rng: crate::sys::WC_RNG = std::mem::zeroed();
let mut rng: crate::sys::WC_RNG = std::mem::zeroed();
let ret = crate::sys::wc_InitRng(&mut rng);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_InitRng returned {}",
ret
)));
}
let ret = crate::sys::wc_curve448_make_key(&mut rng, 56, &mut key);
if ret != 0 {
crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError(
format!("wc_curve448_make_key returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve448_make_key returned {}",
ret
)));
}
let mut pub_sz = public_key.len() as u32;
let mut pub_sz = public_key.len() as u32;
let mut priv_sz = private_key.len() as u32;
// Export in little-endian (EC448_LITTLE_ENDIAN=0), RFC 7748 canonical format.
let ret = crate::sys::wc_curve448_export_key_raw_ex(
&mut key,
private_key.as_mut_ptr(), &mut priv_sz,
public_key.as_mut_ptr(), &mut pub_sz,
private_key.as_mut_ptr(),
&mut priv_sz,
public_key.as_mut_ptr(),
&mut pub_sz,
X448_LE,
);
if ret != 0 {
crate::sys::wc_curve448_free(&mut key);
crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError(
format!("wc_curve448_export_key_raw_ex returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve448_export_key_raw_ex returned {}",
ret
)));
}
crate::sys::wc_curve448_free(&mut key);
crate::sys::wc_FreeRng(&mut rng);
}
Ok(KemKeyPair { public_key, private_key })
Ok(KemKeyPair {
public_key,
private_key,
})
}
fn x448_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
let ephemeral = x448_generate()?;
let shared = x448_dh(&ephemeral.private_key, recipient_pub)?;
let shared = x448_dh(&ephemeral.private_key, recipient_pub)?;
Ok(KemEncapResult {
ciphertext: ephemeral.public_key.clone(),
ciphertext: ephemeral.public_key.clone(),
shared_secret: (*shared).clone(),
})
}
@ -319,52 +340,64 @@ fn x448_decapsulate(
/// pattern) before importing keys.
fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
if private_key.len() != 56 || public_key.len() != 56 {
return Err(CryptoError::InvalidKey("X448: keys must be 56 bytes each".into()));
return Err(CryptoError::InvalidKey(
"X448: keys must be 56 bytes each".into(),
));
}
let mut shared = Zeroizing::new(vec![0u8; 56]);
unsafe {
let mut local_key: crate::sys::curve448_key = std::mem::zeroed();
let mut local_key: crate::sys::curve448_key = std::mem::zeroed();
let mut remote_key: crate::sys::curve448_key = std::mem::zeroed();
// Initialise both key structs before use.
let ret = crate::sys::wc_curve448_init(&mut local_key);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_curve448_init (local) returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve448_init (local) returned {}",
ret
)));
}
let ret = crate::sys::wc_curve448_init(&mut remote_key);
if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key);
return Err(CryptoError::InternalError(
format!("wc_curve448_init (remote) returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve448_init (remote) returned {}",
ret
)));
}
// Import private key in little-endian (EC448_LITTLE_ENDIAN = 0).
let ret = crate::sys::wc_curve448_import_private_ex(
private_key.as_ptr(), 56, &mut local_key, X448_LE,
private_key.as_ptr(),
56,
&mut local_key,
X448_LE,
);
if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key);
crate::sys::wc_curve448_free(&mut remote_key);
return Err(CryptoError::InvalidKey(
format!("wc_curve448_import_private_ex returned {}", ret)
));
return Err(CryptoError::InvalidKey(format!(
"wc_curve448_import_private_ex returned {}",
ret
)));
}
// Import remote public key in little-endian.
let ret = crate::sys::wc_curve448_import_public_ex(
public_key.as_ptr(), 56, &mut remote_key, X448_LE,
public_key.as_ptr(),
56,
&mut remote_key,
X448_LE,
);
if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key);
crate::sys::wc_curve448_free(&mut remote_key);
return Err(CryptoError::InvalidKey(
format!("wc_curve448_import_public_ex returned {}", ret)
));
return Err(CryptoError::InvalidKey(format!(
"wc_curve448_import_public_ex returned {}",
ret
)));
}
let mut shared_sz = 56u32;
@ -381,9 +414,10 @@ fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>,
crate::sys::wc_curve448_free(&mut remote_key);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_curve448_shared_secret_ex returned {}", ret)
));
return Err(CryptoError::InternalError(format!(
"wc_curve448_shared_secret_ex returned {}",
ret
)));
}
}

View File

@ -20,7 +20,13 @@ pub mod provider;
// FFI bindings generated by bindgen in build.rs.
// When wolfSSL headers are not present (e.g. docs.rs), use a stub.
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::all)]
#[allow(
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
dead_code,
clippy::all
)]
mod sys {
// ── Manual ABI-critical type definitions ────────────────────────────────
//
@ -112,48 +118,98 @@ mod sys {
// ── AES-GCM ─────────────────────────────────────────────────────────
pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesGcmEncrypt(
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint,
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *mut c_uchar, auth_tag_sz: c_uint,
auth_in: *const c_uchar, auth_in_sz: c_uint,
) -> c_int { unreachable!() }
aes: *mut Aes,
out: *mut c_uchar,
in_: *const c_uchar,
sz: c_uint,
iv: *const c_uchar,
iv_sz: c_uint,
auth_tag: *mut c_uchar,
auth_tag_sz: c_uint,
auth_in: *const c_uchar,
auth_in_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesGcmDecrypt(
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint,
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *const c_uchar, auth_tag_sz: c_uint,
auth_in: *const c_uchar, auth_in_sz: c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_AesInit(aes: *mut Aes, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
pub unsafe fn wc_AesFree(aes: *mut Aes) { unreachable!() }
aes: *mut Aes,
out: *mut c_uchar,
in_: *const c_uchar,
sz: c_uint,
iv: *const c_uchar,
iv_sz: c_uint,
auth_tag: *const c_uchar,
auth_tag_sz: c_uint,
auth_in: *const c_uchar,
auth_in_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesInit(aes: *mut Aes, heap: *mut c_void, dev_id: c_int) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesFree(aes: *mut Aes) {
unreachable!()
}
// ── ChaCha20-Poly1305 ────────────────────────────────────────────────
pub unsafe fn wc_ChaCha20Poly1305_Encrypt(
key: *const c_uchar, iv: *const c_uchar,
aad: *const c_uchar, aad_sz: c_uint,
in_: *const c_uchar, in_sz: c_uint,
out: *mut c_uchar, auth_tag: *mut c_uchar,
) -> c_int { unreachable!() }
key: *const c_uchar,
iv: *const c_uchar,
aad: *const c_uchar,
aad_sz: c_uint,
in_: *const c_uchar,
in_sz: c_uint,
out: *mut c_uchar,
auth_tag: *mut c_uchar,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_ChaCha20Poly1305_Decrypt(
key: *const c_uchar, iv: *const c_uchar,
aad: *const c_uchar, aad_sz: c_uint,
in_: *const c_uchar, in_sz: c_uint,
auth_tag: *const c_uchar, out: *mut c_uchar,
) -> c_int { unreachable!() }
key: *const c_uchar,
iv: *const c_uchar,
aad: *const c_uchar,
aad_sz: c_uint,
in_: *const c_uchar,
in_sz: c_uint,
auth_tag: *const c_uchar,
out: *mut c_uchar,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_XChaCha20Poly1305_Encrypt(
dst: *mut c_uchar, dst_space: usize,
src: *const c_uchar, src_len: usize,
ad: *const c_uchar, ad_len: usize,
nonce: *const c_uchar, nonce_len: usize,
key: *const c_uchar, key_len: usize,
) -> c_int { unreachable!() }
dst: *mut c_uchar,
dst_space: usize,
src: *const c_uchar,
src_len: usize,
ad: *const c_uchar,
ad_len: usize,
nonce: *const c_uchar,
nonce_len: usize,
key: *const c_uchar,
key_len: usize,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_XChaCha20Poly1305_Decrypt(
dst: *mut c_uchar, dst_space: usize,
src: *const c_uchar, src_len: usize,
ad: *const c_uchar, ad_len: usize,
nonce: *const c_uchar, nonce_len: usize,
key: *const c_uchar, key_len: usize,
) -> c_int { unreachable!() }
dst: *mut c_uchar,
dst_space: usize,
src: *const c_uchar,
src_len: usize,
ad: *const c_uchar,
ad_len: usize,
nonce: *const c_uchar,
nonce_len: usize,
key: *const c_uchar,
key_len: usize,
) -> c_int {
unreachable!()
}
// ── SHA-256 (init/update/final) ──────────────────────────────────────
@ -195,76 +251,161 @@ mod sys {
pub unsafe fn wc_InitBlake2b(b2b: *mut Blake2b, digest_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_InitBlake2b_WithKey(
b2b: *mut Blake2b, digest_sz: c_uint,
key: *const c_uchar, key_sz: c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_Blake2bUpdate(b2b: *mut Blake2b, in_: *const c_uchar, in_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_Blake2bFinal(b2b: *mut Blake2b, final_: *mut c_uchar, request_sz: c_uint) -> c_int { unreachable!() }
b2b: *mut Blake2b,
digest_sz: c_uint,
key: *const c_uchar,
key_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_Blake2bUpdate(
b2b: *mut Blake2b,
in_: *const c_uchar,
in_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_Blake2bFinal(
b2b: *mut Blake2b,
final_: *mut c_uchar,
request_sz: c_uint,
) -> c_int {
unreachable!()
}
// ── HMAC ────────────────────────────────────────────────────────────
pub unsafe fn wc_HmacInit(hmac: *mut Hmac, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
pub unsafe fn wc_HmacSetKey(hmac: *mut Hmac, type_: c_int, key: *const c_uchar, key_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_HmacUpdate(hmac: *mut Hmac, in_: *const c_uchar, in_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFinal(hmac: *mut Hmac, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFree(hmac: *mut Hmac) { unreachable!() }
pub unsafe fn wc_HmacSetKey(
hmac: *mut Hmac,
type_: c_int,
key: *const c_uchar,
key_sz: c_uint,
) -> c_int {
unreachable!()
}
// ── HKDF (TLS-1.3 Extract — RFC 5869 Extract step) ──────────────────
pub unsafe fn wc_Tls13_HKDF_Extract(
prk: *mut c_uchar,
salt: *const c_uchar, salt_len: c_uint,
ikm: *mut c_uchar, ikm_len: c_uint,
salt: *const c_uchar,
salt_len: c_uint,
ikm: *mut c_uchar,
ikm_len: c_uint,
digest: c_int,
) -> c_int { unreachable!() }
) -> c_int {
unreachable!()
}
// ── RNG ─────────────────────────────────────────────────────────────
pub unsafe fn wc_InitRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
pub unsafe fn wc_RNG_GenerateBlock(rng: *mut WC_RNG, buf: *mut c_uchar, sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_FreeRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
pub unsafe fn wc_RNG_GenerateBlock(
rng: *mut WC_RNG,
buf: *mut c_uchar,
sz: c_uint,
) -> c_int {
unreachable!()
}
// ── Curve25519 ───────────────────────────────────────────────────────
pub unsafe fn wc_curve25519_make_key(rng: *mut WC_RNG, key_sz: c_int, key: *mut curve25519_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_init(key: *mut curve25519_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_make_key(
rng: *mut WC_RNG,
key_sz: c_int,
key: *mut curve25519_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_export_key_raw(
key: *mut curve25519_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint,
pub_: *mut c_uchar, pub_sz: *mut c_uint,
) -> c_int { unreachable!() }
priv_: *mut c_uchar,
priv_sz: *mut c_uint,
pub_: *mut c_uchar,
pub_sz: *mut c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_import_private(
priv_: *const c_uchar, priv_sz: c_uint, key: *mut curve25519_key,
) -> c_int { unreachable!() }
priv_: *const c_uchar,
priv_sz: c_uint,
key: *mut curve25519_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_import_public(
pub_: *const c_uchar, pub_sz: c_uint, key: *mut curve25519_key,
) -> c_int { unreachable!() }
pub_: *const c_uchar,
pub_sz: c_uint,
key: *mut curve25519_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_shared_secret_ex(
local: *mut curve25519_key, remote: *mut curve25519_key,
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_free(key: *mut curve25519_key) { unreachable!() }
local: *mut curve25519_key,
remote: *mut curve25519_key,
out: *mut c_uchar,
out_sz: *mut c_uint,
endian: c_int,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_free(key: *mut curve25519_key) {
unreachable!()
}
// ── Curve448 ─────────────────────────────────────────────────────────
pub unsafe fn wc_curve448_make_key(rng: *mut WC_RNG, key_sz: c_int, key: *mut curve448_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_init(key: *mut curve448_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_make_key(
rng: *mut WC_RNG,
key_sz: c_int,
key: *mut curve448_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_init(key: *mut curve448_key) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_export_key_raw(
key: *mut curve448_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint,
pub_: *mut c_uchar, pub_sz: *mut c_uint,
) -> c_int { unreachable!() }
priv_: *mut c_uchar,
priv_sz: *mut c_uint,
pub_: *mut c_uchar,
pub_sz: *mut c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_import_private(
priv_: *const c_uchar, priv_sz: c_uint, key: *mut curve448_key,
) -> c_int { unreachable!() }
priv_: *const c_uchar,
priv_sz: c_uint,
key: *mut curve448_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_import_public(
pub_: *const c_uchar, pub_sz: c_uint, key: *mut curve448_key,
) -> c_int { unreachable!() }
pub_: *const c_uchar,
pub_sz: c_uint,
key: *mut curve448_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_shared_secret_ex(
local: *mut curve448_key, remote: *mut curve448_key,
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_free(key: *mut curve448_key) { unreachable!() }
local: *mut curve448_key,
remote: *mut curve448_key,
out: *mut c_uchar,
out_sz: *mut c_uint,
endian: c_int,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_free(key: *mut curve448_key) {
unreachable!()
}
}
}
@ -276,7 +417,6 @@ pub use provider::WolfSslProvider;
/// Call this once from `ccc_init()` in the bridge crate.
pub fn init() {
let provider = WolfSslProvider::new();
ccc_crypto_core::ProviderRegistry::global()
.register("wolfssl", Box::new(provider));
ccc_crypto_core::ProviderRegistry::global().register("wolfssl", Box::new(provider));
log::info!("[ccc-crypto-wolfssl] provider registered");
}

View File

@ -17,20 +17,15 @@ const WC_HASH_TYPE_SHA512: i32 = 8;
// ──────────────────────────────────────────────────────────────────────────────
/// Compute a MAC tag over `data` using the specified algorithm and `key`.
pub fn compute_mac(
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
pub fn compute_mac(algo: MacAlgorithm, key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
match algo {
MacAlgorithm::HmacSha256 => hmac(WC_HASH_TYPE_SHA256, key, data, 32),
MacAlgorithm::HmacSha384 => hmac(WC_HASH_TYPE_SHA384, key, data, 48),
MacAlgorithm::HmacSha512 => hmac(WC_HASH_TYPE_SHA512, key, data, 64),
MacAlgorithm::Blake2bMac => blake2b_mac(key, data),
MacAlgorithm::Poly1305 =>
Err(CryptoError::UnsupportedAlgorithm(
"Poly1305 standalone MAC is not exposed in this Phase".into()
)),
MacAlgorithm::Poly1305 => Err(CryptoError::UnsupportedAlgorithm(
"Poly1305 standalone MAC is not exposed in this Phase".into(),
)),
}
}
@ -71,11 +66,8 @@ pub(crate) fn hmac(
use std::alloc::{alloc_zeroed, dealloc, Layout};
// sizeof(Hmac) = 456 bytes; alignment = 16 (from embedded wc_Sha256).
let layout = Layout::from_size_align(
std::mem::size_of::<crate::sys::Hmac>().max(456),
16,
)
.expect("layout must be valid");
let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Hmac>().max(456), 16)
.expect("layout must be valid");
let hmac_ptr = alloc_zeroed(layout) as *mut crate::sys::Hmac;
if hmac_ptr.is_null() {
@ -90,40 +82,40 @@ pub(crate) fn hmac(
);
if ret != 0 {
dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_HmacInit returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_HmacInit returned {}",
ret
)));
}
let ret = crate::sys::wc_HmacSetKey(
hmac_ptr,
hash_type,
key.as_ptr(),
key.len() as u32,
);
let ret = crate::sys::wc_HmacSetKey(hmac_ptr, hash_type, key.as_ptr(), key.len() as u32);
if ret != 0 {
crate::sys::wc_HmacFree(hmac_ptr);
dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InvalidKey(format!("wc_HmacSetKey returned {}", ret)));
return Err(CryptoError::InvalidKey(format!(
"wc_HmacSetKey returned {}",
ret
)));
}
let ret = crate::sys::wc_HmacUpdate(
hmac_ptr,
data.as_ptr(),
data.len() as u32,
);
let ret = crate::sys::wc_HmacUpdate(hmac_ptr, data.as_ptr(), data.len() as u32);
if ret != 0 {
crate::sys::wc_HmacFree(hmac_ptr);
dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_HmacUpdate returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_HmacUpdate returned {}",
ret
)));
}
let ret = crate::sys::wc_HmacFinal(
hmac_ptr,
out.as_mut_ptr(),
);
let ret = crate::sys::wc_HmacFinal(hmac_ptr, out.as_mut_ptr());
if ret != 0 {
crate::sys::wc_HmacFree(hmac_ptr);
dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_HmacFinal returned {}", ret)));
return Err(CryptoError::InternalError(format!(
"wc_HmacFinal returned {}",
ret
)));
}
crate::sys::wc_HmacFree(hmac_ptr);
@ -146,7 +138,7 @@ pub(crate) fn hmac(
fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
if key.is_empty() || key.len() > 64 {
return Err(CryptoError::InvalidKey(
"BLAKE2b-MAC: key must be 164 bytes".into()
"BLAKE2b-MAC: key must be 164 bytes".into(),
));
}
@ -157,30 +149,26 @@ fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut b2b: crate::sys::Blake2b = std::mem::zeroed();
// Initialise with digest size and key for keyed (MAC) mode.
let ret = crate::sys::wc_InitBlake2b_WithKey(
&mut b2b,
OUT_LEN,
key.as_ptr(),
key.len() as u32,
);
let ret =
crate::sys::wc_InitBlake2b_WithKey(&mut b2b, OUT_LEN, key.as_ptr(), key.len() as u32);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_InitBlake2b_WithKey returned {ret}")
));
return Err(CryptoError::InternalError(format!(
"wc_InitBlake2b_WithKey returned {ret}"
)));
}
let ret = crate::sys::wc_Blake2bUpdate(&mut b2b, data.as_ptr(), data.len() as u32);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_Blake2bUpdate returned {ret}")
));
return Err(CryptoError::InternalError(format!(
"wc_Blake2bUpdate returned {ret}"
)));
}
let ret = crate::sys::wc_Blake2bFinal(&mut b2b, out.as_mut_ptr(), OUT_LEN);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_Blake2bFinal returned {ret}")
));
return Err(CryptoError::InternalError(format!(
"wc_Blake2bFinal returned {ret}"
)));
}
}
@ -201,7 +189,10 @@ fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
}
// Accumulate XOR differences; the branch is on the final accumulated value
// only, not on any individual byte.
let diff: u8 = a.iter().zip(b.iter()).fold(0u8, |acc, (x, y)| acc | (x ^ y));
let diff: u8 = a
.iter()
.zip(b.iter())
.fold(0u8, |acc, (x, y)| acc | (x ^ y));
diff == 0
}

View File

@ -12,9 +12,7 @@ use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::ProviderCapabilities,
error::CryptoError,
provider::{
AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider,
},
provider::{AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
types::{AlgoTestResult, BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
};
@ -27,34 +25,34 @@ use crate::{aead, capabilities, hash, kdf, kem, mac};
// ──────────────────────────────────────────────────────────────────────────────
struct AeadVector {
algo: AeadAlgorithm,
key: &'static str,
nonce: &'static str,
aad: &'static str,
pt: &'static str,
ct_tag: &'static str, // ciphertext || tag, hex-encoded
algo: AeadAlgorithm,
key: &'static str,
nonce: &'static str,
aad: &'static str,
pt: &'static str,
ct_tag: &'static str, // ciphertext || tag, hex-encoded
}
/// NIST SP 800-38D and RFC 8439 test vectors.
static AEAD_VECTORS: &[AeadVector] = &[
// ── AES-256-GCM: NIST CAVS test case GCM-256/96/128 (test case 1) ───────
AeadVector {
algo: AeadAlgorithm::AesGcm256,
key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
aad: "",
pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
algo: AeadAlgorithm::AesGcm256,
key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
aad: "",
pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
ct_tag: "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa\
8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
},
// ── ChaCha20-Poly1305: RFC 8439 §2.8.2 test vector ──────────────────────
AeadVector {
algo: AeadAlgorithm::ChaCha20Poly1305,
key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
nonce: "070000004041424344454647",
aad: "50515253c0c1c2c3c4c5c6c7",
pt: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373\
algo: AeadAlgorithm::ChaCha20Poly1305,
key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
nonce: "070000004041424344454647",
aad: "50515253c0c1c2c3c4c5c6c7",
pt: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373\
206f6620273939",
ct_tag: "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63\
dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b369\
@ -97,14 +95,19 @@ impl WolfSslProvider {
self.bench.get_or_init(|| {
log::info!("[ccc-crypto-wolfssl] running throughput benchmark…");
let report = capabilities::run_benchmark();
log::info!("[ccc-crypto-wolfssl] benchmark complete ({} algos)", report.results.len());
log::info!(
"[ccc-crypto-wolfssl] benchmark complete ({} algos)",
report.results.len()
);
report
})
}
}
impl Default for WolfSslProvider {
fn default() -> Self { Self::new() }
fn default() -> Self {
Self::new()
}
}
// ──────────────────────────────────────────────────────────────────────────────
@ -113,15 +116,23 @@ impl Default for WolfSslProvider {
impl AeadProvider for WolfSslProvider {
fn encrypt_aead(
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8],
plaintext: &[u8], aad: &[u8],
&self,
algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
aead::encrypt(algo, key, nonce, plaintext, aad)
}
fn decrypt_aead(
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8],
ciphertext_and_tag: &[u8], aad: &[u8],
&self,
algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
aead::decrypt(algo, key, nonce, ciphertext_and_tag, aad)
}
@ -129,7 +140,12 @@ impl AeadProvider for WolfSslProvider {
impl KdfProvider for WolfSslProvider {
fn derive_key(
&self, algo: KdfAlgorithm, ikm: &[u8], salt: &[u8], info: &[u8], length: usize,
&self,
algo: KdfAlgorithm,
ikm: &[u8],
salt: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
kdf::derive_key(algo, ikm, salt, info, length)
}
@ -137,13 +153,20 @@ impl KdfProvider for WolfSslProvider {
impl MacProvider for WolfSslProvider {
fn compute_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8],
&self,
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
mac::compute_mac(algo, key, data)
}
fn verify_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8], mac_bytes: &[u8],
&self,
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
mac_bytes: &[u8],
) -> Result<bool, CryptoError> {
mac::verify_mac(algo, key, data, mac_bytes)
}
@ -162,12 +185,17 @@ impl KemProvider for WolfSslProvider {
kem::generate_keypair(algo)
}
fn encapsulate(
&self, algo: KemAlgorithm, public_key: &[u8],
&self,
algo: KemAlgorithm,
public_key: &[u8],
) -> Result<KemEncapResult, CryptoError> {
kem::encapsulate(algo, public_key)
}
fn decapsulate(
&self, algo: KemAlgorithm, private_key: &[u8], ciphertext: &[u8],
&self,
algo: KemAlgorithm,
private_key: &[u8],
ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
kem::decapsulate(algo, private_key, ciphertext)
}
@ -178,13 +206,17 @@ impl KemProvider for WolfSslProvider {
// ──────────────────────────────────────────────────────────────────────────────
impl CryptoProvider for WolfSslProvider {
fn provider_name(&self) -> &'static str { "wolfssl" }
fn provider_name(&self) -> &'static str {
"wolfssl"
}
fn capabilities(&self) -> ProviderCapabilities {
self.caps.get_or_init(|| {
let bench = self.get_or_run_benchmark();
capabilities::probe_capabilities(Some(bench))
}).clone()
self.caps
.get_or_init(|| {
let bench = self.get_or_run_benchmark();
capabilities::probe_capabilities(Some(bench))
})
.clone()
}
fn self_test(&self) -> SelfTestReport {
@ -218,10 +250,10 @@ fn run_aead_vector(v: &AeadVector) -> AlgoTestResult {
.collect()
};
let key = decode(v.key);
let nonce = decode(v.nonce);
let aad = decode(v.aad);
let pt = decode(v.pt);
let key = decode(v.key);
let nonce = decode(v.nonce);
let aad = decode(v.aad);
let pt = decode(v.pt);
let expected = decode(v.ct_tag);
match aead::encrypt(v.algo, &key, &nonce, &pt, &aad) {
@ -237,7 +269,8 @@ fn run_aead_vector(v: &AeadVector) -> AlgoTestResult {
passed: false,
error_message: Some(format!(
"output mismatch: got {} bytes, expected {} bytes",
ct_tag.len(), expected.len()
ct_tag.len(),
expected.len()
)),
},
Err(e) => AlgoTestResult {

View File

@ -61,17 +61,17 @@ struct HashVec {
/// Both sides are symmetric: `decapsulate(algo, bob_private, alice_public)` is
/// verified as a second assertion in `run_kem()`.
struct KemDhVec {
name: &'static str,
algo: KemAlgorithm,
name: &'static str,
algo: KemAlgorithm,
/// Alice's static private key (little-endian).
alice_private: &'static str,
alice_private: &'static str,
/// Alice's corresponding public key (little-endian), used for the Bob→Alice
/// direction check.
alice_public: &'static str,
alice_public: &'static str,
/// Bob's static private key (little-endian).
bob_private: &'static str,
bob_private: &'static str,
/// Bob's corresponding public key (little-endian).
bob_public: &'static str,
bob_public: &'static str,
/// Expected shared secret (little-endian).
expected_shared: &'static str,
}
@ -347,12 +347,24 @@ fn run_aead(p: &WolfSslProvider, failures: &mut usize) {
// Verify round-trip decrypt as well.
match p.decrypt_aead(v.algo, &key, &nonce, &ct_tag, &aad) {
Ok(recovered) if recovered == pt => pass(v.name),
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", v.name); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", v.name); }
Ok(_) => {
*failures += 1;
println!(" [FAIL] {} (decrypt mismatch)", v.name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (decrypt error: {e})", v.name);
}
}
}
Ok(ct_tag) => { *failures += 1; fail(v.name, &ct_tag, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (encrypt error: {e})", v.name); }
Ok(ct_tag) => {
*failures += 1;
fail(v.name, &ct_tag, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (encrypt error: {e})", v.name);
}
}
}
}
@ -367,8 +379,14 @@ fn run_kdf(p: &WolfSslProvider, failures: &mut usize) {
match p.derive_key(v.algo, &ikm, &salt, &info, v.length) {
Ok(out) if out.as_slice() == expected.as_slice() => pass(v.name),
Ok(out) => { *failures += 1; fail(v.name, &out, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
Ok(out) => {
*failures += 1;
fail(v.name, &out, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", v.name);
}
}
}
}
@ -385,12 +403,24 @@ fn run_mac(p: &WolfSslProvider, failures: &mut usize) {
// Also verify constant-time path.
match p.verify_mac(v.algo, &key, &data, &tag) {
Ok(true) => pass(v.name),
Ok(false) => { *failures += 1; println!(" [FAIL] {} (verify false)", v.name); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (verify error: {e})", v.name); }
Ok(false) => {
*failures += 1;
println!(" [FAIL] {} (verify false)", v.name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (verify error: {e})", v.name);
}
}
}
Ok(tag) => { *failures += 1; fail(v.name, &tag, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
Ok(tag) => {
*failures += 1;
fail(v.name, &tag, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", v.name);
}
}
}
}
@ -403,8 +433,14 @@ fn run_hash(p: &WolfSslProvider, failures: &mut usize) {
match p.hash(v.algo, &data) {
Ok(digest) if digest == expected => pass(v.name),
Ok(digest) => { *failures += 1; fail(v.name, &digest, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
Ok(digest) => {
*failures += 1;
fail(v.name, &digest, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", v.name);
}
}
}
}
@ -431,16 +467,28 @@ fn run_kem(p: &WolfSslProvider, failures: &mut usize) {
let test_a = format!("{} (Alice→Bob)", v.name);
match p.decapsulate(v.algo, &alice_priv, &bob_pub) {
Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_a),
Ok(shared) => { *failures += 1; fail(&test_a, &shared, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_a); }
Ok(shared) => {
*failures += 1;
fail(&test_a, &shared, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", test_a);
}
}
// Bob→Alice direction (symmetric).
let test_b = format!("{} (Bob→Alice)", v.name);
match p.decapsulate(v.algo, &bob_priv, &alice_pub) {
Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_b),
Ok(shared) => { *failures += 1; fail(&test_b, &shared, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_b); }
Ok(shared) => {
*failures += 1;
fail(&test_b, &shared, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", test_b);
}
}
}
}
@ -455,27 +503,33 @@ fn run_kem_roundtrip(p: &WolfSslProvider, failures: &mut usize) {
for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] {
let name = format!("{:?} ephemeral roundtrip", algo);
match p.generate_keypair(algo) {
Err(e) => { *failures += 1; println!(" [FAIL] {} (keygen: {e})", name); continue; }
Ok(kp) => {
match p.encapsulate(algo, &kp.public_key) {
Err(e) => { *failures += 1; println!(" [FAIL] {} (encap: {e})", name); }
Ok(encap) => {
match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) {
Err(e) => { *failures += 1; println!(" [FAIL] {} (decap: {e})", name); }
Ok(decap_secret) => {
if decap_secret.as_slice() == encap.shared_secret.as_slice() {
pass(&name);
} else {
*failures += 1;
println!(" [FAIL] {} (shared-secret mismatch)", name);
println!(" encap: {}", hex::encode(&encap.shared_secret));
println!(" decap: {}", hex::encode(&decap_secret));
}
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (keygen: {e})", name);
continue;
}
Ok(kp) => match p.encapsulate(algo, &kp.public_key) {
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (encap: {e})", name);
}
Ok(encap) => match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) {
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (decap: {e})", name);
}
Ok(decap_secret) => {
if decap_secret.as_slice() == encap.shared_secret.as_slice() {
pass(&name);
} else {
*failures += 1;
println!(" [FAIL] {} (shared-secret mismatch)", name);
println!(" encap: {}", hex::encode(&encap.shared_secret));
println!(" decap: {}", hex::encode(&decap_secret));
}
}
}
}
},
},
}
}
}
@ -511,10 +565,22 @@ fn run_xchacha20_kat(p: &WolfSslProvider, failures: &mut usize) {
// Print the ct_tag for pinning purposes.
println!(" [INFO] {} ct_tag = {}", v.name, hex::encode(&ct_tag));
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct_tag, &aad) {
match p.decrypt_aead(
AeadAlgorithm::XChaCha20Poly1305,
&key,
&nonce,
&ct_tag,
&aad,
) {
Ok(recovered) if recovered == pt => pass(&rt_name),
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", rt_name); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", rt_name); }
Ok(_) => {
*failures += 1;
println!(" [FAIL] {} (decrypt mismatch)", rt_name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (decrypt error: {e})", rt_name);
}
}
// ── Auth-failure check ────────────────────────────────────
@ -522,10 +588,24 @@ fn run_xchacha20_kat(p: &WolfSslProvider, failures: &mut usize) {
let auth_name = format!("{} [auth-fail]", v.name);
let mut tampered = ct_tag.clone();
*tampered.last_mut().unwrap() ^= 0xff;
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &tampered, &aad) {
Err(ccc_crypto_core::error::CryptoError::AuthenticationFailed) => pass(&auth_name),
Ok(_) => { *failures += 1; println!(" [FAIL] {} (expected auth failure, got Ok)", auth_name); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (wrong error type: {e})", auth_name); }
match p.decrypt_aead(
AeadAlgorithm::XChaCha20Poly1305,
&key,
&nonce,
&tampered,
&aad,
) {
Err(ccc_crypto_core::error::CryptoError::AuthenticationFailed) => {
pass(&auth_name)
}
Ok(_) => {
*failures += 1;
println!(" [FAIL] {} (expected auth failure, got Ok)", auth_name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (wrong error type: {e})", auth_name);
}
}
}
}