diff --git a/.gitmodules b/.gitmodules index e2296e6..9c82f86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ +[submodule "mbedtls"] + path = mbedtls + url = https://github.com/Mbed-TLS/mbedtls.git [submodule "open62541"] path = open62541 url = https://github.com/open62541/open62541.git diff --git a/Cargo.toml b/Cargo.toml index d3df9ec..389a8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,13 @@ include = [ "src/", "wrapper.c", "wrapper.h", + "mbedtls/**/CMakeLists.txt", + "mbedtls/3rdparty/everest/", + "mbedtls/3rdparty/p256-m/", + "mbedtls/cmake/", + "mbedtls/include/", + "mbedtls/library/", + "mbedtls/pkgconfig/", "open62541/**/CMakeLists.txt", "open62541/arch/", "open62541/deps/", @@ -53,6 +60,10 @@ cc = "1.0.83" cmake = "0.1.50" version_check = "0.9.5" +[features] +default = [] +mbedtls = [] + [lints.rust] future_incompatible = { level = "warn", priority = -1 } keyword_idents = { level = "warn", priority = -1 } diff --git a/build.rs b/build.rs index 1721a37..c7bc5a6 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ +#![allow(clippy::panic)] // Panic only during build time. + use std::{ env, fs::File, @@ -29,9 +31,22 @@ const LEGACY_EXTERN_PATTERN: &str = r#"extern "C" {"#; const LEGACY_EXTERN_REPLACEMENT: &str = r#"unsafe extern "C" {"#; fn main() { + let with_mbedtls = + matches!(env::var("CARGO_FEATURE_MBEDTLS"), Ok(mbedtls) if !mbedtls.is_empty()); + let with_openssl = + matches!(env::var("CARGO_FEATURE_OPENSSL"), Ok(openssl) if !openssl.is_empty()); + // For now, we do not actually announce feature flag `openssl` in `Cargo.toml`. + let encryption = match (with_mbedtls, with_openssl) { + (false, false) => None, + (true, false) => Some(Encryption::MbedTls), + (false, true) => Some(Encryption::OpenSsl), + _ => panic!("conflicting encryption feature flags, only one must be enabled"), + }; + let src = env::current_dir().expect("should get current directory"); // Get derived paths relative to `src`. + let src_mbedtls = src.join("mbedtls"); let src_open62541 = src.join("open62541"); let src_wrapper_c = src.join("wrapper.c"); let src_wrapper_h = src.join("wrapper.h"); @@ -41,32 +56,14 @@ fn main() { println!("cargo:rerun-if-changed={}", src_wrapper_c.display()); println!("cargo:rerun-if-changed={}", src_wrapper_h.display()); - // Build bundled copy of `open62541` with CMake. - let mut cmake = cmake::Config::new(src_open62541); - cmake - // Use explicit paths here to avoid generating files where we do not expect them below. - .define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE) - // Some systems (Fedora) default to `lib64/` instead of `lib/` for 64-bit libraries. - .define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB) - // Explicitly set C99 standard to force Windows variants of `vsnprintf()` to conform to this - // standard. This also matches the expected (or supported) C standard of `open62541` itself. - .define("C_STANDARD", "99") - // Python defaults to creating bytecode in `__pycache__` directories. During build, this may - // happen when the tool `nodeset_compiler` is called. When we package a crate, builds should - // never modify files outside of `OUT_DIR`, so we disable the cache to prevent this. - .env("PYTHONDONTWRITEBYTECODE", "1"); - - if matches!(env::var("CARGO_CFG_TARGET_ENV"), Ok(env) if env == "musl") { - let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("should have CARGO_CFG_TARGET_ARCH"); - // We require includes from the Linux headers which are not provided automatically when musl - // is targeted (see https://github.com/open62541/open62541/issues/6360). - // TODO: Remove this when `open62541` enables us to build without including Linux headers. - cmake - .cflag("-idirafter/usr/include") - .cflag(format!("-idirafter/usr/include/{arch}-linux-gnu")); - } + // Build related encryption libraries. + let encryption_dst = encryption.map(|encryption| match encryption { + Encryption::MbedTls => prepare_mbedtls(src_mbedtls), + Encryption::OpenSsl => prepare_openssl(), + }); - let dst = cmake.build(); + // Build `open62541` library. + let dst = build_open62541(src_open62541, encryption_dst.as_ref()); // Get derived paths relative to `dst`. let dst_include = dst.join(CMAKE_INCLUDE); @@ -81,6 +78,15 @@ fn main() { println!("cargo:rustc-link-search={}", dst_lib.display()); println!("cargo:rustc-link-lib={LIB_BASE}"); + // For encryption support enabled, we add the libraries that have to be used as dependencies for + // the final build artifact. + // + // Note: These must come _after_ adding `LIB_BASE` above for linker to resolve dependencies. + if let Some(encryption_dst) = encryption_dst { + encryption_dst.rustc_link_search(); + encryption_dst.rustc_link_lib(); + } + let out = PathBuf::from(env::var("OUT_DIR").expect("should have OUT_DIR")); // Get derived paths relative to `out`. @@ -165,6 +171,131 @@ fn main() { .compile(LIB_EXT); } +#[derive(Debug)] +enum Encryption { + MbedTls, + OpenSsl, +} + +#[derive(Debug)] +enum EncryptionDst { + MbedTls { + dst: PathBuf, + libs: Vec<&'static str>, + }, + OpenSsl { + search: Option<&'static str>, + libs: Vec<&'static str>, + }, +} + +impl EncryptionDst { + const fn search(&self) -> Option<&'static str> { + match self { + EncryptionDst::MbedTls { .. } => None, + EncryptionDst::OpenSsl { search, .. } => *search, + } + } + + fn libs(&self) -> &[&'static str] { + match self { + EncryptionDst::MbedTls { libs, .. } | EncryptionDst::OpenSsl { libs, .. } => libs, + } + } + + fn rustc_link_search(&self) { + if let Some(search) = self.search() { + println!("cargo:rustc-link-search={search}"); + } + } + + fn rustc_link_lib(&self) { + for lib in self.libs() { + println!("cargo:rustc-link-lib={lib}"); + } + } +} + +fn prepare_mbedtls(src: PathBuf) -> EncryptionDst { + // Build bundled copy of `mbedtls` with CMake. + let mut cmake = cmake::Config::new(src); + cmake + // Use explicit paths here to avoid generating files where we do not expect them below. + .define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE) + // Some systems (Fedora) default to `lib64/` instead of `lib/` for 64-bit libraries. + .define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB) + // Use same C99 standard as is used for building `open62541`. + .define("C_STANDARD", "99") + // Skip building binary programs unnecessary for linking library. + .define("ENABLE_PROGRAMS", "OFF") + // Skip building test programs that we are not going to run anyway. + .define("ENABLE_TESTING", "OFF"); + + let dst = cmake.build(); + + // The set of MbedTLS libraries that must be linked to work with `open62541` has been taken from + // . + let libs = vec!["mbedtls", "mbedx509", "mbedcrypto"]; + + EncryptionDst::MbedTls { dst, libs } +} + +fn prepare_openssl() -> EncryptionDst { + // For macOS, we require the precise link path because we expect OpenSSL to be provided by using + // Homebrew. + let search = matches!(env::var("CARGO_CFG_TARGET_OS"), Ok(os) if os == "macos") + .then_some("/opt/homebrew/opt/openssl/lib"); + + let libs = vec!["ssl", "crypto"]; + + EncryptionDst::OpenSsl { search, libs } +} + +fn build_open62541(src: PathBuf, encryption: Option<&EncryptionDst>) -> PathBuf { + // Build bundled copy of `open62541` with CMake. + let mut cmake = cmake::Config::new(src); + cmake + // Use explicit paths here to avoid generating files where we do not expect them below. + .define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE) + // Some systems (Fedora) default to `lib64/` instead of `lib/` for 64-bit libraries. + .define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB) + // Explicitly set C99 standard to force Windows variants of `vsnprintf()` to conform to this + // standard. This also matches the expected (or supported) C standard of `open62541` itself. + .define("C_STANDARD", "99") + // Python defaults to creating bytecode in `__pycache__` directories. During build, this may + // happen when the tool `nodeset_compiler` is called. When we package a crate, builds should + // never modify files outside of `OUT_DIR`, so we disable the cache to prevent this. + .env("PYTHONDONTWRITEBYTECODE", "1"); + + if matches!(env::var("CARGO_CFG_TARGET_ENV"), Ok(env) if env == "musl") { + let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("should have CARGO_CFG_TARGET_ARCH"); + // We require includes from the Linux headers which are not provided automatically when musl + // is targeted (see https://github.com/open62541/open62541/issues/6360). + // TODO: Remove this when `open62541` enables us to build without including Linux headers. + cmake + .cflag("-idirafter/usr/include") + .cflag(format!("-idirafter/usr/include/{arch}-linux-gnu")); + } + + // When enabled, we build `open62541` with encryption support. This changes the library and also + // changes the resulting `bindings.rs`. + let encryption = match encryption { + None => "OFF", + Some(EncryptionDst::MbedTls { dst, .. }) => { + // Skip auto-detection and use explicit folders from `mbedtls` build. + cmake + .define("MBEDTLS_FOLDER_INCLUDE", dst.join(CMAKE_INCLUDE)) + .define("MBEDTLS_FOLDER_LIBRARY", dst.join(CMAKE_LIB)); + "MBEDTLS" + } + Some(EncryptionDst::OpenSsl { .. }) => "OPENSSL", + }; + + cmake.define("UA_ENABLE_ENCRYPTION", encryption); + + cmake.build() +} + #[derive(Debug)] struct CustomCallbacks { /// Destination of CMake build of `open62541`. diff --git a/mbedtls b/mbedtls new file mode 160000 index 0000000..107ea89 --- /dev/null +++ b/mbedtls @@ -0,0 +1 @@ +Subproject commit 107ea89daaefb9867ea9121002fbbdf926780e98