lum_ccc_rust/crates/ccc-crypto-wolfssl/build.rs

313 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Build script for ccc-crypto-wolfssl.
//!
//! Responsibilities:
//! 1. Locate the wolfSSL source tree (vendored submodule).
//! 2. Build wolfSSL as a static library using CMake.
//! 3. Generate Rust FFI bindings to the wolfCrypt headers via bindgen.
//!
//! # Environment variables
//!
//! * `WOLFSSL_SOURCE_DIR` Override the default wolfSSL source path.
//! Useful for CI environments where the submodule may be at a non-standard
//! location. Defaults to `<workspace_root>/vendors/wolfssl`.
//! * `WOLFSSL_INSTALL_DIR` If set, skip the CMake build and link against
//! a pre-installed wolfSSL at this path. The path must contain
//! `include/wolfssl/` and `lib/libwolfssl.a`.
use std::{env, path::PathBuf};
fn main() {
// When the `stub_ffi` feature is enabled (e.g. for type-checking without
// the wolfSSL C library), skip all build steps and write an empty bindings
// file so the `include!()` in lib.rs still compiles.
if std::env::var("CARGO_FEATURE_STUB_FFI").is_ok() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let bindings_path = std::path::Path::new(&out_dir).join("wolfcrypt_bindings.rs");
std::fs::write(&bindings_path, "// stub — no wolfSSL headers\n").unwrap();
return;
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
// Workspace root is two levels up from crates/ccc-crypto-wolfssl/
let workspace_root = manifest_dir.join("../..").canonicalize().unwrap();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
// ── 1. Locate wolfSSL ────────────────────────────────────────────────────
let (include_dir, lib_dir) =
if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
// Use a pre-installed wolfSSL — skip the CMake build.
let p = PathBuf::from(&install_dir);
println!("cargo:warning=Using pre-installed wolfSSL at {}", install_dir);
(p.join("include"), p.join("lib"))
} else {
// Build from source (default — uses our git submodule).
let source_dir = env::var("WOLFSSL_SOURCE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_root.join("vendors/wolfssl"));
if !source_dir.join("CMakeLists.txt").exists() {
panic!(
"\n\nwolfSSL source not found at {}.\n\
Run `git submodule update --init --recursive` to fetch it,\n\
or set WOLFSSL_SOURCE_DIR to an alternative path.\n",
source_dir.display()
);
}
build_wolfssl_cmake(&source_dir, &out_dir)
};
// ── 2. Tell Cargo how to link ────────────────────────────────────────────
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=wolfssl");
// Link system libraries required by wolfSSL on each platform.
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
match target_os.as_str() {
"macos" | "ios" => {
println!("cargo:rustc-link-lib=framework=Security");
println!("cargo:rustc-link-lib=c++");
}
"linux" | "android" => {
println!("cargo:rustc-link-lib=stdc++");
}
_ => {}
}
// Re-run the build script if any of these change.
println!("cargo:rerun-if-env-changed=WOLFSSL_SOURCE_DIR");
println!("cargo:rerun-if-env-changed=WOLFSSL_INSTALL_DIR");
// ── 3. Generate FFI bindings via bindgen ─────────────────────────────────
generate_bindings(&include_dir, &out_dir);
}
/// Build wolfSSL from source using CMake. Returns `(include_dir, lib_dir)`.
fn build_wolfssl_cmake(source_dir: &PathBuf, _out_dir: &PathBuf) -> (PathBuf, PathBuf) {
println!(
"cargo:warning=Building wolfSSL from source at {}",
source_dir.display()
);
let mut cfg = cmake::Config::new(source_dir);
// ── Core algorithm selection ─────────────────────────────────────────────
// 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")
// 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_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", "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_MIN_RSA_BITS", "2048")
// 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 include_dir = install_path.join("include");
let lib_dir = install_path.join("lib");
(include_dir, lib_dir)
}
/// Run bindgen over the wolfCrypt headers we actually use.
fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
let header = include_dir.join("wolfssl/wolfcrypt/aes.h");
if !header.exists() {
// Bindings are only generated when wolfSSL headers are present.
// In environments without the C library (e.g. pure Rust CI), a
// pre-generated bindings file can be committed at src/bindings.rs.
println!(
"cargo:warning=wolfSSL headers not found at {}; \
using pre-generated bindings if available.",
include_dir.display()
);
return;
}
let bindings = bindgen::Builder::default()
// Include all wolfCrypt headers we need.
.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()) // 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()))
// 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_XChaCha20Poly1305.*")
// SHA-256 (init/update/final — avoids WOLFSSL_SMALL_STACK heap allocation in wc_Sha256Hash)
.allowlist_function("wc_InitSha256")
.allowlist_function("wc_Sha256Update")
.allowlist_function("wc_Sha256Final")
.allowlist_function("wc_Sha256Free")
.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_HmacInit")
.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("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_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()
.expect("bindgen failed to generate wolfCrypt bindings");
bindings
.write_to_file(out_dir.join("wolfcrypt_bindings.rs"))
.expect("failed to write wolfCrypt bindings");
println!("cargo:rerun-if-changed=build.rs");
}