1use std::collections::HashSet;
23use std::env;
24use std::io::BufRead;
25use std::path::{Path, PathBuf};
26
27pub struct Artifacts {
29 pub lib_dir: PathBuf,
31 pub include_dir: PathBuf,
33 pub settings_include_dir: PathBuf,
35 pub defines: HashSet<String>,
37}
38
39pub struct Build {
41 source_dir: Option<PathBuf>,
43 fips: bool,
45}
46
47impl Build {
48 pub fn new() -> Self {
49 Build {
50 source_dir: None,
51 fips: false,
52 }
53 }
54
55 pub fn source_dir(&mut self, dir: PathBuf) -> &mut Self {
58 self.source_dir = Some(dir);
59 self
60 }
61
62 pub fn fips(&mut self, enable: bool) -> &mut Self {
64 self.fips = enable;
65 self
66 }
67
68 pub fn build(&self) -> Artifacts {
70 let wolfssl_dir = self.resolve_source_dir();
71 let settings_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
72
73 let user_settings_name = if cfg!(feature = "cryptocb-pure") {
76 "user_settings_cryptocb_pure.h"
77 } else if cfg!(feature = "cryptocb-only") {
78 "user_settings_cryptocb_only.h"
79 } else if cfg!(feature = "riscv-bare-metal") {
80 "user_settings_riscv.h"
81 } else {
82 "user_settings.h"
83 };
84 let user_settings_path = settings_dir.join(user_settings_name);
85 let mut defines = parse_defines(&user_settings_path);
86 if self.fips {
87 let fips_path = settings_dir.join("user_settings_fips.h");
88 if !fips_path.exists() {
89 panic!(
90 "FIPS build requested but {} does not exist. \
91 Create it with the required FIPS #defines.",
92 fips_path.display()
93 );
94 }
95 defines.extend(parse_defines(&fips_path));
96 }
97
98 let wolfcrypt_src = wolfssl_dir.join("wolfcrypt").join("src");
100 let ssl_src = wolfssl_dir.join("src");
101
102 let mut wolfcrypt_sources: Vec<&str> = if cfg!(feature = "cryptocb-pure") {
103 CRYPTOCB_PURE_CORE_SOURCES.to_vec()
104 } else if cfg!(feature = "cryptocb-only") {
105 CRYPTOCB_ONLY_CORE_SOURCES.to_vec()
106 } else {
107 CORE_WOLFCRYPT_SOURCES.to_vec()
108 };
109 if self.fips {
110 wolfcrypt_sources.extend_from_slice(FIPS_WOLFCRYPT_SOURCES);
111 }
112 if cfg!(feature = "cryptocb-pure") {
113 append_cryptocb_pure_sources(&defines, &mut wolfcrypt_sources);
114 } else if cfg!(feature = "cryptocb-only") {
115 append_cryptocb_only_sources(&defines, &mut wolfcrypt_sources);
116 } else {
117 append_conditional_wolfcrypt_sources(&defines, &mut wolfcrypt_sources);
118 }
119 let ssl_srcs: &[&str] = if cfg!(any(
123 feature = "cryptocb-pure",
124 feature = "cryptocb-only",
125 feature = "riscv-bare-metal",
126 )) {
127 &[]
128 } else {
129 ssl_sources(&defines)
130 };
131
132 let mut build = cc::Build::new();
134 build.include(&wolfssl_dir);
135
136 if cfg!(any(
140 feature = "riscv-bare-metal",
141 feature = "cryptocb-only",
142 feature = "cryptocb-pure"
143 )) {
144 let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
145 let src_name = if cfg!(feature = "cryptocb-pure") {
146 "user_settings_cryptocb_pure.h"
147 } else if cfg!(feature = "cryptocb-only") {
148 "user_settings_cryptocb_only.h"
149 } else {
150 "user_settings_riscv.h"
151 };
152 let src = settings_dir.join(src_name);
153 let dst = out_dir.join("user_settings.h");
154 std::fs::copy(&src, &dst).unwrap_or_else(|e| panic!("failed to copy {src_name}: {e}"));
155 build.include(&out_dir);
157
158 if let Ok(stubs) = env::var("WOLFSSL_BARE_METAL_STUBS") {
160 build.include(stubs);
161 }
162
163 let helpers = settings_dir.join("riscv_bare_metal_helpers.c");
166 if helpers.exists() {
167 build.file(&helpers);
168 println!("cargo:rerun-if-changed={}", helpers.display());
169 }
170 }
171 build.include(&settings_dir);
172
173 build.define("WOLFSSL_USER_SETTINGS", None);
174 if self.fips {
175 build.define("HAVE_FIPS", None);
176 }
177
178 let patches_dir = settings_dir.join("patches");
182 for src in &wolfcrypt_sources {
183 let patched = patches_dir.join(src);
184 let path = if patched.exists() {
185 patched
186 } else {
187 let upstream = wolfcrypt_src.join(src);
188 if !upstream.exists() {
189 panic!(
190 "required wolfcrypt source not found: {}",
191 upstream.display()
192 );
193 }
194 upstream
195 };
196 build.file(&path);
197 println!("cargo:rerun-if-changed={}", path.display());
198 }
199 for src in ssl_srcs {
200 let path = ssl_src.join(src);
201 if !path.exists() {
202 panic!("required wolfssl source not found: {}", path.display());
203 }
204 build.file(&path);
205 println!("cargo:rerun-if-changed={}", path.display());
206 }
207
208 build.warnings(false);
209 build.opt_level(2);
210 build.compile("wolfssl");
211
212 println!("cargo:rerun-if-changed={}", user_settings_path.display());
213 println!("cargo:rerun-if-changed={}", patches_dir.display());
214 if self.fips {
215 println!(
216 "cargo:rerun-if-changed={}",
217 settings_dir.join("user_settings_fips.h").display()
218 );
219 }
220
221 Artifacts {
222 lib_dir: PathBuf::from(env::var("OUT_DIR").unwrap()),
223 include_dir: wolfssl_dir,
224 settings_include_dir: settings_dir,
225 defines,
226 }
227 }
228
229 fn resolve_source_dir(&self) -> PathBuf {
230 if let Some(ref dir) = self.source_dir {
232 if !dir.exists() {
233 panic!("wolfssl source dir does not exist: {}", dir.display());
234 }
235 return dir.clone();
236 }
237
238 if let Ok(dir) = env::var("WOLFSSL_SRC")
240 && !dir.is_empty()
241 {
242 let path = PathBuf::from(&dir);
243 if !path.exists() {
244 panic!("WOLFSSL_SRC={dir} does not exist");
245 }
246 return path;
247 }
248
249 let bundled = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("wolfssl");
251 if bundled.join("wolfcrypt/src").exists() {
252 return bundled;
253 }
254
255 if let Some(dir) = Self::find_via_pkg_config() {
257 return dir;
258 }
259
260 panic!(
261 "wolfSSL source not found. Either:\n \
262 - Run: git submodule update --init\n \
263 - Set WOLFSSL_SRC to the path of your wolfssl checkout\n \
264 - Install wolfssl-dev so that pkg-config can find it"
265 );
266 }
267
268 fn find_via_pkg_config() -> Option<PathBuf> {
276 if let Some(prefix) = pkg_config_var("prefix") {
279 let path = PathBuf::from(&prefix);
280 if path.join("wolfcrypt").join("src").exists() {
281 return Some(path);
282 }
283 }
284
285 if let Some(incdir) = pkg_config_var("includedir") {
288 let path = PathBuf::from(&incdir);
289 if let Some(parent) = path.parent()
291 && parent.join("wolfcrypt").join("src").exists()
292 {
293 return Some(parent.to_path_buf());
294 }
295 }
296
297 None
298 }
299}
300
301fn pkg_config_var(var: &str) -> Option<String> {
303 let output = std::process::Command::new("pkg-config")
304 .args(["--variable", var, "wolfssl"])
305 .output()
306 .ok()?;
307 if !output.status.success() {
308 return None;
309 }
310 let val = String::from_utf8(output.stdout).ok()?;
311 let val = val.trim();
312 if val.is_empty() {
313 None
314 } else {
315 Some(val.to_string())
316 }
317}
318
319impl Default for Build {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325pub fn parse_defines(path: &Path) -> HashSet<String> {
333 let file =
334 std::fs::File::open(path).unwrap_or_else(|e| panic!("cannot open {}: {e}", path.display()));
335 let reader = std::io::BufReader::new(file);
336 let mut defines = HashSet::new();
337 for line in reader.lines() {
338 let line = line.expect("read error");
339 let trimmed = line.trim();
340 let Some(rest) = trimmed.strip_prefix('#') else {
341 continue;
342 };
343 let rest = rest.trim_start();
344 let Some(rest) = rest.strip_prefix("define") else {
345 continue;
346 };
347 if !rest.starts_with(|c: char| c.is_ascii_whitespace()) {
348 continue;
349 }
350 let name = rest
351 .trim_start()
352 .split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
353 .next()
354 .unwrap_or("");
355 if !name.is_empty() {
356 defines.insert(name.to_string());
357 }
358 }
359 defines
360}
361
362const CORE_WOLFCRYPT_SOURCES: &[&str] = &[
367 "aes.c",
368 "arc4.c",
369 "asn.c",
370 "blake2b.c",
371 "blake2s.c",
372 "camellia.c",
373 "cmac.c",
374 "coding.c",
375 "cpuid.c",
376 "cryptocb.c",
377 "dsa.c",
378 "error.c",
379 "hash.c",
380 "logging.c",
381 "md4.c",
382 "md5.c",
383 "memory.c",
384 "pkcs7.c",
385 "pkcs12.c",
386 "random.c",
387 "sha.c",
388 "sha256.c",
389 "signature.c",
390 "sp_int.c",
391 "sp_c32.c",
392 "sp_c64.c",
393 "srp.c",
394 "wc_encrypt.c",
395 "wc_port.c",
396 "wolfmath.c",
397];
398
399const FIPS_WOLFCRYPT_SOURCES: &[&str] = &[
400 "fips.c",
401 "fips_test.c",
402 "wolfcrypt_first.c",
403 "wolfcrypt_last.c",
404];
405
406const CRYPTOCB_ONLY_CORE_SOURCES: &[&str] = &[
425 "aes.c",
426 "asn.c",
427 "coding.c",
428 "cpuid.c",
429 "cryptocb.c",
430 "error.c",
431 "hash.c",
432 "logging.c",
433 "memory.c",
434 "random.c",
435 "sha.c",
436 "sha256.c",
437 "signature.c",
438 "wc_encrypt.c",
439 "wc_port.c",
440];
441
442const CRYPTOCB_PURE_CORE_SOURCES: &[&str] = &[
457 "aes.c",
458 "cryptocb.c",
459 "error.c",
460 "hash.c",
461 "logging.c",
462 "memory.c",
463 "random.c",
464 "sha.c",
465 "sha256.c",
466 "wc_port.c",
467];
468
469fn append_cryptocb_only_sources(defines: &HashSet<String>, sources: &mut Vec<&'static str>) {
477 if !defines.contains("NO_HMAC") {
479 sources.push("hmac.c");
480 }
481 if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
483 sources.push("sha512.c");
484 }
485 if defines.contains("HAVE_ECC") {
487 sources.push("ecc.c");
488 }
489 if defines.contains("HAVE_HKDF") {
491 sources.push("kdf.c");
492 }
493 if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
495 sources.push("evp.c");
496 }
497}
498
499fn append_cryptocb_pure_sources(defines: &HashSet<String>, sources: &mut Vec<&'static str>) {
504 if !defines.contains("NO_HMAC") {
506 sources.push("hmac.c");
507 }
508 if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
510 sources.push("sha512.c");
511 }
512 if defines.contains("HAVE_ECC") {
514 sources.push("ecc.c");
515 }
516 }
519
520fn append_conditional_wolfcrypt_sources(
521 defines: &HashSet<String>,
522 sources: &mut Vec<&'static str>,
523) {
524 if defines.contains("HAVE_CHACHA") {
525 sources.push("chacha.c");
526 }
527 if defines.contains("HAVE_CHACHA") && defines.contains("HAVE_POLY1305") {
528 sources.push("chacha20_poly1305.c");
529 }
530 if defines.contains("HAVE_POLY1305") {
531 sources.push("poly1305.c");
532 }
533 if defines.contains("HAVE_ECC") {
534 sources.push("ecc.c");
535 }
536 if defines.contains("HAVE_ED25519") || defines.contains("HAVE_CURVE25519") {
537 sources.push("curve25519.c");
538 sources.push("fe_operations.c");
539 sources.push("ge_operations.c");
540 }
541 if defines.contains("HAVE_ED25519") {
542 sources.push("ed25519.c");
543 }
544 if defines.contains("HAVE_ED448") || defines.contains("HAVE_CURVE448") {
545 sources.push("curve448.c");
546 sources.push("fe_448.c");
547 sources.push("ge_448.c");
548 }
549 if defines.contains("HAVE_ED448") {
550 sources.push("ed448.c");
551 }
552 if !defines.contains("NO_DH") {
553 sources.push("dh.c");
554 }
555 if !defines.contains("NO_RSA") {
556 sources.push("rsa.c");
557 }
558 if !defines.contains("NO_HMAC") {
559 sources.push("hmac.c");
560 }
561 if !defines.contains("NO_DES3") {
562 sources.push("des3.c");
563 }
564 if defines.contains("WOLFSSL_SHA3") {
565 sources.push("sha3.c");
566 }
567 if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
568 sources.push("sha512.c");
569 }
570 if defines.contains("HAVE_DILITHIUM") {
571 sources.push("dilithium.c");
572 }
573 if defines.contains("WOLFSSL_HAVE_MLKEM") {
574 sources.push("wc_mlkem.c");
575 sources.push("wc_mlkem_poly.c");
576 }
577 if defines.contains("HAVE_HKDF") {
578 sources.push("kdf.c");
579 }
580 if defines.contains("HAVE_PBKDF2") {
581 sources.push("pwdbased.c");
582 }
583 if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
584 sources.push("evp.c");
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591 use std::io::Write;
592
593 #[test]
594 fn parse_defines_basic() {
595 let mut f = tempfile::NamedTempFile::new().unwrap();
596 writeln!(f, "#define HAVE_ECC").unwrap();
597 writeln!(f, "#define HAVE_AES").unwrap();
598 writeln!(f, "#define WOLFSSL_SHA256").unwrap();
599 writeln!(f, "// not a define").unwrap();
600 writeln!(f, "int x = 5;").unwrap();
601 let defs = parse_defines(f.path());
602 assert!(defs.contains("HAVE_ECC"), "missing HAVE_ECC: {:?}", defs);
603 assert!(defs.contains("HAVE_AES"), "missing HAVE_AES: {:?}", defs);
604 assert!(
605 defs.contains("WOLFSSL_SHA256"),
606 "missing WOLFSSL_SHA256: {:?}",
607 defs
608 );
609 assert_eq!(defs.len(), 3, "unexpected defines: {:?}", defs);
610 }
611
612 #[test]
613 fn parse_defines_with_values() {
614 let mut f = tempfile::NamedTempFile::new().unwrap();
615 writeln!(f, "#define WOLFSSL_MAX_STRENGTH 1").unwrap();
616 writeln!(f, "#define HAVE_FIPS_VERSION 5").unwrap();
617 let defs = parse_defines(f.path());
618 assert!(defs.contains("WOLFSSL_MAX_STRENGTH"));
619 assert!(defs.contains("HAVE_FIPS_VERSION"));
620 }
621
622 #[test]
623 fn parse_defines_ignores_non_defines() {
624 let mut f = tempfile::NamedTempFile::new().unwrap();
625 writeln!(f, "#include <stdio.h>").unwrap();
626 writeln!(f, "#ifdef HAVE_ECC").unwrap();
627 writeln!(f, "#endif").unwrap();
628 writeln!(f, "void foo(void);").unwrap();
629 let defs = parse_defines(f.path());
630 assert!(defs.is_empty(), "should have no defines: {:?}", defs);
631 }
632
633 #[test]
634 fn parse_defines_empty_file() {
635 let f = tempfile::NamedTempFile::new().unwrap();
636 let defs = parse_defines(f.path());
637 assert!(defs.is_empty());
638 }
639}
640
641fn ssl_sources(defines: &HashSet<String>) -> &'static [&'static str] {
642 if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
643 &[
644 "pk.c",
645 "pk_ec.c",
646 "pk_rsa.c",
647 "ssl.c",
648 "ssl_api_pk.c",
649 "ssl_asn1.c",
650 "ssl_bn.c",
651 "ssl_crypto.c",
652 "ssl_load.c",
653 "ssl_misc.c",
654 "ssl_sk.c",
655 ]
656 } else {
657 &[]
658 }
659}