//! 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 `/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"); }