309 lines
14 KiB
Rust
309 lines
14 KiB
Rust
//! 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 (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("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");
|
||
}
|