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(feature = "riscv-bare-metal", feature = "cryptocb-only", feature = "cryptocb-pure")) {
140 let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
141 let src_name = if cfg!(feature = "cryptocb-pure") {
142 "user_settings_cryptocb_pure.h"
143 } else if cfg!(feature = "cryptocb-only") {
144 "user_settings_cryptocb_only.h"
145 } else {
146 "user_settings_riscv.h"
147 };
148 let src = settings_dir.join(src_name);
149 let dst = out_dir.join("user_settings.h");
150 std::fs::copy(&src, &dst)
151 .unwrap_or_else(|e| panic!("failed to copy {src_name}: {e}"));
152 build.include(&out_dir);
154
155 if let Ok(stubs) = env::var("WOLFSSL_BARE_METAL_STUBS") {
157 build.include(stubs);
158 }
159
160 let helpers = settings_dir.join("riscv_bare_metal_helpers.c");
163 if helpers.exists() {
164 build.file(&helpers);
165 println!("cargo:rerun-if-changed={}", helpers.display());
166 }
167 }
168 build.include(&settings_dir);
169
170 build.define("WOLFSSL_USER_SETTINGS", None);
171 if self.fips {
172 build.define("HAVE_FIPS", None);
173 }
174
175 let patches_dir = settings_dir.join("patches");
179 for src in &wolfcrypt_sources {
180 let patched = patches_dir.join(src);
181 let path = if patched.exists() {
182 patched
183 } else {
184 let upstream = wolfcrypt_src.join(src);
185 if !upstream.exists() {
186 panic!("required wolfcrypt source not found: {}", upstream.display());
187 }
188 upstream
189 };
190 build.file(&path);
191 println!("cargo:rerun-if-changed={}", path.display());
192 }
193 for src in ssl_srcs {
194 let path = ssl_src.join(src);
195 if !path.exists() {
196 panic!("required wolfssl source not found: {}", path.display());
197 }
198 build.file(&path);
199 println!("cargo:rerun-if-changed={}", path.display());
200 }
201
202 build.warnings(false);
203 build.opt_level(2);
204 build.compile("wolfssl");
205
206 println!("cargo:rerun-if-changed={}", user_settings_path.display());
207 println!("cargo:rerun-if-changed={}", patches_dir.display());
208 if self.fips {
209 println!("cargo:rerun-if-changed={}", settings_dir.join("user_settings_fips.h").display());
210 }
211
212 Artifacts {
213 lib_dir: PathBuf::from(env::var("OUT_DIR").unwrap()),
214 include_dir: wolfssl_dir,
215 settings_include_dir: settings_dir,
216 defines,
217 }
218 }
219
220 fn resolve_source_dir(&self) -> PathBuf {
221 if let Some(ref dir) = self.source_dir {
223 if !dir.exists() {
224 panic!("wolfssl source dir does not exist: {}", dir.display());
225 }
226 return dir.clone();
227 }
228
229 if let Ok(dir) = env::var("WOLFSSL_SRC") {
231 if !dir.is_empty() {
232 let path = PathBuf::from(&dir);
233 if !path.exists() {
234 panic!("WOLFSSL_SRC={dir} does not exist");
235 }
236 return path;
237 }
238 }
239
240 let bundled = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("wolfssl");
242 if bundled.join("wolfcrypt/src").exists() {
243 return bundled;
244 }
245
246 if let Some(dir) = Self::find_via_pkg_config() {
248 return dir;
249 }
250
251 panic!(
252 "wolfSSL source not found. Either:\n \
253 - Run: git submodule update --init\n \
254 - Set WOLFSSL_SRC to the path of your wolfssl checkout\n \
255 - Install wolfssl-dev so that pkg-config can find it"
256 );
257 }
258
259 fn find_via_pkg_config() -> Option<PathBuf> {
267 if let Some(prefix) = pkg_config_var("prefix") {
270 let path = PathBuf::from(&prefix);
271 if path.join("wolfcrypt").join("src").exists() {
272 return Some(path);
273 }
274 }
275
276 if let Some(incdir) = pkg_config_var("includedir") {
279 let path = PathBuf::from(&incdir);
280 if let Some(parent) = path.parent() {
282 if parent.join("wolfcrypt").join("src").exists() {
283 return Some(parent.to_path_buf());
284 }
285 }
286 }
287
288 None
289 }
290}
291
292fn pkg_config_var(var: &str) -> Option<String> {
294 let output = std::process::Command::new("pkg-config")
295 .args(["--variable", var, "wolfssl"])
296 .output()
297 .ok()?;
298 if !output.status.success() {
299 return None;
300 }
301 let val = String::from_utf8(output.stdout).ok()?;
302 let val = val.trim();
303 if val.is_empty() {
304 None
305 } else {
306 Some(val.to_string())
307 }
308}
309
310impl Default for Build {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316pub fn parse_defines(path: &Path) -> HashSet<String> {
324 let file = std::fs::File::open(path)
325 .unwrap_or_else(|e| panic!("cannot open {}: {e}", path.display()));
326 let reader = std::io::BufReader::new(file);
327 let mut defines = HashSet::new();
328 for line in reader.lines() {
329 let line = line.expect("read error");
330 let trimmed = line.trim();
331 let Some(rest) = trimmed.strip_prefix('#') else {
332 continue;
333 };
334 let rest = rest.trim_start();
335 let Some(rest) = rest.strip_prefix("define") else {
336 continue;
337 };
338 if !rest.starts_with(|c: char| c.is_ascii_whitespace()) {
339 continue;
340 }
341 let name = rest
342 .trim_start()
343 .split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
344 .next()
345 .unwrap_or("");
346 if !name.is_empty() {
347 defines.insert(name.to_string());
348 }
349 }
350 defines
351}
352
353const CORE_WOLFCRYPT_SOURCES: &[&str] = &[
358 "aes.c",
359 "arc4.c",
360 "asn.c",
361 "blake2b.c",
362 "blake2s.c",
363 "camellia.c",
364 "cmac.c",
365 "coding.c",
366 "cpuid.c",
367 "cryptocb.c",
368 "dsa.c",
369 "error.c",
370 "hash.c",
371 "logging.c",
372 "md4.c",
373 "md5.c",
374 "memory.c",
375 "pkcs7.c",
376 "pkcs12.c",
377 "random.c",
378 "sha.c",
379 "sha256.c",
380 "signature.c",
381 "sp_int.c",
382 "sp_c32.c",
383 "sp_c64.c",
384 "srp.c",
385 "wc_encrypt.c",
386 "wc_port.c",
387 "wolfmath.c",
388];
389
390const FIPS_WOLFCRYPT_SOURCES: &[&str] = &[
391 "fips.c",
392 "fips_test.c",
393 "wolfcrypt_first.c",
394 "wolfcrypt_last.c",
395];
396
397const CRYPTOCB_ONLY_CORE_SOURCES: &[&str] = &[
416 "aes.c",
417 "asn.c",
418 "coding.c",
419 "cpuid.c",
420 "cryptocb.c",
421 "error.c",
422 "hash.c",
423 "logging.c",
424 "memory.c",
425 "random.c",
426 "sha.c",
427 "sha256.c",
428 "signature.c",
429 "wc_encrypt.c",
430 "wc_port.c",
431];
432
433const CRYPTOCB_PURE_CORE_SOURCES: &[&str] = &[
448 "aes.c",
449 "cryptocb.c",
450 "error.c",
451 "hash.c",
452 "logging.c",
453 "memory.c",
454 "random.c",
455 "sha.c",
456 "sha256.c",
457 "wc_port.c",
458];
459
460fn append_cryptocb_only_sources(defines: &HashSet<String>, sources: &mut Vec<&'static str>) {
468 if !defines.contains("NO_HMAC") {
470 sources.push("hmac.c");
471 }
472 if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
474 sources.push("sha512.c");
475 }
476 if defines.contains("HAVE_ECC") {
478 sources.push("ecc.c");
479 }
480 if defines.contains("HAVE_HKDF") {
482 sources.push("kdf.c");
483 }
484 if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
486 sources.push("evp.c");
487 }
488}
489
490fn append_cryptocb_pure_sources(defines: &HashSet<String>, sources: &mut Vec<&'static str>) {
495 if !defines.contains("NO_HMAC") {
497 sources.push("hmac.c");
498 }
499 if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
501 sources.push("sha512.c");
502 }
503 if defines.contains("HAVE_ECC") {
505 sources.push("ecc.c");
506 }
507 }
510
511fn append_conditional_wolfcrypt_sources(defines: &HashSet<String>, sources: &mut Vec<&'static str>) {
512 if defines.contains("HAVE_CHACHA") {
513 sources.push("chacha.c");
514 }
515 if defines.contains("HAVE_CHACHA") && defines.contains("HAVE_POLY1305") {
516 sources.push("chacha20_poly1305.c");
517 }
518 if defines.contains("HAVE_POLY1305") {
519 sources.push("poly1305.c");
520 }
521 if defines.contains("HAVE_ECC") {
522 sources.push("ecc.c");
523 }
524 if defines.contains("HAVE_ED25519") || defines.contains("HAVE_CURVE25519") {
525 sources.push("curve25519.c");
526 sources.push("fe_operations.c");
527 sources.push("ge_operations.c");
528 }
529 if defines.contains("HAVE_ED25519") {
530 sources.push("ed25519.c");
531 }
532 if defines.contains("HAVE_ED448") || defines.contains("HAVE_CURVE448") {
533 sources.push("curve448.c");
534 sources.push("fe_448.c");
535 sources.push("ge_448.c");
536 }
537 if defines.contains("HAVE_ED448") {
538 sources.push("ed448.c");
539 }
540 if !defines.contains("NO_DH") {
541 sources.push("dh.c");
542 }
543 if !defines.contains("NO_RSA") {
544 sources.push("rsa.c");
545 }
546 if !defines.contains("NO_HMAC") {
547 sources.push("hmac.c");
548 }
549 if !defines.contains("NO_DES3") {
550 sources.push("des3.c");
551 }
552 if defines.contains("WOLFSSL_SHA3") {
553 sources.push("sha3.c");
554 }
555 if defines.contains("WOLFSSL_SHA512") || defines.contains("WOLFSSL_SHA384") {
556 sources.push("sha512.c");
557 }
558 if defines.contains("HAVE_DILITHIUM") {
559 sources.push("dilithium.c");
560 }
561 if defines.contains("WOLFSSL_HAVE_MLKEM") {
562 sources.push("wc_mlkem.c");
563 sources.push("wc_mlkem_poly.c");
564 }
565 if defines.contains("HAVE_HKDF") {
566 sources.push("kdf.c");
567 }
568 if defines.contains("HAVE_PBKDF2") {
569 sources.push("pwdbased.c");
570 }
571 if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
572 sources.push("evp.c");
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use std::io::Write;
580
581 #[test]
582 fn parse_defines_basic() {
583 let mut f = tempfile::NamedTempFile::new().unwrap();
584 writeln!(f, "#define HAVE_ECC").unwrap();
585 writeln!(f, "#define HAVE_AES").unwrap();
586 writeln!(f, "#define WOLFSSL_SHA256").unwrap();
587 writeln!(f, "// not a define").unwrap();
588 writeln!(f, "int x = 5;").unwrap();
589 let defs = parse_defines(f.path());
590 assert!(defs.contains("HAVE_ECC"), "missing HAVE_ECC: {:?}", defs);
591 assert!(defs.contains("HAVE_AES"), "missing HAVE_AES: {:?}", defs);
592 assert!(defs.contains("WOLFSSL_SHA256"), "missing WOLFSSL_SHA256: {:?}", defs);
593 assert_eq!(defs.len(), 3, "unexpected defines: {:?}", defs);
594 }
595
596 #[test]
597 fn parse_defines_with_values() {
598 let mut f = tempfile::NamedTempFile::new().unwrap();
599 writeln!(f, "#define WOLFSSL_MAX_STRENGTH 1").unwrap();
600 writeln!(f, "#define HAVE_FIPS_VERSION 5").unwrap();
601 let defs = parse_defines(f.path());
602 assert!(defs.contains("WOLFSSL_MAX_STRENGTH"));
603 assert!(defs.contains("HAVE_FIPS_VERSION"));
604 }
605
606 #[test]
607 fn parse_defines_ignores_non_defines() {
608 let mut f = tempfile::NamedTempFile::new().unwrap();
609 writeln!(f, "#include <stdio.h>").unwrap();
610 writeln!(f, "#ifdef HAVE_ECC").unwrap();
611 writeln!(f, "#endif").unwrap();
612 writeln!(f, "void foo(void);").unwrap();
613 let defs = parse_defines(f.path());
614 assert!(defs.is_empty(), "should have no defines: {:?}", defs);
615 }
616
617 #[test]
618 fn parse_defines_empty_file() {
619 let f = tempfile::NamedTempFile::new().unwrap();
620 let defs = parse_defines(f.path());
621 assert!(defs.is_empty());
622 }
623}
624
625fn ssl_sources(defines: &HashSet<String>) -> &'static [&'static str] {
626 if defines.contains("OPENSSL_EXTRA") || defines.contains("OPENSSL_ALL") {
627 &[
628 "pk.c",
629 "pk_ec.c",
630 "pk_rsa.c",
631 "ssl.c",
632 "ssl_api_pk.c",
633 "ssl_asn1.c",
634 "ssl_bn.c",
635 "ssl_crypto.c",
636 "ssl_load.c",
637 "ssl_misc.c",
638 "ssl_sk.c",
639 ]
640 } else {
641 &[]
642 }
643}