11use std:: path:: { Path , PathBuf } ;
22use std:: process:: Command ;
33
4+ use self :: config:: MbedtlsConfig ;
45use anyhow:: { anyhow, Result } ;
56use bindgen:: Builder ;
67use cmake:: Config ;
78use enumset:: { EnumSet , EnumSetType } ;
89
10+ mod config;
11+
912/// What hooks to install in MbedTLS
1013#[ derive( EnumSetType , Debug ) ]
1114pub enum Hook {
@@ -19,6 +22,61 @@ pub enum Hook {
1922 ExpMod ,
2023}
2124
25+ impl Hook {
26+ const fn work_area_size ( self ) -> Option < usize > {
27+ match self {
28+ Self :: Sha1 => Some ( 208 ) ,
29+ Self :: Sha256 => Some ( 208 ) ,
30+ Self :: Sha512 => Some ( 304 ) ,
31+ Self :: ExpMod => None ,
32+ }
33+ }
34+
35+ /// Returns the header name that MbedTLS expects for this hook, if any.
36+ ///
37+ /// These header names are hard-coded in MbedTLS.
38+ /// We cannot change them to make them less ambiguous.
39+ const fn header_name ( self ) -> Option < & ' static str > {
40+ match self {
41+ Self :: Sha1 => Some ( "sha1_alt.h" ) ,
42+ Self :: Sha256 => Some ( "sha256_alt.h" ) ,
43+ Self :: Sha512 => Some ( "sha512_alt.h" ) ,
44+ Self :: ExpMod => None ,
45+ }
46+ }
47+
48+ /// Returns the config identifier corresponding to this hook.
49+ const fn config_ident ( self ) -> & ' static str {
50+ match self {
51+ Self :: Sha1 => "SHA1_ALT" ,
52+ Self :: Sha256 => "SHA256_ALT" ,
53+ Self :: Sha512 => "SHA512_ALT" ,
54+ Self :: ExpMod => "MPI_EXP_MOD_ALT_FALLBACK" ,
55+ }
56+ }
57+
58+ fn apply_to_config ( self , config : & mut MbedtlsConfig ) {
59+ config. set ( self . config_ident ( ) , true ) ;
60+ if let Some ( work_area_size) = self . work_area_size ( ) {
61+ // This is not relevant for MbedTLS itself, but our
62+ // implementation needs to know the work area size.
63+ let size_ident = format ! ( "{}_WORK_AREA_SIZE" , self . config_ident( ) ) ;
64+ config. add ( & size_ident, work_area_size. to_string ( ) ) ;
65+ }
66+ }
67+ }
68+
69+ /// Compilation artifacts.
70+ ///
71+ /// Returned by [`MbedtlsBuilder::compile`].
72+ pub struct MbedtlsArtifacts {
73+ /// Include directory containing the MbedTLS headers.
74+ pub include : PathBuf ,
75+ /// Directory containing the compiled MbedTLS libraries to link against.
76+ #[ allow( unused, reason = "xtask doesn't use this" ) ]
77+ pub libraries : PathBuf ,
78+ }
79+
2280/// The MbedTLS builder
2381pub struct MbedtlsBuilder {
2482 hooks : EnumSet < Hook > ,
@@ -79,9 +137,14 @@ impl MbedtlsBuilder {
79137 ///
80138 /// Arguments:
81139 /// - `out_path`: Path to write the bindings to
140+ /// - `include_dir`: Path to the directory containing the MbedTLS headers
141+ /// to generate bindings for.
142+ /// - `copy_file_path`: Optional path to copy the generated bindings to
143+ /// (e.g. for caching or pre-generation purposes)
82144 pub fn generate_bindings (
83145 & self ,
84146 out_path : & Path ,
147+ include_dir : & Path ,
85148 copy_file_path : Option < & Path > ,
86149 ) -> Result < PathBuf > {
87150 log:: info!( "Generating MbedTLS bindings" ) ;
@@ -128,16 +191,7 @@ impl MbedtlsBuilder {
128191 . join ( "include.h" )
129192 . to_string_lossy ( ) ,
130193 )
131- . clang_args ( [
132- & format ! (
133- "-I{}" ,
134- canon( & self . crate_root_path. join( "mbedtls" ) . join( "include" ) )
135- ) ,
136- & format ! (
137- "-I{}" ,
138- canon( & self . crate_root_path. join( "gen" ) . join( "include" ) )
139- ) ,
140- ] ) ;
194+ . clang_arg ( format ! ( "-I{}" , canon( include_dir) ) ) ;
141195
142196 if self . short_enums ( ) {
143197 builder = builder. clang_arg ( "-fshort-enums" ) ;
@@ -158,16 +212,6 @@ impl MbedtlsBuilder {
158212 builder = builder. clang_arg ( format ! ( "--target={target}" ) ) ;
159213 }
160214
161- for hook in self . hooks {
162- let def = self . hook_def ( hook) ;
163-
164- builder = builder. clang_arg ( format ! ( "-D{def}" ) ) ;
165-
166- if let Some ( size_def) = self . hook_work_area_size_def ( hook) {
167- builder = builder. clang_arg ( format ! ( "-D{def}_WORK_AREA_SIZE={size_def}" ) ) ;
168- }
169- }
170-
171215 let bindings = builder
172216 . generate ( )
173217 . map_err ( |_| anyhow ! ( "Failed to generate bindings" ) ) ?;
@@ -194,13 +238,86 @@ impl MbedtlsBuilder {
194238 Ok ( bindings_file)
195239 }
196240
197- /// Compile mbedtls
241+ /// Generates the MbedTLS config header and writes it to the specified path.
242+ pub fn generate_config ( & self , out_file : & Path ) -> Result < ( ) > {
243+ let config_source_path = self
244+ . cmake_configurer
245+ . project_path
246+ . join ( "include/mbedtls/mbedtls_config.h" ) ;
247+ let mut config = MbedtlsConfig :: load_from_header ( & config_source_path) ?;
248+ // HACK: For some reason the 'MPI_EXP_MOD_ALT_FALLBACK' config option
249+ // is missing in the `mbedtls_config.h` file, but it's used in
250+ // `bignum.c`.
251+ config. add ( "MPI_EXP_MOD_ALT_FALLBACK" , false ) ;
252+
253+ config
254+ . set ( "DEPRECATED_REMOVED" , true )
255+ . set ( "HAVE_TIME" , false )
256+ . set ( "HAVE_TIME_DATE" , false )
257+ . set ( "PLATFORM_MEMORY" , true )
258+ // We want to provide our own Rust-backed zeroization function.
259+ . set ( "PLATFORM_ZEROIZE_ALT" , true )
260+ . set ( "AES_ROM_TABLES" , true )
261+ . set ( "PK_PARSE_EC_COMPRESSED" , false )
262+ . set ( "GENPRIME" , false )
263+ . set ( "FS_IO" , false )
264+ . set ( "NO_PLATFORM_ENTROPY" , true )
265+ . set ( "PSA_CRYPTO_EXTERNAL_RNG" , true )
266+ . set ( "PSA_KEY_STORE_DYNAMIC" , false )
267+ . set ( "SSL_KEYING_MATERIAL_EXPORT" , false )
268+ . set ( "AESNI_C" , false )
269+ . set ( "AESCE_C" , false )
270+ . set ( "NET_C" , false )
271+ . set ( "PSA_CRYPTO_STORAGE_C" , false )
272+ . set ( "PSA_ITS_FILE_C" , false )
273+ . set ( "SHA3_C" , false )
274+ . set ( "TIMING_C" , false ) ;
275+
276+ self . hooks
277+ . iter ( )
278+ . for_each ( |hook| hook. apply_to_config ( & mut config) ) ;
279+
280+ config. write_to_path ( out_file) ?;
281+ Ok ( ( ) )
282+ }
283+
284+ /// Compile MbedTLS.
285+ ///
286+ /// Uses CMake to compile MbedTLS and prepares the headers for consumption
287+ /// by bindgen and other crates.
288+ /// The tricky part is the MbedTLS configuration. In a CMake-based world,
289+ /// MbedTLS uses "public" compile definitions to bubble up the external
290+ /// config file as well as other relevant configuration options.
291+ /// This simply doesn't work well when using Cargo. To make everyone's life
292+ /// easier, we manually patch up the headers after compiling MbedTLS such
293+ /// that no other compile definitions are necessary when consuming the
294+ /// generated libraries and headers.
198295 ///
199296 /// Arguments:
200297 /// - `out_path`: Path to write the compiled libraries to
201- pub fn compile ( & self , out_path : & Path , copy_path : Option < & Path > ) -> Result < PathBuf > {
298+ pub fn compile ( & self , out_path : & Path , copy_path : Option < & Path > ) -> Result < MbedtlsArtifacts > {
299+ // This directory is temporarily added to the include path when
300+ // compiling MbedTLS. Afterwards, we merge it into MbedTLS's include
301+ // directory for consumption by downstream crates.
302+ let mut staging_include_dir =
303+ StagingIncludeDirectory :: new ( out_path. join ( "staging" ) . join ( "include" ) ) ?;
304+
305+ let config_file_path = staging_include_dir. path . join ( "mbedtls_rs_sys_config.h" ) ;
306+ self . generate_config ( & config_file_path) ?;
307+ // We want our generated config file to fully replace MbedTLS's default
308+ // config file.
309+ staging_include_dir. track ( & config_file_path, "mbedtls/mbedtls_config.h" ) ;
310+
311+ // Copy all the relevant hook headers to the staging include directory.
312+ let hook_header_dir = self . crate_root_path . join ( "gen" ) . join ( "hook" ) ;
313+ self . hooks
314+ . iter ( )
315+ . filter_map ( |hook| hook. header_name ( ) )
316+ . try_for_each ( |name| staging_include_dir. add ( hook_header_dir. join ( name) ) ) ?;
317+
202318 let target_dir = out_path. join ( "mbedtls" ) . join ( "build" ) ;
203319 std:: fs:: create_dir_all ( & target_dir) ?;
320+ let target_include_dir = target_dir. join ( "include" ) ;
204321
205322 let target_lib_dir = out_path. join ( "mbedtls" ) . join ( "lib" ) ;
206323
@@ -220,37 +337,21 @@ impl MbedtlsBuilder {
220337 . define ( "CMAKE_EXPORT_COMPILE_COMMANDS" , "ON" )
221338 // Clang will complain about some documentation formatting in mbedtls
222339 . define ( "MBEDTLS_FATAL_WARNINGS" , "OFF" )
223- . define (
224- "MBEDTLS_CONFIG_FILE" ,
225- self . crate_root_path
226- . join ( "gen" )
227- . join ( "include" )
228- . join ( "config.h" ) ,
229- )
230- . cflag ( format ! (
231- "-I{}" ,
232- self . crate_root_path. join( "gen" ) . join( "include" ) . display( )
233- ) )
234- . cflag ( "-DMBEDTLS_CONFIG_FILE='<config.h>'" )
235- . cxxflag ( "-DMBEDTLS_CONFIG_FILE='<config.h>'" )
340+ . define ( "MBEDTLS_CONFIG_FILE" , config_file_path)
341+ . cflag ( format ! ( "-I{}" , staging_include_dir. path. display( ) ) )
236342 . profile ( "Release" )
237343 . out_dir ( & target_dir) ;
238344
239- for hook in self . hooks {
240- let def = self . hook_def ( hook) ;
241-
242- config. cflag ( format ! ( "-D{def}" ) ) . cxxflag ( format ! ( "-D{def}" ) ) ;
243-
244- if let Some ( size_def) = self . hook_work_area_size_def ( hook) {
245- config
246- . cflag ( format ! ( "-D{def}_WORK_AREA_SIZE={size_def}" ) )
247- . cxxflag ( format ! ( "-D{def}_WORK_AREA_SIZE={size_def}" ) ) ;
248- }
249- }
250-
251345 config. build ( ) ;
252346
253- Ok ( lib_dir. to_path_buf ( ) )
347+ // Now that MbedTLS is compiled, we merge the staging include
348+ // directory into its include directory.
349+ staging_include_dir. merge_into ( & target_include_dir) ?;
350+
351+ Ok ( MbedtlsArtifacts {
352+ include : target_include_dir,
353+ libraries : lib_dir. to_path_buf ( ) ,
354+ } )
254355 }
255356
256357 /// Re-run the build script if the file or directory has changed.
@@ -259,24 +360,6 @@ impl MbedtlsBuilder {
259360 println ! ( "cargo:rerun-if-changed={}" , file_or_dir. display( ) )
260361 }
261362
262- fn hook_def ( & self , hook : Hook ) -> & ' static str {
263- match hook {
264- Hook :: Sha1 => "MBEDTLS_SHA1_ALT" ,
265- Hook :: Sha256 => "MBEDTLS_SHA256_ALT" ,
266- Hook :: Sha512 => "MBEDTLS_SHA512_ALT" ,
267- Hook :: ExpMod => "MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK" ,
268- }
269- }
270-
271- fn hook_work_area_size_def ( & self , hook : Hook ) -> Option < usize > {
272- match hook {
273- Hook :: Sha1 => Some ( 208 ) ,
274- Hook :: Sha256 => Some ( 208 ) ,
275- Hook :: Sha512 => Some ( 304 ) ,
276- _ => None ,
277- }
278- }
279-
280363 /// A heuristics (we don't have anything better) to signal to `bindgen` whether the GCC toolchain
281364 /// for the target emits short enums or not.
282365 ///
@@ -289,6 +372,81 @@ impl MbedtlsBuilder {
289372 }
290373}
291374
375+ /// Helper for managing the staging include directory used when compiling MbedTLS.
376+ struct StagingIncludeDirectory {
377+ /// Path to the staging include directory.
378+ path : PathBuf ,
379+ // List of (source, destination) pairs of files to copy to the staging
380+ // include directory.
381+ // The source is always relative to `path` and the destination is relative
382+ // to the final include directory path.
383+ files : Vec < ( PathBuf , PathBuf ) > ,
384+ }
385+
386+ impl StagingIncludeDirectory {
387+ fn new ( path : PathBuf ) -> Result < Self > {
388+ std:: fs:: create_dir_all ( & path) ?;
389+ Ok ( Self {
390+ path,
391+ files : Vec :: new ( ) ,
392+ } )
393+ }
394+
395+ /// Merges the staging include directory into the final include directory.
396+ ///
397+ /// This should be called after MbedTLS is compiled, and the final include
398+ /// directory is ready to be consumed by downstream crates.
399+ fn merge_into ( & self , include_dir : & Path ) -> Result < ( ) > {
400+ for ( rel_src, rel_dest) in & self . files {
401+ let dest = include_dir. join ( rel_dest) ;
402+ if let Some ( parent) = dest. parent ( ) {
403+ std:: fs:: create_dir_all ( parent) ?;
404+ }
405+ let src = self . path . join ( rel_src) ;
406+ std:: fs:: copy ( src, dest) ?;
407+ }
408+ Ok ( ( ) )
409+ }
410+
411+ /// Adds an external file to be tracked and copied.
412+ ///
413+ /// The file will be copied into the staging include directory under the
414+ /// same name. When merged, the file will be copied to the final include
415+ /// directory, again under the same name.
416+ ///
417+ /// # Panics
418+ ///
419+ /// If `source` is not a path containing a file name.
420+ fn add ( & mut self , source : impl AsRef < Path > ) -> Result < ( ) > {
421+ let source = source. as_ref ( ) ;
422+ let source_file_name = source. file_name ( ) . expect ( "'source' must have a file name" ) ;
423+ let staging_path = self . path . join ( source_file_name) ;
424+ std:: fs:: copy ( source, & staging_path) ?;
425+ self . track ( staging_path, source_file_name) ;
426+ Ok ( ( ) )
427+ }
428+
429+ /// Tracks a file to be copied from the staging directory into the final
430+ /// include directory.
431+ ///
432+ /// # Panics
433+ ///
434+ /// If `source` is not within the staging include directory, or if
435+ /// `destination` is not a relative path.
436+ fn track ( & mut self , source : impl AsRef < Path > , destination : impl AsRef < Path > ) {
437+ let source = source
438+ . as_ref ( )
439+ . strip_prefix ( & self . path )
440+ . expect ( "'source' must be within the staging include directory" ) ;
441+ let destination = destination. as_ref ( ) ;
442+ if destination. is_absolute ( ) {
443+ panic ! ( "'destination' must be a relative path" ) ;
444+ }
445+ self . files
446+ . push ( ( source. to_path_buf ( ) , destination. to_path_buf ( ) ) ) ;
447+ }
448+ }
449+
292450// TODO: Move to `embuild`
293451#[ derive( Debug , Clone ) ]
294452pub struct CMakeConfigurer {
0 commit comments