Skip to main content

wolfssl_src/
lib.rs

1//! Compile wolfSSL from source.
2//!
3//! This crate provides a [`Build`] API for compiling the wolfSSL C library
4//! from source via the [`cc`] crate.  It is used by `wolfcrypt-sys` when
5//! the `vendored` feature is enabled (similar to the `openssl-src` /
6//! `openssl-sys` pattern).
7//!
8//! # Usage
9//!
10//! ```rust,no_run
11//! let artifacts = wolfssl_src::Build::new().build();
12//! println!("lib dir: {}", artifacts.lib_dir.display());
13//! println!("include dir: {}", artifacts.include_dir.display());
14//! ```
15//!
16//! The builder discovers wolfSSL sources in order:
17//! 1. `source_dir()` programmatic override
18//! 2. `WOLFSSL_SRC` environment variable
19//! 3. `pkg-config` (looks for a `wolfssl` package whose prefix contains source files)
20
21use std::collections::HashSet;
22use std::env;
23use std::io::BufRead;
24use std::path::{Path, PathBuf};
25
26/// Result of a successful wolfSSL build.
27pub struct Artifacts {
28    /// Directory containing the compiled `libwolfssl.a`.
29    pub lib_dir: PathBuf,
30    /// wolfSSL source root — use as `-I` path for headers.
31    pub include_dir: PathBuf,
32    /// Directory containing `user_settings.h` — use as `-I` path.
33    pub settings_include_dir: PathBuf,
34    /// Parsed `#define` names from `user_settings.h`.
35    pub defines: HashSet<String>,
36}
37
38/// Builder for compiling wolfSSL from source.
39pub struct Build {
40    /// Path to the wolfSSL source tree.
41    source_dir: Option<PathBuf>,
42    /// Enable FIPS build.
43    fips: bool,
44}
45
46impl Build {
47    pub fn new() -> Self {
48        Build {
49            source_dir: None,
50            fips: false,
51        }
52    }
53
54    /// Set the path to the wolfSSL source tree.
55    /// If not set, defaults to `WOLFSSL_SRC` env var, then `pkg-config`.
56    pub fn source_dir(&mut self, dir: PathBuf) -> &mut Self {
57        self.source_dir = Some(dir);
58        self
59    }
60
61    /// Enable FIPS 140-3 build.
62    pub fn fips(&mut self, enable: bool) -> &mut Self {
63        self.fips = enable;
64        self
65    }
66
67    /// Compile wolfSSL and return artifact paths.
68    pub fn build(&self) -> Artifacts {
69        let wolfssl_dir = self.resolve_source_dir();
70        let settings_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
71
72        // Select user_settings header based on target
73        let user_settings_name = if cfg!(feature = "riscv-bare-metal") {
74            "user_settings_riscv.h"
75        } else {
76            "user_settings.h"
77        };
78        let user_settings_path = settings_dir.join(user_settings_name);
79        let mut defines = parse_defines(&user_settings_path);
80        if self.fips {
81            let fips_path = settings_dir.join("user_settings_fips.h");
82            if !fips_path.exists() {
83                panic!(
84                    "FIPS build requested but {} does not exist. \
85                     Create it with the required FIPS #defines.",
86                    fips_path.display()
87                );
88            }
89            defines.extend(parse_defines(&fips_path));
90        }
91
92        // Collect source files
93        let wolfcrypt_src = wolfssl_dir.join("wolfcrypt").join("src");
94        let ssl_src = wolfssl_dir.join("src");
95
96        let mut wolfcrypt_sources: Vec<&str> = CORE_WOLFCRYPT_SOURCES.to_vec();
97        if self.fips {
98            wolfcrypt_sources.extend_from_slice(FIPS_WOLFCRYPT_SOURCES);
99        }
100        append_conditional_wolfcrypt_sources(&defines, &mut wolfcrypt_sources);
101        // For riscv-bare-metal, compile ssl.c only (it includes pk.c, pk_ec.c,
102        // ssl_bn.c, etc. internally as one compilation unit).
103        let ssl_srcs: &[&str] = if cfg!(feature = "riscv-bare-metal") {
104            &["ssl.c"]
105        } else {
106            ssl_sources(&defines)
107        };
108
109        // Compile
110        let mut build = cc::Build::new();
111        build.include(&wolfssl_dir);
112
113        // For riscv-bare-metal, place a copy of user_settings_riscv.h as
114        // user_settings.h in OUT_DIR so it shadows the default.
115        if cfg!(feature = "riscv-bare-metal") {
116            let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
117            let riscv_src = settings_dir.join("user_settings_riscv.h");
118            let riscv_dst = out_dir.join("user_settings.h");
119            std::fs::copy(&riscv_src, &riscv_dst)
120                .expect("failed to copy user_settings_riscv.h");
121            // OUT_DIR comes first so its user_settings.h takes precedence
122            build.include(&out_dir);
123
124            // Add bare-metal stub headers (stdio.h, etc.) if available
125            if let Ok(stubs) = env::var("WOLFSSL_BARE_METAL_STUBS") {
126                build.include(stubs);
127            }
128
129            // Compile bare-metal helper functions
130            let helpers = settings_dir.join("riscv_bare_metal_helpers.c");
131            if helpers.exists() {
132                build.file(&helpers);
133                println!("cargo:rerun-if-changed={}", helpers.display());
134            }
135        }
136        build.include(&settings_dir);
137
138        build.define("WOLFSSL_USER_SETTINGS", None);
139        if self.fips {
140            build.define("HAVE_FIPS", None);
141        }
142
143        for src in &wolfcrypt_sources {
144            let path = wolfcrypt_src.join(src);
145            if !path.exists() {
146                panic!("required wolfcrypt source not found: {}", path.display());
147            }
148            build.file(&path);
149            println!("cargo:rerun-if-changed={}", path.display());
150        }
151        for src in ssl_srcs {
152            let path = ssl_src.join(src);
153            if !path.exists() {
154                panic!("required wolfssl source not found: {}", path.display());
155            }
156            build.file(&path);
157            println!("cargo:rerun-if-changed={}", path.display());
158        }
159
160        build.warnings(false);
161        build.opt_level(2);
162        build.compile("wolfssl");
163
164        println!("cargo:rerun-if-changed={}", user_settings_path.display());
165        if self.fips {
166            println!("cargo:rerun-if-changed={}", settings_dir.join("user_settings_fips.h").display());
167        }
168
169        Artifacts {
170            lib_dir: PathBuf::from(env::var("OUT_DIR").unwrap()),
171            include_dir: wolfssl_dir,
172            settings_include_dir: settings_dir,
173            defines,
174        }
175    }
176
177    fn resolve_source_dir(&self) -> PathBuf {
178        // 1. Programmatic override
179        if let Some(ref dir) = self.source_dir {
180            if !dir.exists() {
181                panic!("wolfssl source dir does not exist: {}", dir.display());
182            }
183            return dir.clone();
184        }
185
186        // 2. WOLFSSL_SRC env var
187        if let Ok(dir) = env::var("WOLFSSL_SRC") {
188            let path = PathBuf::from(&dir);
189            if !path.exists() {
190                panic!("WOLFSSL_SRC={dir} does not exist");
191            }
192            return path;
193        }
194
195        // 3. pkg-config
196        if let Some(dir) = Self::find_via_pkg_config() {
197            return dir;
198        }
199
200        panic!(
201            "wolfSSL source not found. Either:\n  \
202             - Set WOLFSSL_SRC to the path of your wolfssl checkout\n  \
203             - Install wolfssl-dev so that pkg-config can find it\n  \
204             - Clone it: git clone https://github.com/wolfSSL/wolfssl.git"
205        );
206    }
207
208    /// Try to locate wolfSSL source via `pkg-config`.
209    ///
210    /// Queries `pkg-config --variable=prefix wolfssl` and checks whether the
211    /// returned prefix contains a wolfSSL source tree (i.e. `wolfcrypt/src/`
212    /// exists under it).  Falls back to the include directory if the prefix
213    /// doesn't contain source files — some installs place the full tree
214    /// under the include root.
215    fn find_via_pkg_config() -> Option<PathBuf> {
216        // Try the prefix first (works for source-tree installs like
217        // ./configure --prefix=/opt/wolfssl && make install)
218        if let Some(prefix) = pkg_config_var("prefix") {
219            let path = PathBuf::from(&prefix);
220            if path.join("wolfcrypt").join("src").exists() {
221                return Some(path);
222            }
223        }
224
225        // Fall back to includedir — strip the trailing /include (or
226        // /include/wolfssl) to get the root.
227        if let Some(incdir) = pkg_config_var("includedir") {
228            let path = PathBuf::from(&incdir);
229            // Try <includedir>/../ (e.g. /usr/local/include → /usr/local)
230            if let Some(parent) = path.parent() {
231                if parent.join("wolfcrypt").join("src").exists() {
232                    return Some(parent.to_path_buf());
233                }
234            }
235        }
236
237        None
238    }
239}
240
241/// Query a pkg-config variable for the `wolfssl` package.
242fn pkg_config_var(var: &str) -> Option<String> {
243    let output = std::process::Command::new("pkg-config")
244        .args(["--variable", var, "wolfssl"])
245        .output()
246        .ok()?;
247    if !output.status.success() {
248        return None;
249    }
250    let val = String::from_utf8(output.stdout).ok()?;
251    let val = val.trim();
252    if val.is_empty() {
253        None
254    } else {
255        Some(val.to_string())
256    }
257}
258
259impl Default for Build {
260    fn default() -> Self {
261        Self::new()
262    }
263}
264
265// ================================================================
266// Settings parser
267// ================================================================
268
269/// Parse a C header and return all `#define`d macro names.
270///
271/// Flat scan — does not evaluate `#if`/`#ifdef` guards.
272pub fn parse_defines(path: &Path) -> HashSet<String> {
273    let file = std::fs::File::open(path)
274        .unwrap_or_else(|e| panic!("cannot open {}: {e}", path.display()));
275    let reader = std::io::BufReader::new(file);
276    let mut defines = HashSet::new();
277    for line in reader.lines() {
278        let line = line.expect("read error");
279        let trimmed = line.trim();
280        let Some(rest) = trimmed.strip_prefix('#') else {
281            continue;
282        };
283        let rest = rest.trim_start();
284        let Some(rest) = rest.strip_prefix("define") else {
285            continue;
286        };
287        if !rest.starts_with(|c: char| c.is_ascii_whitespace()) {
288            continue;
289        }
290        let name = rest
291            .trim_start()
292            .split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
293            .next()
294            .unwrap_or("");
295        if !name.is_empty() {
296            defines.insert(name.to_string());
297        }
298    }
299    defines
300}
301
302// ================================================================
303// Source file lists
304// ================================================================
305
306const CORE_WOLFCRYPT_SOURCES: &[&str] = &[
307    "aes.c",
308    "arc4.c",
309    "asn.c",
310    "blake2b.c",
311    "blake2s.c",
312    "camellia.c",
313    "cmac.c",
314    "coding.c",
315    "cpuid.c",
316    "cryptocb.c",
317    "dsa.c",
318    "error.c",
319    "hash.c",
320    "logging.c",
321    "md4.c",
322    "md5.c",
323    "memory.c",
324    "pkcs7.c",
325    "pkcs12.c",
326    "random.c",
327    "sha.c",
328    "sha256.c",
329    "signature.c",
330    "sp_int.c",
331    "sp_c32.c",
332    "sp_c64.c",
333    "srp.c",
334    "wc_encrypt.c",
335    "wc_port.c",
336    "wolfmath.c",
337];
338
339const FIPS_WOLFCRYPT_SOURCES: &[&str] = &[
340    "fips.c",
341    "fips_test.c",
342    "wolfcrypt_first.c",
343    "wolfcrypt_last.c",
344];
345
346fn append_conditional_wolfcrypt_sources(defines: &HashSet<String>, sources: &mut Vec<&'static str>) {
347    if defines.contains("HAVE_CHACHA") {
348        sources.push("chacha.c");
349    }
350    if defines.contains("HAVE_CHACHA") && defines.contains("HAVE_POLY1305") {
351        sources.push("chacha20_poly1305.c");
352    }
353    if defines.contains("HAVE_POLY1305") {
354        sources.push("poly1305.c");
355    }
356    if defines.contains("HAVE_ECC") {
357        sources.push("ecc.c");
358    }
359    if defines.contains("HAVE_ED25519") || defines.contains("HAVE_CURVE25519") {
360        sources.push("curve25519.c");
361        sources.push("fe_operations.c");
362        sources.push("ge_operations.c");
363    }
364    if defines.contains("HAVE_ED25519") {
365        sources.push("ed25519.c");
366    }
367    if defines.contains("HAVE_ED448") || defines.contains("HAVE_CURVE448") {
368        sources.push("curve448.c");
369        sources.push("fe_448.c");
370        sources.push("ge_448.c");
371    }
372    if defines.contains("HAVE_ED448") {
373        sources.push("ed448.c");
374    }
375    if !defines.contains("NO_DH") {
376        sources.push("dh.c");
377    }
378    if !defines.contains("NO_RSA") {
379        sources.push("rsa.c");
380    }
381    if !defines.contains("NO_HMAC") {
382        sources.push("hmac.c");
383    }
384    if !defines.contains("NO_DES3") {
385        sources.push("des3.c");
386    }
387    if defines.contains("WOLFSSL_SHA3") {
388        sources.push("sha3.c");
389    }
390    if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
391        sources.push("sha512.c");
392    }
393    if defines.contains("HAVE_DILITHIUM") {
394        sources.push("dilithium.c");
395    }
396    if defines.contains("WOLFSSL_HAVE_MLKEM") {
397        sources.push("wc_mlkem.c");
398        sources.push("wc_mlkem_poly.c");
399    }
400    if defines.contains("HAVE_HKDF") {
401        sources.push("kdf.c");
402    }
403    if defines.contains("HAVE_PBKDF2") {
404        sources.push("pwdbased.c");
405    }
406    if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
407        sources.push("evp.c");
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414    use std::io::Write;
415
416    #[test]
417    fn parse_defines_basic() {
418        let mut f = tempfile::NamedTempFile::new().unwrap();
419        writeln!(f, "#define HAVE_ECC").unwrap();
420        writeln!(f, "#define HAVE_AES").unwrap();
421        writeln!(f, "#define WOLFSSL_SHA256").unwrap();
422        writeln!(f, "// not a define").unwrap();
423        writeln!(f, "int x = 5;").unwrap();
424        let defs = parse_defines(f.path());
425        assert!(defs.contains("HAVE_ECC"), "missing HAVE_ECC: {:?}", defs);
426        assert!(defs.contains("HAVE_AES"), "missing HAVE_AES: {:?}", defs);
427        assert!(defs.contains("WOLFSSL_SHA256"), "missing WOLFSSL_SHA256: {:?}", defs);
428        assert_eq!(defs.len(), 3, "unexpected defines: {:?}", defs);
429    }
430
431    #[test]
432    fn parse_defines_with_values() {
433        let mut f = tempfile::NamedTempFile::new().unwrap();
434        writeln!(f, "#define WOLFSSL_MAX_STRENGTH 1").unwrap();
435        writeln!(f, "#define HAVE_FIPS_VERSION 5").unwrap();
436        let defs = parse_defines(f.path());
437        assert!(defs.contains("WOLFSSL_MAX_STRENGTH"));
438        assert!(defs.contains("HAVE_FIPS_VERSION"));
439    }
440
441    #[test]
442    fn parse_defines_ignores_non_defines() {
443        let mut f = tempfile::NamedTempFile::new().unwrap();
444        writeln!(f, "#include <stdio.h>").unwrap();
445        writeln!(f, "#ifdef HAVE_ECC").unwrap();
446        writeln!(f, "#endif").unwrap();
447        writeln!(f, "void foo(void);").unwrap();
448        let defs = parse_defines(f.path());
449        assert!(defs.is_empty(), "should have no defines: {:?}", defs);
450    }
451
452    #[test]
453    fn parse_defines_empty_file() {
454        let f = tempfile::NamedTempFile::new().unwrap();
455        let defs = parse_defines(f.path());
456        assert!(defs.is_empty());
457    }
458}
459
460fn ssl_sources(defines: &HashSet<String>) -> &'static [&'static str] {
461    if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
462        &[
463            "pk.c",
464            "pk_ec.c",
465            "pk_rsa.c",
466            "ssl.c",
467            "ssl_api_pk.c",
468            "ssl_asn1.c",
469            "ssl_bn.c",
470            "ssl_crypto.c",
471            "ssl_load.c",
472            "ssl_misc.c",
473            "ssl_sk.c",
474        ]
475    } else {
476        &[]
477    }
478}