From 3b226d0319a5ddb34ae733570c7bb1d540261ea7 Mon Sep 17 00:00:00 2001 From: JohnE Date: Mon, 23 Feb 2026 18:01:22 -0800 Subject: [PATCH] FIX: lots of mis-match between what's in the generated bindings and what our code calls with wolfcrypt --- Cargo.lock | 62 ++++++++ crates/ccc-crypto-wolfssl/Cargo.toml | 2 + crates/ccc-crypto-wolfssl/build.rs | 196 +++++++++++++++++------ crates/ccc-crypto-wolfssl/src/aead.rs | 81 ++++++---- crates/ccc-crypto-wolfssl/src/hash.rs | 113 +++++++++----- crates/ccc-crypto-wolfssl/src/kdf.rs | 199 +++++++++++++++++------- crates/ccc-crypto-wolfssl/src/lib.rs | 116 +++++++++----- crates/ccc-crypto-wolfssl/src/mac.rs | 49 ++++-- crates/ccc-flutter-bridge/src/bridge.rs | 4 +- 9 files changed, 598 insertions(+), 224 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45b91a7..ad22847 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/crates/ccc-crypto-wolfssl/Cargo.toml b/crates/ccc-crypto-wolfssl/Cargo.toml index a390a37..4de3055 100644 --- a/crates/ccc-crypto-wolfssl/Cargo.toml +++ b/crates/ccc-crypto-wolfssl/Cargo.toml @@ -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. diff --git a/crates/ccc-crypto-wolfssl/build.rs b/crates/ccc-crypto-wolfssl/build.rs index b11d333..8b36588 100644 --- a/crates/ccc-crypto-wolfssl/build.rs +++ b/crates/ccc-crypto-wolfssl/build.rs @@ -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() diff --git a/crates/ccc-crypto-wolfssl/src/aead.rs b/crates/ccc-crypto-wolfssl/src/aead.rs index 61833ce..a5a413d 100644 --- a/crates/ccc-crypto-wolfssl/src/aead.rs +++ b/crates/ccc-crypto-wolfssl/src/aead.rs @@ -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, 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, CryptoError> { - let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?; - chacha20_poly1305_decrypt(&subkey, &chacha_nonce, ciphertext_and_tag, aad) -} + if ciphertext_and_tag.len() < TAG_LEN { + return Err(CryptoError::InvalidInput( + "XChaCha20-Poly1305: ciphertext too short".into() + )); + } + let pt_len = ciphertext_and_tag.len() - TAG_LEN; + let mut dst = vec![0u8; pt_len]; -/// 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, Vec), 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, + // 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::InternalError( - format!("wc_HChaCha20 returned {}", ret) - )); + return Err(CryptoError::AuthenticationFailed); } } - // ChaCha nonce = [0u8 × 4] || nonce[16..24] - let mut chacha_nonce = vec![0u8; 12]; - chacha_nonce[4..12].copy_from_slice(&nonce[16..24]); - Ok((subkey, chacha_nonce)) + Ok(dst) } diff --git a/crates/ccc-crypto-wolfssl/src/hash.rs b/crates/ccc-crypto-wolfssl/src/hash.rs index 4754c5e..cea0179 100644 --- a/crates/ccc-crypto-wolfssl/src/hash.rs +++ b/crates/ccc-crypto-wolfssl/src/hash.rs @@ -43,16 +43,24 @@ fn sha256(data: &[u8]) -> Result, CryptoError> { Ok(out) } +/// SHA-384: `wc_Sha384` is `typedef struct wc_Sha512 wc_Sha384` in wolfSSL's sha512.h. fn sha384(data: &[u8]) -> Result, 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, CryptoError> { fn sha512(data: &[u8]) -> Result, 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, 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, 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, CryptoError> { fn sha3_512(data: &[u8]) -> Result, 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, CryptoError> { // BLAKE2b-512 // ────────────────────────────────────────────────────────────────────────────── -fn blake2b_512(data: &[u8]) -> Result, 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, 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) diff --git a/crates/ccc-crypto-wolfssl/src/kdf.rs b/crates/ccc-crypto-wolfssl/src/kdf.rs index 3f64704..93accc9 100644 --- a/crates/ccc-crypto-wolfssl/src/kdf.rs +++ b/crates/ccc-crypto-wolfssl/src/kdf.rs @@ -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 @@ -29,23 +49,24 @@ pub fn derive_key( length: usize, ) -> Result>, CryptoError> { match algo { - KdfAlgorithm::Sha256 => hkdf(WC_HASH_TYPE_SHA256, ikm, salt, info, length), - KdfAlgorithm::Sha384 => hkdf(WC_HASH_TYPE_SHA384, ikm, salt, info, length), - KdfAlgorithm::Sha512 => hkdf(WC_HASH_TYPE_SHA512, ikm, salt, info, length), + KdfAlgorithm::Sha256 => hkdf(WC_HASH_TYPE_SHA256, ikm, salt, info, length), + KdfAlgorithm::Sha384 => hkdf(WC_HASH_TYPE_SHA384, ikm, salt, info, length), + 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 => + KdfAlgorithm::Argon2id => argon2id(ikm, salt, length), + KdfAlgorithm::Kmac256 => Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())), } } // ────────────────────────────────────────────────────────────────────────────── -// 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>, 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>, 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) +} - Ok(out) +/// 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>, 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}") + )); + } + + 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>, 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) } diff --git a/crates/ccc-crypto-wolfssl/src/lib.rs b/crates/ccc-crypto-wolfssl/src/lib.rs index 393a530..da2775f 100644 --- a/crates/ccc-crypto-wolfssl/src/lib.rs +++ b/crates/ccc-crypto-wolfssl/src/lib.rs @@ -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, diff --git a/crates/ccc-crypto-wolfssl/src/mac.rs b/crates/ccc-crypto-wolfssl/src/mac.rs index e31ea6a..ab77905 100644 --- a/crates/ccc-crypto-wolfssl/src/mac.rs +++ b/crates/ccc-crypto-wolfssl/src/mac.rs @@ -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 1–64 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, CryptoError> { if key.is_empty() || key.len() > 64 { return Err(CryptoError::InvalidKey( @@ -111,22 +118,36 @@ fn blake2b_mac(key: &[u8], data: &[u8]) -> Result, 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}") )); } } diff --git a/crates/ccc-flutter-bridge/src/bridge.rs b/crates/ccc-flutter-bridge/src/bridge.rs index b3761b2..fa905da 100644 --- a/crates/ccc-flutter-bridge/src/bridge.rs +++ b/crates/ccc-flutter-bridge/src/bridge.rs @@ -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,