1use std::collections::HashSet;
22use std::env;
23use std::io::BufRead;
24use std::path::{Path, PathBuf};
25
26pub struct Artifacts {
28 pub lib_dir: PathBuf,
30 pub include_dir: PathBuf,
32 pub settings_include_dir: PathBuf,
34 pub defines: HashSet<String>,
36}
37
38pub struct Build {
40 source_dir: Option<PathBuf>,
42 fips: bool,
44}
45
46impl Build {
47 pub fn new() -> Self {
48 Build {
49 source_dir: None,
50 fips: false,
51 }
52 }
53
54 pub fn source_dir(&mut self, dir: PathBuf) -> &mut Self {
57 self.source_dir = Some(dir);
58 self
59 }
60
61 pub fn fips(&mut self, enable: bool) -> &mut Self {
63 self.fips = enable;
64 self
65 }
66
67 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 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 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 let ssl_srcs: &[&str] = if cfg!(feature = "riscv-bare-metal") {
104 &["ssl.c"]
105 } else {
106 ssl_sources(&defines)
107 };
108
109 let mut build = cc::Build::new();
111 build.include(&wolfssl_dir);
112
113 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 build.include(&out_dir);
123
124 if let Ok(stubs) = env::var("WOLFSSL_BARE_METAL_STUBS") {
126 build.include(stubs);
127 }
128
129 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 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 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 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 fn find_via_pkg_config() -> Option<PathBuf> {
216 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 if let Some(incdir) = pkg_config_var("includedir") {
228 let path = PathBuf::from(&incdir);
229 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
241fn 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
265pub 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
302const 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}