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

View File

@ -11,6 +11,8 @@ ccc-crypto-core.workspace = true
zeroize.workspace = true zeroize.workspace = true
thiserror.workspace = true thiserror.workspace = true
log.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] [build-dependencies]
# cmake crate drives the wolfSSL CMake build from source. # 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)`. /// 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!( println!(
"cargo:warning=Building wolfSSL from source at {}", "cargo:warning=Building wolfSSL from source at {}",
source_dir.display() 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); let mut cfg = cmake::Config::new(source_dir);
// ── Core algorithm selection ───────────────────────────────────────────── // ── Core algorithm selection ─────────────────────────────────────────────
// Enable exactly the algorithms the CCC system needs in Phase 4. // wolfSSL cmake uses add_option() with "yes"/"no" string values (not ON/OFF).
// Additional algorithms can be enabled here for Phase 5+ PQ support. // 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 cfg
// Build as a static library. // Build as a static library.
.define("BUILD_SHARED_LIBS", "OFF") .define("BUILD_SHARED_LIBS", "OFF")
.define("WOLFSSL_BUILD_SHARED_LIBS", "OFF") // wolfCrypt-only — no TLS stack. This is the correct cmake variable.
// Disable TLS/SSL stack — we only need the wolfCrypt algorithms. .define("WOLFSSL_CRYPT_ONLY", "yes")
.define("WOLFSSL_NO_TLS", "ON") // AEAD (WOLFSSL_AESGCM — note: no underscore between AES and GCM)
// AEAD .define("WOLFSSL_AESGCM", "yes")
.define("WOLFSSL_AES_GCM", "ON") .define("WOLFSSL_CHACHA", "yes")
.define("WOLFSSL_CHACHA", "ON") .define("WOLFSSL_POLY1305", "yes")
.define("WOLFSSL_POLY1305", "ON")
// Hash // Hash
.define("WOLFSSL_SHA224", "ON") .define("WOLFSSL_SHA384", "yes")
.define("WOLFSSL_SHA384", "ON") .define("WOLFSSL_SHA512", "yes")
.define("WOLFSSL_SHA512", "ON") .define("WOLFSSL_SHA3", "yes")
.define("WOLFSSL_SHA3", "ON") // BLAKE2b/BLAKE2s — not a cmake option; enabled via C preprocessor flag.
.define("WOLFSSL_BLAKE2", "ON") // Added to both cmake C flags and bindgen clang args below.
// KDF // KDF (HKDF via TLS-1.3 HKDF primitives, PBKDF2)
.define("WOLFSSL_HKDF", "ON") .define("WOLFSSL_HKDF", "yes")
.define("WOLFSSL_PWDBASED", "ON") // PBKDF2, Argon2 .define("WOLFSSL_PWDBASED", "yes")
// MAC // MAC
.define("WOLFSSL_HMAC", "ON") .define("WOLFSSL_HMAC", "yes")
// Asymmetric (needed for X25519 ratchet) // Asymmetric (X25519 / X448 DH for ratchet)
.define("WOLFSSL_CURVE25519", "ON") .define("WOLFSSL_CURVE25519", "yes")
.define("WOLFSSL_CURVE448", "ON") .define("WOLFSSL_CURVE448", "yes")
// RNG (needed internally) // RNG
.define("WOLFSSL_RNG", "ON") .define("WOLFSSL_RNG", "yes")
// Minimise binary size. // Minimise binary size.
.define("WOLFSSL_CRYPTONLY", "ON")
.define("WOLFSSL_MIN_RSA_BITS", "2048") .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(); 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/aes.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/chacha20_poly1305.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/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/sha3.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/blake2.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/hmac.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/kdf.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/curve25519.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/curve448.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()) .header(include_dir.join("wolfssl/wolfcrypt/random.h").to_str().unwrap())
.clang_arg(format!("-I{}", include_dir.display())) .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_Aes.*")
.allowlist_function("wc_AesGcm.*")
.allowlist_function("wc_AesInit")
.allowlist_function("wc_AesFree")
.allowlist_function("wc_ChaCha20Poly1305.*") .allowlist_function("wc_ChaCha20Poly1305.*")
.allowlist_function("wc_Sha.*") .allowlist_function("wc_XChaCha20Poly1305.*")
.allowlist_function("wc_Blake2.*") // SHA-256 (one-shot available)
.allowlist_function("wc_Hmac.*") .allowlist_function("wc_Sha256Hash")
.allowlist_function("wc_HKDF.*") .allowlist_function("wc_Sha256.*")
.allowlist_function("wc_Pbkdf2.*") // SHA-384 (init/update/final — typedef of wc_Sha512 struct)
.allowlist_function("wc_Argon2.*") .allowlist_function("wc_InitSha384")
.allowlist_function("wc_curve25519.*") .allowlist_function("wc_Sha384Update")
.allowlist_function("wc_curve448.*") .allowlist_function("wc_Sha384Final")
.allowlist_function("wc_InitRng.*") .allowlist_function("wc_Sha384Free")
.allowlist_function("wc_RNG.*") // SHA-512 (init/update/final)
.allowlist_function("wc_FreeRng.*") .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("Aes")
.allowlist_type("Hmac") .allowlist_type("Hmac")
.allowlist_type("WC_RNG") .allowlist_type("WC_RNG")
.allowlist_type("curve25519.*") .allowlist_type("OS_Seed")
.allowlist_type("curve448.*") .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("AES_.*")
.allowlist_var("WC_.*") .allowlist_var("WC_SHA.*")
.allowlist_var("SHA.*") .allowlist_var("WC_HASH.*")
.allowlist_var("BLAKE2.*") .allowlist_var("CHACHA20_POLY1305.*")
// Silence warnings for types we don't control. // Only block specific C internal types that cause noise.
.blocklist_type("__.*") // 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) .derive_debug(false)
.layout_tests(false) .layout_tests(false)
.generate() .generate()

View File

@ -251,10 +251,12 @@ fn chacha20_poly1305_decrypt(
// XChaCha20-Poly1305 (24-byte nonce) // XChaCha20-Poly1305 (24-byte nonce)
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
// //
// wolfCrypt does not expose a single XChaCha20-Poly1305 one-shot function. // wolfCrypt exposes `wc_XChaCha20Poly1305_Encrypt/Decrypt` as public one-shot
// We derive a sub-key from the first 16 bytes of the nonce using HChaCha20, // functions that accept the 24-byte XChaCha nonce directly and handle the
// then apply ChaCha20-Poly1305 with the last 12 bytes for the nonce. // internal HChaCha20 sub-key derivation (RFC 8439 §2) themselves.
// This matches the XChaCha20-Poly1305 construction in RFC 8439 §2. //
// Output layout: dst = ciphertext || 16-byte Poly1305 tag
// (dst_space = src_len + CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE = src_len + 16).
fn xchacha20_poly1305_encrypt( fn xchacha20_poly1305_encrypt(
key: &[u8], key: &[u8],
@ -262,8 +264,32 @@ fn xchacha20_poly1305_encrypt(
plaintext: &[u8], plaintext: &[u8],
aad: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?; // dst holds ciphertext (same length as plaintext) followed by the 16-byte tag.
chacha20_poly1305_encrypt(&subkey, &chacha_nonce, plaintext, aad) 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( fn xchacha20_poly1305_decrypt(
@ -272,32 +298,33 @@ fn xchacha20_poly1305_decrypt(
ciphertext_and_tag: &[u8], ciphertext_and_tag: &[u8],
aad: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?; if ciphertext_and_tag.len() < TAG_LEN {
chacha20_poly1305_decrypt(&subkey, &chacha_nonce, ciphertext_and_tag, aad) return Err(CryptoError::InvalidInput(
} "XChaCha20-Poly1305: ciphertext too short".into()
/// 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)
)); ));
} }
} let pt_len = ciphertext_and_tag.len() - TAG_LEN;
// ChaCha nonce = [0u8 × 4] || nonce[16..24] let mut dst = vec![0u8; pt_len];
let mut chacha_nonce = vec![0u8; 12];
chacha_nonce[4..12].copy_from_slice(&nonce[16..24]);
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) 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> { fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 48]; let mut out = vec![0u8; 48];
unsafe { unsafe {
let ret = crate::sys::wc_Sha384Hash( let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed();
data.as_ptr(), let ret = crate::sys::wc_InitSha384(&mut sha);
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 { 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) Ok(out)
@ -61,13 +69,20 @@ fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> { fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64]; let mut out = vec![0u8; 64];
unsafe { unsafe {
let ret = crate::sys::wc_Sha512Hash( let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed();
data.as_ptr(), let ret = crate::sys::wc_InitSha512(&mut sha);
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 { 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) Ok(out)
@ -77,17 +92,25 @@ fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
// SHA-3 family // 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> { fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 32]; let mut out = vec![0u8; 32];
unsafe { unsafe {
// wolfCrypt: wc_Sha3_256Hash(data, dataSz, digest) let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed();
let ret = crate::sys::wc_Sha3_256Hash( let ret = crate::sys::wc_InitSha3_256(&mut sha, std::ptr::null_mut(), -2);
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 { 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) Ok(out)
@ -96,13 +119,20 @@ fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> { fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64]; let mut out = vec![0u8; 64];
unsafe { unsafe {
let ret = crate::sys::wc_Sha3_512Hash( let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed();
data.as_ptr(), let ret = crate::sys::wc_InitSha3_512(&mut sha, std::ptr::null_mut(), -2);
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 { 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) Ok(out)
@ -112,23 +142,26 @@ fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
// BLAKE2b-512 // BLAKE2b-512
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
fn blake2b_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> { /// Unkeyed BLAKE2b-512 digest using wolfCrypt init / update / final.
let mut out = vec![0u8; 64]; ///
/// 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 { unsafe {
// wc_Blake2bHash(out, outLen, key, keyLen, data, dataSz) let mut b2b: crate::sys::Blake2b = std::mem::zeroed();
// No key = unkeyed hash (pass NULL, 0). let ret = crate::sys::wc_InitBlake2b(&mut b2b, OUT_LEN);
let ret = crate::sys::wc_Blake2bHash(
out.as_mut_ptr(),
64u32,
std::ptr::null(),
0u32,
data.as_ptr(),
data.len() as u32,
);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!("wc_InitBlake2b returned {ret}")));
format!("wc_Blake2bHash 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) Ok(out)

View File

@ -1,20 +1,40 @@
//! Key derivation function implementations via wolfCrypt. //! Key derivation function implementations via wolfCrypt.
//! //!
//! Covers HKDF (SHA-256 / SHA-384 / SHA-512), Argon2id, and BLAKE2b-based KDF. //! 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 zeroize::Zeroizing;
use ccc_crypto_core::{algorithms::KdfAlgorithm, error::CryptoError}; use ccc_crypto_core::{algorithms::KdfAlgorithm, error::CryptoError};
// ────────────────────────────────────────────────────────────────────────────── // wolfCrypt hash type constants from `enum wc_HashType` in types.h (non-FIPS).
// wolfCrypt hash type constants (passed to wc_HKDF) 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. /// Output byte lengths for each HMAC hash variant, matching the digest size.
/// These match the `enum wc_HashType` values in `wolfssl/wolfcrypt/hash.h`. fn hash_output_len(hash_type: i32) -> usize {
const WC_HASH_TYPE_SHA256: i32 = 8; match hash_type {
const WC_HASH_TYPE_SHA384: i32 = 9; WC_HASH_TYPE_SHA256 => 32,
const WC_HASH_TYPE_SHA512: i32 = 10; WC_HASH_TYPE_SHA384 => 48,
WC_HASH_TYPE_SHA512 => 64,
_ => 32,
}
}
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
// Public entry point // 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( fn hkdf(
hash_type: i32, hash_type: i32,
ikm: &[u8], ikm: &[u8],
@ -53,26 +74,94 @@ fn hkdf(
info: &[u8], info: &[u8],
length: usize, length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> { ) -> 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 { unsafe {
let ret = crate::sys::wc_HKDF( let ret = crate::sys::wc_Tls13_HKDF_Extract(
hash_type, prk.as_mut_ptr(),
ikm.as_ptr(),
ikm.len() as u32,
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, salt.len() as u32,
if info.is_empty() { std::ptr::null() } else { info.as_ptr() }, ikm_buf.as_mut_ptr(),
info.len() as u32, ikm_buf.len() as u32,
out.as_mut_ptr(), hash_type,
length as u32,
); );
if ret != 0 { 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
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
/// 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 /// 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( fn argon2id(
password: &[u8], // treated as ikm / password password: &[u8],
salt: &[u8], salt: &[u8],
length: usize, length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> { ) -> 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 { if salt.len() < 8 {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(
"Argon2id requires at least 8-byte salt".into() "Argon2id requires at least 8-byte salt".into()
)); ));
} }
let mut out = Zeroizing::new(vec![0u8; length]); use argon2::{Algorithm, Argon2, Params, Version};
unsafe { // Match DEFAULT_CIPHER_PARAMS in cipher_constants.dart:
// wolfCrypt Argon2: // argon2_memory_kb: 65536 (64 MiB)
// typedef struct Argon2_t { ... } Argon2; // argon2_iterations: 3
// int wc_Argon2Hash(Argon2* arg2, const byte* pwd, word32 pwdSz, // argon2_parallelism: 4
// const byte* salt, word32 saltSz, byte* out, word32 outSz) let params = Params::new(
// 65_536, // m_cost: 64 MiB
// We use the simplified wc_Argon2id_Hash wrapper (available in wolfCrypt ≥ 5.5). 3, // t_cost: iterations
let ret = crate::sys::wc_Argon2id_Hash( 4, // p_cost: parallelism (lanes)
out.as_mut_ptr(), Some(length), // output length
length as u32, )
password.as_ptr(), .map_err(|e| CryptoError::InternalError(format!("Argon2 params error: {e}")))?;
password.len() as u32,
salt.as_ptr(), let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
salt.len() as u32,
std::ptr::null(), // secret let mut out = Zeroizing::new(vec![0u8; length]);
0u32, // secretLen argon2
std::ptr::null(), // additional data .hash_password_into(password, salt, &mut out)
0u32, // additional data len .map_err(|e| CryptoError::InternalError(format!("Argon2id hash error: {e}")))?;
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)));
}
}
Ok(out) Ok(out)
} }

View File

@ -42,7 +42,7 @@ mod sys {
//! feature is only used for type-checking and documentation builds. //! feature is only used for type-checking and documentation builds.
#![allow(non_camel_case_types, dead_code, unused_variables)] #![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 ──────────────────────────────────────────── // ── Opaque C struct stubs ────────────────────────────────────────────
@ -58,6 +58,18 @@ mod sys {
#[repr(C)] #[repr(C)]
pub struct WC_RNG([u8; 256]); 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. /// Stub for wolfCrypt `curve25519_key` struct.
#[repr(C)] #[repr(C)]
pub struct curve25519_key([u8; 256]); 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, 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, auth_in: *const c_uchar, auth_in_sz: c_uint,
) -> c_int { unreachable!() } ) -> 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 ──────────────────────────────────────────────── // ── ChaCha20-Poly1305 ────────────────────────────────────────────────
@ -94,41 +108,63 @@ mod sys {
in_: *const c_uchar, in_sz: c_uint, in_: *const c_uchar, in_sz: c_uint,
auth_tag: *const c_uchar, out: *mut c_uchar, auth_tag: *const c_uchar, out: *mut c_uchar,
) -> c_int { unreachable!() } ) -> c_int { unreachable!() }
pub unsafe fn wc_XChaCha20Poly1305_Encrypt(
// ── HChaCha20 (for XChaCha20 sub-key) ─────────────────────────────── dst: *mut c_uchar, dst_space: usize,
// wc_HChaCha20(out: *mut u32, key: *const u32, nonce_16: *const u32) -> c_int src: *const c_uchar, src_len: usize,
pub unsafe fn wc_HChaCha20( ad: *const c_uchar, ad_len: usize,
out: *mut std::os::raw::c_uint, nonce: *const c_uchar, nonce_len: usize,
key: *const std::os::raw::c_uint, key: *const c_uchar, key_len: usize,
nonce: *const std::os::raw::c_uint, ) -> 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!() } ) -> c_int { unreachable!() }
// ── KDF ───────────────────────────────────────────────────────────── // ── SHA-256 (one-shot available) ─────────────────────────────────────
pub unsafe fn wc_HKDF( pub unsafe fn wc_Sha256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
type_: c_int,
ikm: *const c_uchar, ikm_sz: c_uint, // ── SHA-384 (init/update/final) ──────────────────────────────────────
salt: *const c_uchar, salt_sz: c_uint, // wc_Sha384 is typedef wc_Sha512 in wolfSSL; bindgen exposes wc_Sha512.
info: *const c_uchar, info_sz: c_uint,
out: *mut c_uchar, out_sz: c_uint, pub unsafe fn wc_InitSha384(sha: *mut wc_Sha512) -> c_int { unreachable!() }
) -> 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_Argon2id_Hash( pub unsafe fn wc_Sha384Free(sha: *mut wc_Sha512) { unreachable!() }
out: *mut c_uchar, out_sz: c_uint,
pwd: *const c_uchar, pwd_sz: c_uint, // ── SHA-512 (init/update/final) ──────────────────────────────────────
salt: *const c_uchar, salt_sz: c_uint,
secret: *const c_uchar, secret_sz: c_uint, pub unsafe fn wc_InitSha512(sha: *mut wc_Sha512) -> c_int { unreachable!() }
ad: *const c_uchar, ad_sz: c_uint, pub unsafe fn wc_Sha512Update(sha: *mut wc_Sha512, data: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
mem_kb: c_uint, iterations: c_uint, parallelism: c_uint, pub unsafe fn wc_Sha512Final(sha: *mut wc_Sha512, hash: *mut c_uchar) -> c_int { unreachable!() }
) -> c_int { unreachable!() } pub unsafe fn wc_Sha512Free(sha: *mut wc_Sha512) { unreachable!() }
// ── BLAKE2b hash / MAC ─────────────────────────────────────────────── // ── SHA3-256 (init/update/final) ─────────────────────────────────────
pub unsafe fn wc_Blake2bHash( pub unsafe fn wc_InitSha3_256(sha: *mut wc_Sha3, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
out: *mut c_uchar, out_sz: c_uint, pub unsafe fn wc_Sha3_256_Update(sha: *mut wc_Sha3, data: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
in_: *const c_uchar, in_sz: c_uint, pub unsafe fn wc_Sha3_256_Final(sha: *mut wc_Sha3, hash: *mut c_uchar) -> c_int { unreachable!() }
key: *const c_uchar, key_sz: c_uint, // key_sz = 0 for unkeyed 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!() } ) -> 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 ──────────────────────────────────────────────────────────── // ── 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_HmacFinal(hmac: *mut Hmac, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFree(hmac: *mut Hmac) { 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_Tls13_HKDF_Extract(
pub unsafe fn wc_Sha384Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() } prk: *mut c_uchar,
pub unsafe fn wc_Sha512Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() } salt: *const c_uchar, salt_len: c_uint,
pub unsafe fn wc_Sha3_256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() } ikm: *mut c_uchar, ikm_len: c_uint,
pub unsafe fn wc_Sha3_512Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() } digest: c_int,
) -> c_int { unreachable!() }
// ── RNG ───────────────────────────────────────────────────────────── // ── RNG ─────────────────────────────────────────────────────────────
pub unsafe fn wc_InitRng(rng: *mut WC_RNG) -> c_int { unreachable!() } 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_FreeRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
// ── Curve25519 ─────────────────────────────────────────────────────── // ── 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_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( pub unsafe fn wc_curve25519_export_key_raw(
key: *mut curve25519_key, key: *mut curve25519_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint, priv_: *mut c_uchar, priv_sz: *mut c_uint,
@ -173,6 +212,7 @@ mod sys {
// ── Curve448 ───────────────────────────────────────────────────────── // ── 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_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( pub unsafe fn wc_curve448_export_key_raw(
key: *mut curve448_key, key: *mut curve448_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint, priv_: *mut c_uchar, priv_sz: *mut c_uint,

View File

@ -6,10 +6,11 @@
use ccc_crypto_core::{algorithms::MacAlgorithm, error::CryptoError}; use ccc_crypto_core::{algorithms::MacAlgorithm, error::CryptoError};
// wolfCrypt hash type constants matching `enum wc_HashType` in hash.h. // wolfCrypt hash type constants from `enum wc_HashType` in types.h (non-FIPS build).
const WC_HASH_TYPE_SHA256: i32 = 8; // WC_HASH_TYPE_SHA256 = 6, WC_HASH_TYPE_SHA384 = 7, WC_HASH_TYPE_SHA512 = 8.
const WC_HASH_TYPE_SHA384: i32 = 9; const WC_HASH_TYPE_SHA256: i32 = 6;
const WC_HASH_TYPE_SHA512: i32 = 10; const WC_HASH_TYPE_SHA384: i32 = 7;
const WC_HASH_TYPE_SHA512: i32 = 8;
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
// Public entry points // Public entry points
@ -52,7 +53,11 @@ pub fn verify_mac(
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
/// HMAC using wolfCrypt's `wc_HmacSetKey` / `wc_HmacUpdate` / `wc_HmacFinal`. /// 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, hash_type: i32,
key: &[u8], key: &[u8],
data: &[u8], data: &[u8],
@ -104,6 +109,8 @@ fn hmac(
/// ///
/// Key length must be 164 bytes (wolfCrypt limitation). /// Key length must be 164 bytes (wolfCrypt limitation).
/// Output is always 64 bytes (BLAKE2b-512 full digest length). /// 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> { fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
if key.is_empty() || key.len() > 64 { if key.is_empty() || key.len() > 64 {
return Err(CryptoError::InvalidKey( 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 { unsafe {
// wolfCrypt: wc_Blake2b(blake2b, out, outLen, key, keyLen, data, dataSz) let mut b2b: crate::sys::Blake2b = std::mem::zeroed();
// Using the simplified one-shot Blake2bHash wrapper.
let ret = crate::sys::wc_Blake2bHash( // Initialise with digest size and key for keyed (MAC) mode.
out.as_mut_ptr(), let ret = crate::sys::wc_InitBlake2b_WithKey(
64u32, &mut b2b,
OUT_LEN,
key.as_ptr(), key.as_ptr(),
key.len() as u32, key.len() as u32,
data.as_ptr(),
data.len() as u32,
); );
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( 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::{ use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm}, algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
registry::ProviderRegistry, 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::{ use super::dto::{
AeadEncryptRequest, AeadEncryptResult, AlgorithmCapabilityDto, HashRequest, KdfDeriveRequest, AeadEncryptRequest, AeadEncryptResult, AlgorithmCapabilityDto, HashRequest, KdfDeriveRequest,