//! 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 ───────────────────────────────────────────── // Enable exactly the algorithms the CCC system needs in Phase 4. // Additional algorithms can be enabled here for Phase 5+ PQ support. 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") // 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 // 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") // Minimise binary size. .define("WOLFSSL_CRYPTONLY", "ON") .define("WOLFSSL_MIN_RSA_BITS", "2048") .define("WOLFSSL_NO_FILESYSTEM", "ON"); 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()) .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/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. .allowlist_function("wc_Aes.*") .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_type("Aes") .allowlist_type("Hmac") .allowlist_type("WC_RNG") .allowlist_type("curve25519.*") .allowlist_type("curve448.*") .allowlist_var("AES_.*") .allowlist_var("WC_.*") .allowlist_var("SHA.*") .allowlist_var("BLAKE2.*") // Silence warnings for types we don't control. .blocklist_type("__.*") .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"); }