FIX: lots of mis-match between what's in the generated bindings and what our code calls with wolfcrypt

This commit is contained in:
JohnE 2026-02-23 18:01:22 -08:00
parent 3c9320ffa5
commit 3b226d0319
9 changed files with 598 additions and 224 deletions

62
Cargo.lock generated
View File

@ -111,6 +111,19 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
"zeroize",
]
[[package]]
name = "atomic"
version = "0.5.3"
@ -132,6 +145,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bindgen"
version = "0.72.1"
@ -158,6 +177,15 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -229,6 +257,7 @@ dependencies = [
name = "ccc-crypto-wolfssl"
version = "0.1.0"
dependencies = [
"argon2",
"bindgen",
"ccc-crypto-core",
"cmake",
@ -303,6 +332,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
@ -351,6 +389,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -728,6 +767,17 @@ dependencies = [
"log",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -777,6 +827,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "regex"
version = "1.12.3"
@ -879,6 +935,12 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"

View File

@ -11,6 +11,8 @@ ccc-crypto-core.workspace = true
zeroize.workspace = true
thiserror.workspace = true
log.workspace = true
# Argon2id KDF — wolfSSL v5.7.x has no built-in Argon2, so we use RustCrypto.
argon2 = { version = "0.5", features = ["zeroize"] }
[build-dependencies]
# cmake crate drives the wolfSSL CMake build from source.

View File

@ -86,7 +86,7 @@ fn main() {
}
/// Build wolfSSL from source using CMake. Returns `(include_dir, lib_dir)`.
fn build_wolfssl_cmake(source_dir: &PathBuf, out_dir: &PathBuf) -> (PathBuf, PathBuf) {
fn build_wolfssl_cmake(source_dir: &PathBuf, _out_dir: &PathBuf) -> (PathBuf, PathBuf) {
println!(
"cargo:warning=Building wolfSSL from source at {}",
source_dir.display()
@ -95,38 +95,55 @@ fn build_wolfssl_cmake(source_dir: &PathBuf, out_dir: &PathBuf) -> (PathBuf, Pat
let mut cfg = cmake::Config::new(source_dir);
// ── Core algorithm selection ─────────────────────────────────────────────
// Enable exactly the algorithms the CCC system needs in Phase 4.
// Additional algorithms can be enabled here for Phase 5+ PQ support.
// wolfSSL cmake uses add_option() with "yes"/"no" string values (not ON/OFF).
// Option names must exactly match the `add_option("NAME" ...)` calls in
// wolfSSL's CMakeLists.txt — notably WOLFSSL_AESGCM (no underscore between
// AES and GCM) and WOLFSSL_CRYPT_ONLY (not WOLFSSL_CRYPTONLY).
cfg
// Build as a static library.
.define("BUILD_SHARED_LIBS", "OFF")
.define("WOLFSSL_BUILD_SHARED_LIBS", "OFF")
// Disable TLS/SSL stack — we only need the wolfCrypt algorithms.
.define("WOLFSSL_NO_TLS", "ON")
// AEAD
.define("WOLFSSL_AES_GCM", "ON")
.define("WOLFSSL_CHACHA", "ON")
.define("WOLFSSL_POLY1305", "ON")
// wolfCrypt-only — no TLS stack. This is the correct cmake variable.
.define("WOLFSSL_CRYPT_ONLY", "yes")
// AEAD (WOLFSSL_AESGCM — note: no underscore between AES and GCM)
.define("WOLFSSL_AESGCM", "yes")
.define("WOLFSSL_CHACHA", "yes")
.define("WOLFSSL_POLY1305", "yes")
// Hash
.define("WOLFSSL_SHA224", "ON")
.define("WOLFSSL_SHA384", "ON")
.define("WOLFSSL_SHA512", "ON")
.define("WOLFSSL_SHA3", "ON")
.define("WOLFSSL_BLAKE2", "ON")
// KDF
.define("WOLFSSL_HKDF", "ON")
.define("WOLFSSL_PWDBASED", "ON") // PBKDF2, Argon2
.define("WOLFSSL_SHA384", "yes")
.define("WOLFSSL_SHA512", "yes")
.define("WOLFSSL_SHA3", "yes")
// BLAKE2b/BLAKE2s — not a cmake option; enabled via C preprocessor flag.
// Added to both cmake C flags and bindgen clang args below.
// KDF (HKDF via TLS-1.3 HKDF primitives, PBKDF2)
.define("WOLFSSL_HKDF", "yes")
.define("WOLFSSL_PWDBASED", "yes")
// MAC
.define("WOLFSSL_HMAC", "ON")
// Asymmetric (needed for X25519 ratchet)
.define("WOLFSSL_CURVE25519", "ON")
.define("WOLFSSL_CURVE448", "ON")
// RNG (needed internally)
.define("WOLFSSL_RNG", "ON")
.define("WOLFSSL_HMAC", "yes")
// Asymmetric (X25519 / X448 DH for ratchet)
.define("WOLFSSL_CURVE25519", "yes")
.define("WOLFSSL_CURVE448", "yes")
// RNG
.define("WOLFSSL_RNG", "yes")
// Minimise binary size.
.define("WOLFSSL_CRYPTONLY", "ON")
.define("WOLFSSL_MIN_RSA_BITS", "2048")
.define("WOLFSSL_NO_FILESYSTEM", "ON");
// BLAKE2b — two steps needed:
// 1. cmake option adds blake2b.c to the library source list.
// 2. C preprocessor flag enables the code inside blake2b.c.
.define("WOLFSSL_BLAKE2", "yes")
// Disable building test / benchmark executables.
// WOLFSSL_CRYPT_ONLY already disables TLS examples (WOLFSSL_EXAMPLES),
// but WOLFSSL_CRYPT_TESTS (test + benchmark binaries) defaults to "yes"
// and must be disabled separately. Without this, the linker fails on
// missing TLS symbols (_GetCA etc.) referenced from asn.c when it tries
// to link the test/benchmark executables.
.define("WOLFSSL_CRYPT_TESTS", "no")
// BLAKE2b (step 2) and XChaCha20 have no cmake option — pass as C
// compiler flags so the code inside the compiled files is enabled.
// BLAKE2: wc_InitBlake2b/wc_Blake2bUpdate/wc_Blake2bFinal API.
// XCHACHA: wc_XChaCha20Poly1305_Encrypt/Decrypt (24-byte nonce).
.cflag("-DHAVE_BLAKE2")
.cflag("-DHAVE_BLAKE2B")
.cflag("-DHAVE_XCHACHA");
let install_path = cfg.build();
@ -157,40 +174,127 @@ fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
.header(include_dir.join("wolfssl/wolfcrypt/aes.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/chacha20_poly1305.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/sha256.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/sha512.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/sha512.h").to_str().unwrap()) // also covers sha384
.header(include_dir.join("wolfssl/wolfcrypt/sha3.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/blake2.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/hmac.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/kdf.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/pwdbased.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/curve25519.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/curve448.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/random.h").to_str().unwrap())
.clang_arg(format!("-I{}", include_dir.display()))
// Only generate bindings for the types/functions we whitelist.
// Pass all required preprocessor defines explicitly so that bindgen's
// libclang sees the same feature flags as the C build, regardless of
// whether wolfSSL's options.h is correctly resolved by clang.
//
// AES-GCM (wc_AesGcmSetKey etc. are behind #ifdef HAVE_AESGCM)
.clang_arg("-DHAVE_AESGCM")
// ChaCha20 / Poly1305 / XChaCha20
.clang_arg("-DHAVE_CHACHA")
.clang_arg("-DHAVE_POLY1305")
.clang_arg("-DHAVE_XCHACHA")
// BLAKE2 is not a cmake option — enable via clang preprocessor flags
// so that the headers expose the BLAKE2b/BLAKE2s function prototypes.
.clang_arg("-DHAVE_BLAKE2")
.clang_arg("-DHAVE_BLAKE2B")
// Required so sha512.h exposes SHA384 typedef + functions.
.clang_arg("-DWOLFSSL_SHA384")
.clang_arg("-DWOLFSSL_SHA512")
// SHA3 functions (wc_InitSha3_256, etc.)
.clang_arg("-DWOLFSSL_SHA3")
// HKDF via TLS-1.3 primitives (wc_Tls13_HKDF_Extract/Expand_Label)
.clang_arg("-DHAVE_HKDF")
// Curve25519 / Curve448 need HAVE_CURVE25519 / HAVE_CURVE448
.clang_arg("-DHAVE_CURVE25519")
.clang_arg("-DHAVE_CURVE448")
// Only generate bindings for the types/functions we use.
.allowlist_function("wc_Aes.*")
.allowlist_function("wc_AesGcm.*")
.allowlist_function("wc_AesInit")
.allowlist_function("wc_AesFree")
.allowlist_function("wc_ChaCha20Poly1305.*")
.allowlist_function("wc_Sha.*")
.allowlist_function("wc_Blake2.*")
.allowlist_function("wc_Hmac.*")
.allowlist_function("wc_HKDF.*")
.allowlist_function("wc_Pbkdf2.*")
.allowlist_function("wc_Argon2.*")
.allowlist_function("wc_curve25519.*")
.allowlist_function("wc_curve448.*")
.allowlist_function("wc_InitRng.*")
.allowlist_function("wc_RNG.*")
.allowlist_function("wc_FreeRng.*")
.allowlist_function("wc_XChaCha20Poly1305.*")
// SHA-256 (one-shot available)
.allowlist_function("wc_Sha256Hash")
.allowlist_function("wc_Sha256.*")
// SHA-384 (init/update/final — typedef of wc_Sha512 struct)
.allowlist_function("wc_InitSha384")
.allowlist_function("wc_Sha384Update")
.allowlist_function("wc_Sha384Final")
.allowlist_function("wc_Sha384Free")
// SHA-512 (init/update/final)
.allowlist_function("wc_InitSha512")
.allowlist_function("wc_Sha512Update")
.allowlist_function("wc_Sha512Final")
.allowlist_function("wc_Sha512Free")
// SHA-3 (init/update/final for 256 and 512)
.allowlist_function("wc_InitSha3_256")
.allowlist_function("wc_Sha3_256_Update")
.allowlist_function("wc_Sha3_256_Final")
.allowlist_function("wc_Sha3_256_Free")
.allowlist_function("wc_InitSha3_512")
.allowlist_function("wc_Sha3_512_Update")
.allowlist_function("wc_Sha3_512_Final")
.allowlist_function("wc_Sha3_512_Free")
// BLAKE2b (init/update/final + keyed variant)
.allowlist_function("wc_InitBlake2b")
.allowlist_function("wc_InitBlake2b_WithKey")
.allowlist_function("wc_Blake2bUpdate")
.allowlist_function("wc_Blake2bFinal")
// HMAC
.allowlist_function("wc_HmacSetKey")
.allowlist_function("wc_HmacUpdate")
.allowlist_function("wc_HmacFinal")
.allowlist_function("wc_HmacFree")
// HKDF (TLS-1.3 Extract is RFC 5869 Extract; no generic Expand in wolfSSL)
.allowlist_function("wc_Tls13_HKDF_Extract")
.allowlist_function("wc_Tls13_HKDF_Extract_ex")
// PBKDF2
.allowlist_function("wc_PBKDF2")
.allowlist_function("wc_PBKDF2_ex")
// Curve25519
.allowlist_function("wc_curve25519_make_key")
.allowlist_function("wc_curve25519_init")
.allowlist_function("wc_curve25519_free")
.allowlist_function("wc_curve25519_import_private")
.allowlist_function("wc_curve25519_import_public")
.allowlist_function("wc_curve25519_export_key_raw")
.allowlist_function("wc_curve25519_shared_secret_ex")
// Curve448
.allowlist_function("wc_curve448_make_key")
.allowlist_function("wc_curve448_init")
.allowlist_function("wc_curve448_free")
.allowlist_function("wc_curve448_import_private")
.allowlist_function("wc_curve448_import_public")
.allowlist_function("wc_curve448_export_key_raw")
.allowlist_function("wc_curve448_shared_secret_ex")
// RNG
.allowlist_function("wc_InitRng")
.allowlist_function("wc_RNG_GenerateBlock")
.allowlist_function("wc_FreeRng")
// Types
.allowlist_type("Aes")
.allowlist_type("Hmac")
.allowlist_type("WC_RNG")
.allowlist_type("curve25519.*")
.allowlist_type("curve448.*")
.allowlist_type("OS_Seed")
.allowlist_type("wc_Sha256")
.allowlist_type("wc_Sha512") // also used as wc_Sha384 (typedef)
.allowlist_type("wc_Sha3")
.allowlist_type("Blake2b")
.allowlist_type("curve25519_key")
.allowlist_type("curve448_key")
// Constants we use in Rust
.allowlist_var("AES_.*")
.allowlist_var("WC_.*")
.allowlist_var("SHA.*")
.allowlist_var("BLAKE2.*")
// Silence warnings for types we don't control.
.blocklist_type("__.*")
.allowlist_var("WC_SHA.*")
.allowlist_var("WC_HASH.*")
.allowlist_var("CHACHA20_POLY1305.*")
// Only block specific C internal types that cause noise.
// Note: do NOT use ".blocklist_type("__.*")" here because that regex
// also matches `__BindgenBitfieldUnit`, a Rust helper type that bindgen
// emits for C structs with bitfields (Aes, Hmac, etc.).
.blocklist_type("__uint128_t")
.blocklist_type("__int128_t")
.derive_debug(false)
.layout_tests(false)
.generate()

View File

@ -251,10 +251,12 @@ fn chacha20_poly1305_decrypt(
// XChaCha20-Poly1305 (24-byte nonce)
// ──────────────────────────────────────────────────────────────────────────────
//
// wolfCrypt does not expose a single XChaCha20-Poly1305 one-shot function.
// We derive a sub-key from the first 16 bytes of the nonce using HChaCha20,
// then apply ChaCha20-Poly1305 with the last 12 bytes for the nonce.
// This matches the XChaCha20-Poly1305 construction in RFC 8439 §2.
// wolfCrypt exposes `wc_XChaCha20Poly1305_Encrypt/Decrypt` as public one-shot
// functions that accept the 24-byte XChaCha nonce directly and handle the
// internal HChaCha20 sub-key derivation (RFC 8439 §2) themselves.
//
// Output layout: dst = ciphertext || 16-byte Poly1305 tag
// (dst_space = src_len + CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE = src_len + 16).
fn xchacha20_poly1305_encrypt(
key: &[u8],
@ -262,8 +264,32 @@ fn xchacha20_poly1305_encrypt(
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?;
chacha20_poly1305_encrypt(&subkey, &chacha_nonce, plaintext, aad)
// dst holds ciphertext (same length as plaintext) followed by the 16-byte tag.
let mut dst = vec![0u8; plaintext.len() + TAG_LEN];
unsafe {
// wc_XChaCha20Poly1305_Encrypt(dst, dst_space, src, src_len,
// ad, ad_len, nonce, nonce_len, key, key_len)
let ret = crate::sys::wc_XChaCha20Poly1305_Encrypt(
dst.as_mut_ptr(),
dst.len(),
plaintext.as_ptr(),
plaintext.len(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
aad.len(),
nonce.as_ptr(),
nonce.len(),
key.as_ptr(),
key.len(),
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_XChaCha20Poly1305_Encrypt returned {}", ret)
));
}
}
Ok(dst)
}
fn xchacha20_poly1305_decrypt(
@ -272,32 +298,33 @@ fn xchacha20_poly1305_decrypt(
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?;
chacha20_poly1305_decrypt(&subkey, &chacha_nonce, ciphertext_and_tag, aad)
}
/// Derive a 32-byte sub-key and a 12-byte ChaCha nonce from a 32-byte key
/// and a 24-byte XChaCha nonce, following RFC 8439 §2.
fn xchacha_derive_subkey(key: &[u8], nonce: &[u8]) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
debug_assert_eq!(nonce.len(), 24);
// HChaCha20 takes key[32] + nonce[0..16] → 32-byte subkey.
let mut subkey = vec![0u8; 32];
unsafe {
// wc_HChaCha20(output, key, nonce_16bytes) — available in wolfCrypt.
let ret = crate::sys::wc_HChaCha20(
subkey.as_mut_ptr() as *mut u32,
key.as_ptr() as *const u32,
nonce.as_ptr() as *const u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_HChaCha20 returned {}", ret)
if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput(
"XChaCha20-Poly1305: ciphertext too short".into()
));
}
}
// ChaCha nonce = [0u8 × 4] || nonce[16..24]
let mut chacha_nonce = vec![0u8; 12];
chacha_nonce[4..12].copy_from_slice(&nonce[16..24]);
let pt_len = ciphertext_and_tag.len() - TAG_LEN;
let mut dst = vec![0u8; pt_len];
Ok((subkey, chacha_nonce))
unsafe {
// wc_XChaCha20Poly1305_Decrypt(dst, dst_space, src, src_len,
// ad, ad_len, nonce, nonce_len, key, key_len)
let ret = crate::sys::wc_XChaCha20Poly1305_Decrypt(
dst.as_mut_ptr(),
dst.len(),
ciphertext_and_tag.as_ptr(),
ciphertext_and_tag.len(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
aad.len(),
nonce.as_ptr(),
nonce.len(),
key.as_ptr(),
key.len(),
);
if ret != 0 {
return Err(CryptoError::AuthenticationFailed);
}
}
Ok(dst)
}

View File

@ -43,16 +43,24 @@ fn sha256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
Ok(out)
}
/// SHA-384: `wc_Sha384` is `typedef struct wc_Sha512 wc_Sha384` in wolfSSL's sha512.h.
fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 48];
unsafe {
let ret = crate::sys::wc_Sha384Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
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_Sha384Hash 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}")));
}
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}")));
}
}
Ok(out)
@ -61,13 +69,20 @@ fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64];
unsafe {
let ret = crate::sys::wc_Sha512Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
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_Sha512Hash 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}")));
}
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}")));
}
}
Ok(out)
@ -77,17 +92,25 @@ fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
// SHA-3 family
// ──────────────────────────────────────────────────────────────────────────────
/// SHA3-256: wolfCrypt provides only init/update/final (no one-shot).
/// `wc_InitSha3_256(sha3, heap, devId)` — INVALID_DEVID = -2.
fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 32];
unsafe {
// wolfCrypt: wc_Sha3_256Hash(data, dataSz, digest)
let ret = crate::sys::wc_Sha3_256Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
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_Sha3_256Hash 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}")));
}
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}")));
}
}
Ok(out)
@ -96,13 +119,20 @@ fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64];
unsafe {
let ret = crate::sys::wc_Sha3_512Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
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_Sha3_512Hash 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}")));
}
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}")));
}
}
Ok(out)
@ -112,23 +142,26 @@ fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
// BLAKE2b-512
// ──────────────────────────────────────────────────────────────────────────────
fn blake2b_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64];
/// Unkeyed BLAKE2b-512 digest using wolfCrypt init / update / final.
///
/// For keyed BLAKE2b-MAC, see [`crate::mac`].
pub fn blake2b_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
const OUT_LEN: u32 = 64;
let mut out = vec![0u8; OUT_LEN as usize];
unsafe {
// wc_Blake2bHash(out, outLen, key, keyLen, data, dataSz)
// No key = unkeyed hash (pass NULL, 0).
let ret = crate::sys::wc_Blake2bHash(
out.as_mut_ptr(),
64u32,
std::ptr::null(),
0u32,
data.as_ptr(),
data.len() as u32,
);
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_Blake2bHash 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}")));
}
// 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}")));
}
}
Ok(out)

View File

@ -1,20 +1,40 @@
//! Key derivation function implementations via wolfCrypt.
//!
//! Covers HKDF (SHA-256 / SHA-384 / SHA-512), Argon2id, and BLAKE2b-based KDF.
//!
//! ## HKDF implementation
//!
//! wolfSSL v5.7.x does not expose a generic `wc_HKDF()` function. The only
//! available HKDF primitive is `wc_Tls13_HKDF_Extract` (RFC 5869 Extract step)
//! together with the `wc_Hmac*` family. We implement RFC 5869 HKDF in Rust:
//!
//! - **Extract**: `PRK = HMAC-Hash(salt, IKM)` via `wc_Tls13_HKDF_Extract`.
//! - **Expand**: `OKM = T(1)||T(2)||…` where
//! `T(i) = HMAC(PRK, T(i-1) || info || i)` computed with wolfSSL HMAC.
//!
//! ## Argon2id
//!
//! wolfSSL v5.7.x has no built-in Argon2 implementation, so we delegate to
//! the pure-Rust `argon2` crate (which is maintained by RustCrypto).
use zeroize::Zeroizing;
use ccc_crypto_core::{algorithms::KdfAlgorithm, error::CryptoError};
// ──────────────────────────────────────────────────────────────────────────────
// wolfCrypt hash type constants (passed to wc_HKDF)
// ──────────────────────────────────────────────────────────────────────────────
// wolfCrypt hash type constants from `enum wc_HashType` in types.h (non-FIPS).
const WC_HASH_TYPE_SHA256: i32 = 6;
const WC_HASH_TYPE_SHA384: i32 = 7;
const WC_HASH_TYPE_SHA512: i32 = 8;
/// wolfCrypt `wc_HashType` values for hash algorithms used in HKDF.
/// These match the `enum wc_HashType` values in `wolfssl/wolfcrypt/hash.h`.
const WC_HASH_TYPE_SHA256: i32 = 8;
const WC_HASH_TYPE_SHA384: i32 = 9;
const WC_HASH_TYPE_SHA512: i32 = 10;
/// Output byte lengths for each HMAC hash variant, matching the digest size.
fn hash_output_len(hash_type: i32) -> usize {
match hash_type {
WC_HASH_TYPE_SHA256 => 32,
WC_HASH_TYPE_SHA384 => 48,
WC_HASH_TYPE_SHA512 => 64,
_ => 32,
}
}
// ──────────────────────────────────────────────────────────────────────────────
// Public entry point
@ -40,12 +60,13 @@ pub fn derive_key(
}
// ──────────────────────────────────────────────────────────────────────────────
// HKDF (RFC 5869)
// HKDF (RFC 5869) using wolfCrypt primitives
// ──────────────────────────────────────────────────────────────────────────────
/// HKDF using wolfCrypt's `wc_HKDF()`.
/// HKDF: Extract then Expand (RFC 5869).
///
/// `wc_HKDF(type, ikm, ikmSz, salt, saltSz, info, infoSz, okm, okmSz)`
/// Extract uses `wc_Tls13_HKDF_Extract` (wolfSSL's RFC 5869 Extract).
/// Expand is implemented in Rust using wolfSSL's HMAC primitives.
fn hkdf(
hash_type: i32,
ikm: &[u8],
@ -53,26 +74,94 @@ fn hkdf(
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let mut out = Zeroizing::new(vec![0u8; length]);
// ── Extract ──────────────────────────────────────────────────────────────
let hash_len = hash_output_len(hash_type);
// Use zero-valued salt if none is provided (RFC 5869 §2.2).
let default_salt = vec![0u8; hash_len];
let effective_salt = if salt.is_empty() { &default_salt } else { salt };
let prk = hkdf_extract(hash_type, effective_salt, ikm)?;
// ── Expand ───────────────────────────────────────────────────────────────
hkdf_expand(hash_type, &prk, info, length)
}
/// HKDF-Extract: `PRK = HMAC-Hash(salt, IKM)`.
///
/// Wraps `wc_Tls13_HKDF_Extract` which takes a mutable IKM pointer.
fn hkdf_extract(
hash_type: i32,
salt: &[u8],
ikm: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let hash_len = hash_output_len(hash_type);
let mut prk = Zeroizing::new(vec![0u8; hash_len]);
// wolfSSL's wc_Tls13_HKDF_Extract takes `byte* ikm` (mutable), so we
// make a writable copy.
let mut ikm_buf = ikm.to_vec();
unsafe {
let ret = crate::sys::wc_HKDF(
hash_type,
ikm.as_ptr(),
ikm.len() as u32,
let ret = crate::sys::wc_Tls13_HKDF_Extract(
prk.as_mut_ptr(),
if salt.is_empty() { std::ptr::null() } else { salt.as_ptr() },
salt.len() as u32,
if info.is_empty() { std::ptr::null() } else { info.as_ptr() },
info.len() as u32,
out.as_mut_ptr(),
length as u32,
ikm_buf.as_mut_ptr(),
ikm_buf.len() as u32,
hash_type,
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_HKDF returned {}", ret)));
return Err(CryptoError::InternalError(
format!("wc_Tls13_HKDF_Extract returned {ret}")
));
}
}
Ok(prk)
}
/// HKDF-Expand: `OKM = T(1) || T(2) || …` truncated to `length` bytes.
///
/// `T(i) = HMAC(PRK, T(i-1) || info || i)` per RFC 5869 §2.3.
/// Uses wolfSSL HMAC via `crate::mac::hmac`.
fn hkdf_expand(
hash_type: i32,
prk: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let hash_len = hash_output_len(hash_type);
// 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}")
));
}
Ok(out)
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 {
// Build the HMAC input: T(i-1) || info || counter
let mut input = Vec::with_capacity(t.len() + info.len() + 1);
input.extend_from_slice(&t);
input.extend_from_slice(info);
input.push(counter);
// T(i) = HMAC-Hash(PRK, input)
t = crate::mac::hmac(hash_type, prk, &input, hash_len)?;
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())
})?;
}
Ok(okm)
}
// ──────────────────────────────────────────────────────────────────────────────
@ -118,50 +207,44 @@ fn blake2b_kdf(
// Argon2id
// ──────────────────────────────────────────────────────────────────────────────
/// Argon2id KDF via wolfCrypt's `wc_Argon2()`.
/// Argon2id KDF via the pure-Rust `argon2` crate.
///
/// wolfSSL v5.7.x has no built-in Argon2 implementation.
///
/// Uses the memory and iteration parameters from `DEFAULT_CIPHER_PARAMS` in
/// `cipher_constants.dart`: 64 MB memory, 4 threads, 3 iterations.
/// `cipher_constants.dart`: 64 MiB memory, 3 iterations, 4 lanes.
fn argon2id(
password: &[u8], // treated as ikm / password
password: &[u8],
salt: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
// Argon2id requires at least 8 bytes of salt.
// Argon2 requires at least 8 bytes of salt.
if salt.len() < 8 {
return Err(CryptoError::InvalidInput(
"Argon2id requires at least 8-byte salt".into()
));
}
let mut out = Zeroizing::new(vec![0u8; length]);
use argon2::{Algorithm, Argon2, Params, Version};
unsafe {
// wolfCrypt Argon2:
// typedef struct Argon2_t { ... } Argon2;
// int wc_Argon2Hash(Argon2* arg2, const byte* pwd, word32 pwdSz,
// const byte* salt, word32 saltSz, byte* out, word32 outSz)
//
// We use the simplified wc_Argon2id_Hash wrapper (available in wolfCrypt ≥ 5.5).
let ret = crate::sys::wc_Argon2id_Hash(
out.as_mut_ptr(),
length as u32,
password.as_ptr(),
password.len() as u32,
salt.as_ptr(),
salt.len() as u32,
std::ptr::null(), // secret
0u32, // secretLen
std::ptr::null(), // additional data
0u32, // additional data len
64 * 1024, // memory kb (64 MB, matches DEFAULT_CIPHER_PARAMS)
3u32, // iterations (matches DEFAULT_CIPHER_PARAMS)
4u32, // parallelism (matches DEFAULT_CIPHER_PARAMS: 4 cores)
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Argon2id_Hash returned {}", ret)));
}
}
// Match DEFAULT_CIPHER_PARAMS in cipher_constants.dart:
// argon2_memory_kb: 65536 (64 MiB)
// 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
)
.map_err(|e| CryptoError::InternalError(format!("Argon2 params error: {e}")))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut out = Zeroizing::new(vec![0u8; length]);
argon2
.hash_password_into(password, salt, &mut out)
.map_err(|e| CryptoError::InternalError(format!("Argon2id hash error: {e}")))?;
Ok(out)
}

View File

@ -42,7 +42,7 @@ mod sys {
//! feature is only used for type-checking and documentation builds.
#![allow(non_camel_case_types, dead_code, unused_variables)]
use std::os::raw::{c_int, c_uchar, c_uint};
use std::os::raw::{c_int, c_uchar, c_uint, c_void};
// ── Opaque C struct stubs ────────────────────────────────────────────
@ -58,6 +58,18 @@ mod sys {
#[repr(C)]
pub struct WC_RNG([u8; 256]);
/// Stub for wolfCrypt `wc_Sha512` struct (also used as `wc_Sha384`).
#[repr(C)]
pub struct wc_Sha512([u8; 512]);
/// Stub for wolfCrypt `wc_Sha3` struct.
#[repr(C)]
pub struct wc_Sha3([u8; 512]);
/// Stub for wolfCrypt `Blake2b` struct.
#[repr(C)]
pub struct Blake2b([u8; 512]);
/// Stub for wolfCrypt `curve25519_key` struct.
#[repr(C)]
pub struct curve25519_key([u8; 256]);
@ -79,6 +91,8 @@ mod sys {
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 ────────────────────────────────────────────────
@ -94,41 +108,63 @@ mod sys {
in_: *const c_uchar, in_sz: c_uint,
auth_tag: *const c_uchar, out: *mut c_uchar,
) -> c_int { unreachable!() }
// ── HChaCha20 (for XChaCha20 sub-key) ───────────────────────────────
// wc_HChaCha20(out: *mut u32, key: *const u32, nonce_16: *const u32) -> c_int
pub unsafe fn wc_HChaCha20(
out: *mut std::os::raw::c_uint,
key: *const std::os::raw::c_uint,
nonce: *const std::os::raw::c_uint,
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!() }
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!() }
// ── KDF ─────────────────────────────────────────────────────────────
// ── SHA-256 (one-shot available) ─────────────────────────────────────
pub unsafe fn wc_HKDF(
type_: c_int,
ikm: *const c_uchar, ikm_sz: c_uint,
salt: *const c_uchar, salt_sz: c_uint,
info: *const c_uchar, info_sz: c_uint,
out: *mut c_uchar, out_sz: c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_Argon2id_Hash(
out: *mut c_uchar, out_sz: c_uint,
pwd: *const c_uchar, pwd_sz: c_uint,
salt: *const c_uchar, salt_sz: c_uint,
secret: *const c_uchar, secret_sz: c_uint,
ad: *const c_uchar, ad_sz: c_uint,
mem_kb: c_uint, iterations: c_uint, parallelism: c_uint,
) -> c_int { unreachable!() }
// ── BLAKE2b hash / MAC ───────────────────────────────────────────────
pub unsafe fn wc_Blake2bHash(
out: *mut c_uchar, out_sz: c_uint,
in_: *const c_uchar, in_sz: c_uint,
key: *const c_uchar, key_sz: c_uint, // key_sz = 0 for unkeyed
pub unsafe fn wc_Sha256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
// ── SHA-384 (init/update/final) ──────────────────────────────────────
// wc_Sha384 is typedef wc_Sha512 in wolfSSL; bindgen exposes wc_Sha512.
pub unsafe fn wc_InitSha384(sha: *mut wc_Sha512) -> c_int { unreachable!() }
pub unsafe fn wc_Sha384Update(sha: *mut wc_Sha512, data: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_Sha384Final(sha: *mut wc_Sha512, hash: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha384Free(sha: *mut wc_Sha512) { unreachable!() }
// ── SHA-512 (init/update/final) ──────────────────────────────────────
pub unsafe fn wc_InitSha512(sha: *mut wc_Sha512) -> c_int { unreachable!() }
pub unsafe fn wc_Sha512Update(sha: *mut wc_Sha512, data: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_Sha512Final(sha: *mut wc_Sha512, hash: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha512Free(sha: *mut wc_Sha512) { unreachable!() }
// ── SHA3-256 (init/update/final) ─────────────────────────────────────
pub unsafe fn wc_InitSha3_256(sha: *mut wc_Sha3, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_256_Update(sha: *mut wc_Sha3, data: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_256_Final(sha: *mut wc_Sha3, hash: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_256_Free(sha: *mut wc_Sha3) { unreachable!() }
// ── SHA3-512 (init/update/final) ─────────────────────────────────────
pub unsafe fn wc_InitSha3_512(sha: *mut wc_Sha3, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_512_Update(sha: *mut wc_Sha3, data: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_512_Final(sha: *mut wc_Sha3, hash: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_512_Free(sha: *mut wc_Sha3) { unreachable!() }
// ── BLAKE2b (init/update/final) ──────────────────────────────────────
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!() }
// ── HMAC ────────────────────────────────────────────────────────────
@ -137,22 +173,25 @@ mod sys {
pub unsafe fn wc_HmacFinal(hmac: *mut Hmac, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFree(hmac: *mut Hmac) { unreachable!() }
// ── One-shot hashes ──────────────────────────────────────────────────
// ── HKDF (TLS-1.3 Extract — RFC 5869 Extract step) ──────────────────
pub unsafe fn wc_Sha256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha384Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha512Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_512Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
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,
digest: c_int,
) -> 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!() }
// ── 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_export_key_raw(
key: *mut curve25519_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint,
@ -173,6 +212,7 @@ mod sys {
// ── 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_export_key_raw(
key: *mut curve448_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint,

View File

@ -6,10 +6,11 @@
use ccc_crypto_core::{algorithms::MacAlgorithm, error::CryptoError};
// wolfCrypt hash type constants matching `enum wc_HashType` in hash.h.
const WC_HASH_TYPE_SHA256: i32 = 8;
const WC_HASH_TYPE_SHA384: i32 = 9;
const WC_HASH_TYPE_SHA512: i32 = 10;
// wolfCrypt hash type constants from `enum wc_HashType` in types.h (non-FIPS build).
// WC_HASH_TYPE_SHA256 = 6, WC_HASH_TYPE_SHA384 = 7, WC_HASH_TYPE_SHA512 = 8.
const WC_HASH_TYPE_SHA256: i32 = 6;
const WC_HASH_TYPE_SHA384: i32 = 7;
const WC_HASH_TYPE_SHA512: i32 = 8;
// ──────────────────────────────────────────────────────────────────────────────
// Public entry points
@ -52,7 +53,11 @@ pub fn verify_mac(
// ──────────────────────────────────────────────────────────────────────────────
/// HMAC using wolfCrypt's `wc_HmacSetKey` / `wc_HmacUpdate` / `wc_HmacFinal`.
fn hmac(
///
/// `hash_type` must be a `WC_HASH_TYPE_*` constant; `out_len` must match the
/// hash output size. This function is `pub(crate)` so that the KDF module can
/// call it directly for RFC 5869 HKDF-Expand without duplicating HMAC logic.
pub(crate) fn hmac(
hash_type: i32,
key: &[u8],
data: &[u8],
@ -104,6 +109,8 @@ fn hmac(
///
/// Key length must be 164 bytes (wolfCrypt limitation).
/// Output is always 64 bytes (BLAKE2b-512 full digest length).
///
/// Uses `wc_InitBlake2b_WithKey` → `wc_Blake2bUpdate` → `wc_Blake2bFinal`.
fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
if key.is_empty() || key.len() > 64 {
return Err(CryptoError::InvalidKey(
@ -111,22 +118,36 @@ fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
));
}
let mut out = vec![0u8; 64];
const OUT_LEN: u32 = 64;
let mut out = vec![0u8; OUT_LEN as usize];
unsafe {
// wolfCrypt: wc_Blake2b(blake2b, out, outLen, key, keyLen, data, dataSz)
// Using the simplified one-shot Blake2bHash wrapper.
let ret = crate::sys::wc_Blake2bHash(
out.as_mut_ptr(),
64u32,
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,
data.as_ptr(),
data.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_Blake2bHash returned {}", ret)
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}")
));
}
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}")
));
}
}

View File

@ -13,9 +13,11 @@ use zeroize::Zeroizing;
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
registry::ProviderRegistry,
};
// Provider traits are imported for use in future dispatch methods.
#[allow(unused_imports)]
use ccc_crypto_core::provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider};
use super::dto::{
AeadEncryptRequest, AeadEncryptResult, AlgorithmCapabilityDto, HashRequest, KdfDeriveRequest,