From 89a476e320787f0d85dff2d4466969afb118645a Mon Sep 17 00:00:00 2001 From: Jesper Brynolf Date: Fri, 27 Oct 2023 23:10:57 +0200 Subject: [PATCH] Adds support for non pkg-config tpm2-tss installs. - Adds support for specifying path to the tpm2-tss libraries and header files using the environment variable ```TPM2_TSS_PATH```. The specified path is expected to have the following layout: | -> | | -> include | | -> tss2 | | -> tss2_esys.h | | | -> lib | | -> tss2_esys.lib (or .so) Signed-off-by: Jesper Brynolf --- README.md | 2 +- tss-esapi-sys/Cargo.toml | 2 +- tss-esapi-sys/build.rs | 385 +++++++++++++++++++++--------- tss-esapi/tests/Dockerfile-fedora | 2 +- 4 files changed, 274 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 024e161a..1b3016be 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ At the moment we test (via CI) and support the following Rust compiler versions: * On Ubuntu we test with: - The latest stable compiler version, as accessible through `rustup`. - The 1.66 compiler version. -* On Fedora we test with the compiler version included with the Fedora 35 release. +* On Fedora we test with the compiler version included with the Fedora 36 release. If you need support for other versions of the compiler, get in touch with us to see what we can do! diff --git a/tss-esapi-sys/Cargo.toml b/tss-esapi-sys/Cargo.toml index 9e5d22e8..3b7b0dcb 100644 --- a/tss-esapi-sys/Cargo.toml +++ b/tss-esapi-sys/Cargo.toml @@ -13,7 +13,7 @@ documentation = "https://docs.rs/crate/tss-esapi-sys" links = "tss2-esys" [build-dependencies] -bindgen = { version = "0.66.1", optional = true } +bindgen = { version = "0.69.1", optional = true } pkg-config = "0.3.18" target-lexicon = "0.12.0" cfg-if = "1.0.0" diff --git a/tss-esapi-sys/build.rs b/tss-esapi-sys/build.rs index aa183b85..ffb0c00b 100644 --- a/tss-esapi-sys/build.rs +++ b/tss-esapi-sys/build.rs @@ -12,51 +12,54 @@ fn main() { cfg_if::cfg_if! { if #[cfg(feature = "generate-bindings")] { let installation = tpm2_tss::Installation::probe(true); + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + installation.generate_bindings(&out_dir.join("tss_esapi_bindings.rs")); } else { - let installation = tpm2_tss::Installation::probe(false); + target::ensure_supported(); + let _ = tpm2_tss::Installation::probe(false); } } } pub mod target { use std::str::FromStr; - use target_lexicon::{Architecture, OperatingSystem, Triple}; + use target_lexicon::{Architecture, OperatingSystem, Triple}; const TARGET_ENV_VAR_NAME: &str = "TARGET"; /// Ensures that the `TARGET` is valid for cross compilation. pub fn ensure_supported() { - let target = Triple::from_str( - &std::env::var(TARGET_ENV_VAR_NAME) - .unwrap_or_else(|_| { - panic!("Missing environment variable `{}`.", TARGET_ENV_VAR_NAME); - })) - .expect("Failed to parse target triple."); + let target = Triple::from_str(&std::env::var(TARGET_ENV_VAR_NAME).unwrap_or_else(|_| { + panic!("Missing environment variable `{}`.", TARGET_ENV_VAR_NAME); + })) + .expect("Failed to parse target triple."); match (target.architecture, target.operating_system) { - (Architecture::Arm(_), OperatingSystem::Linux) | - (Architecture::Aarch64(_), OperatingSystem::Linux) | - (Architecture::X86_64, OperatingSystem::Darwin) | - (Architecture::X86_64, OperatingSystem::Linux) | - (Architecture::X86_64, OperatingSystem::Windows) => {} + (Architecture::Arm(_), OperatingSystem::Linux) + | (Architecture::Aarch64(_), OperatingSystem::Linux) + | (Architecture::X86_64, OperatingSystem::Darwin) + | (Architecture::X86_64, OperatingSystem::Linux) + | (Architecture::X86_64, OperatingSystem::Windows) => {} (arch, os) => { panic!( - "Compilation target (architecture, OS) tuple ({}, {}) is not part of the supported tuples. Please compile with the \"generate-bindings\" feature or add support for your platform.", - arch, - os); + "Compilation target (architecture, OS) tuple ({}, {}) is not part \ + of the supported tuples. Please compile with the \"generate-bindings\" \ + feature or add support for your platform.", + arch, os + ); } } } } pub mod tpm2_tss { - use std::path::{PathBuf, Path}; use semver::{Version, VersionReq}; + use std::path::{Path, PathBuf}; const MINIMUM_VERSION: &str = "2.4.6"; const PATH_ENV_VAR_NAME: &str = "TPM2_TSS_PATH"; /// The installed tpm2-tss libraries that are of /// interest. pub struct Installation { - tss2_sys: Library, + _tss2_sys: Library, tss2_esys: Library, tss2_tctildr: Library, tss2_mu: Library, @@ -65,65 +68,109 @@ pub mod tpm2_tss { impl Installation { /// Probes the system for an installation. - pub fn probe(with_headers: bool) -> Self { + pub fn probe(with_header_files: bool) -> Self { let install_path = Installation::installation_path_from_env_var(); Installation { - tss2_sys: Library::probe_required("tss2-sys", install_path.as_ref()), - tss2_esys: Library::probe_required("tss2-esys", install_path.as_ref()), - tss2_tctildr: Library::probe_required("tss2-tctildr", install_path.as_ref()), - tss2_mu: Library::probe_required("tss2-mu", install_path.as_ref()), - tss2_tcti_tbs: Library::probe_optional("tss2-tcti-tbs", install_path.as_ref()), + _tss2_sys: Library::probe_required( + "tss2-sys", + install_path.as_ref(), + with_header_files, + false, + ), + tss2_esys: Library::probe_required( + "tss2-esys", + install_path.as_ref(), + with_header_files, + true, + ), + tss2_tctildr: Library::probe_required( + "tss2-tctildr", + install_path.as_ref(), + with_header_files, + false, + ), + tss2_mu: Library::probe_required( + "tss2-mu", + install_path.as_ref(), + with_header_files, + false, + ), + tss2_tcti_tbs: Library::probe_optional( + "tss2-tcti-tbs", + install_path.as_ref(), + with_header_files, + ), } } - /// Generates bindings for the Installation. - pub fn generate_bindings(&self, esapi_out: &Path) { - self.bindgen_builder() - .generate() - .expect("Unable to generate bindings to TSS2 ESYS APIs.") - .write_to_file(esapi_out) - .expect("Couldn't write ESYS bindings!"); - } + cfg_if::cfg_if! { + if #[cfg(feature = "generate-bindings")] { - /// The bindgen builder to use. - fn bindgen_builder(&self) -> bindgen::Builder { - let mut builder = bindgen::Builder::default() - .size_t_is_usize(false) - .clang_arg(self.tss2_esys.include_dir_arg()) - .clang_arg(self.tss2_tctildr.include_dir_arg()) - .clang_arg(self.tss2_mu.include_dir_arg()) - .rustfmt_bindings(true) - .header(self.tss2_esys.header_file_arg()) - .header(self.tss2_tctildr.header_file_arg()) - .header(self.tss2_mu.header_file_arg()) - //See this issue: https://github.com/parallaxsecond/rust-cryptoki/issues/12 - .blocklist_type("max_align_t") - .generate_comments(false) - .derive_default(true); - if let Some(tss2_tcti_tbs) = &self.tss2_tcti_tbs { - builder = builder - .clang_arg(tss2_tcti_tbs.include_dir_arg()) - .header(tss2_tcti_tbs.header_file_arg()); + /// Generates bindings for the Installation. + pub fn generate_bindings(&self, esapi_out: &Path) { + self.bindgen_builder() + .generate() + .expect("Unable to generate bindings to TSS2 ESYS APIs.") + .write_to_file(esapi_out) + .expect("Couldn't write ESYS bindings!"); + } + + /// The bindgen builder to use. + fn bindgen_builder(&self) -> bindgen::Builder { + let mut builder = bindgen::Builder::default() + .size_t_is_usize(false) + .clang_arg(self.tss2_esys.include_dir_arg()) + .clang_arg(self.tss2_tctildr.include_dir_arg()) + .clang_arg(self.tss2_mu.include_dir_arg()) + .formatter(bindgen::Formatter::Rustfmt) + .header(self.tss2_esys.header_file_arg()) + .header(self.tss2_tctildr.header_file_arg()) + .header(self.tss2_mu.header_file_arg()) + //See this issue: https://github.com/parallaxsecond/rust-cryptoki/issues/12 + .generate_comments(false) + .blocklist_type("max_align_t") + // Needed for windows + .blocklist_type("IMAGE_TLS_DIRECTORY") + .blocklist_type("PIMAGE_TLS_DIRECTORY") + .blocklist_type("IMAGE_TLS_DIRECTORY64") + .blocklist_type("PIMAGE_TLS_DIRECTORY64") + .blocklist_type("_IMAGE_TLS_DIRECTORY64") + .blocklist_type("MONITORINFOEX") + .blocklist_type("MONITORINFOEXA") + .blocklist_type("MONITORINFOEXW") + .blocklist_type("tagMONITORINFOEXA") + .blocklist_type("tagMONITORINFOEXW") + .blocklist_type("LPMONITORINFOEX") + .blocklist_type("LPMONITORINFOEXA") + .blocklist_type("LPMONITORINFOEXW") + .derive_default(true); + if let Some(tss2_tcti_tbs) = &self.tss2_tcti_tbs { + builder = builder + .clang_arg(tss2_tcti_tbs.include_dir_arg()) + .header(tss2_tcti_tbs.header_file_arg()); + } + builder + } } - builder } - /// Retrieves the installation path from the environment variable and validates it. fn installation_path_from_env_var() -> Option<(PathBuf, String)> { - std::env::var(PATH_ENV_VAR_NAME).map_or_else(|e| { - match e { + std::env::var(PATH_ENV_VAR_NAME).map_or_else( + |e| match e { std::env::VarError::NotUnicode(invalid_value) => { - panic!("Invalid `{}` env var: `{:?}`.", PATH_ENV_VAR_NAME, invalid_value); - }, + panic!( + "Invalid `{}` env var: `{:?}`.", + PATH_ENV_VAR_NAME, invalid_value + ); + } std::env::VarError::NotPresent => None, - } - }, |var| { - Some(Installation::ensure_valid_installation_path(var)) - }) + }, + |var| Some(Installation::ensure_valid_installation_path(var)), + ) } /// Ensures that the installation path is valid. - /// + /// /// # Details /// In order to be considered valid the following /// requirements needs to be full filled: @@ -131,145 +178,255 @@ pub mod tpm2_tss { /// 2. Sub directories `include` and `lib` must exist. /// 3. A `VERSION` file must be present in the directory and it needs to be /// be specifying a version that is greater then the minimum supported version. - /// + /// /// # Arguments /// env_var - The value of the environment variable that contains the installation path. - /// + /// /// # Returns /// A tuple containing the validated installation path and the version associated with it. fn ensure_valid_installation_path(env_var: String) -> (PathBuf, String) { let install_path = PathBuf::from(env_var); if !install_path.is_dir() { - panic!("`{}` specifies a path `{}`, that does not exist", PATH_ENV_VAR_NAME, install_path.to_string_lossy()); + panic!( + "`{}` specifies a path `{}`, that does not exist", + PATH_ENV_VAR_NAME, + install_path.to_string_lossy() + ); } if !install_path.join("include").is_dir() { - panic!("`{}` specifies a path `{}`, that does not contain an `include` directory", PATH_ENV_VAR_NAME, install_path.to_string_lossy()); + panic!( + "`{}` specifies a path `{}`, that does not contain an `include` directory", + PATH_ENV_VAR_NAME, + install_path.to_string_lossy() + ); } if !install_path.join("lib").is_dir() { - panic!("`{}` specifies a path `{}`, that does not contain an `lib` directory", PATH_ENV_VAR_NAME, install_path.to_string_lossy()); + panic!( + "`{}` specifies a path `{}`, that does not contain an `lib` directory", + PATH_ENV_VAR_NAME, + install_path.to_string_lossy() + ); } let version_str = std::fs::read_to_string(install_path.join("VERSION")) .expect("Failed to read VERSION file."); let version = Version::parse(version_str.as_str()) .expect("Failed to parse the content of the VERSION file."); - let min_version_req_str = format!(">={}",MINIMUM_VERSION); + let min_version_req_str = format!(">={}", MINIMUM_VERSION); let min_version_req = VersionReq::parse(&min_version_req_str) .expect("Failed to parse minimum version requirement."); if !min_version_req.matches(&version) { - panic!("Minimum supported version is {}, found installed version {}", MINIMUM_VERSION, version_str); + panic!( + "Minimum supported version is {}, found installed version {}", + MINIMUM_VERSION, version_str + ); } (install_path, version_str) } } - /// Struct holding the information for a library. struct Library { - header_file: PathBuf, + header_file: Option, version: String, name: String, } impl Library { - /// Probes the different options for a required library. - /// + /// Probes the different options for a required library. + /// /// # Arguments - /// lib_name - The name of the library. - /// install_path - Optional path and version of installation. + /// `lib_name` - The name of the library. + /// `install_path` - Optional path and version of installation. + /// `with_header_files` - Flag indicating if header files are required. /// /// # Returns /// The detected installed library. - /// # Panics + /// # Panics /// - If the library is not found. - pub fn probe_required(lib_name: &str, install_path: Option<&(PathBuf, String)>) -> Self { - Self::probe_optional(lib_name, install_path) - .unwrap_or_else(|| panic!("Failed to find {} library.", lib_name)) + pub fn probe_required( + lib_name: &str, + install_path: Option<&(PathBuf, String)>, + with_header_files: bool, + report_version: bool, + ) -> Self { + Self::probe_optional(lib_name, install_path, with_header_files).map_or_else( + || panic!("Failed to find {} library.", lib_name), + |lib| { + if report_version { + println!("cargo:version={}", lib.version); + } + lib + }, + ) } - /// Probes the different options for an optional library. - /// + /// Probes the different options for an optional library. + /// /// # Arguments - /// lib_name - The name of the library. - /// install_path - Optional path and version of installation. + /// `lib_name` - The name of the library. + /// `install_path` - Optional path and version of installation. + /// `with_header_files` - Flag indicating if header files are required. /// /// # Returns /// The detected installed library or None if no library was found. - pub fn probe_optional(lib_name: &str, install_path: Option<&(PathBuf, String)>) -> Option { + pub fn probe_optional( + lib_name: &str, + install_path: Option<&(PathBuf, String)>, + with_header_files: bool, + ) -> Option { install_path .map(|(path, version)| { - Self::probe_install_path(lib_name, &path, &version) + Self::probe_install_path(lib_name, path, version, with_header_files) }) - .or_else(|| Self::probe_pkg_config_optional(lib_name)) + .or_else(|| Self::probe_pkg_config_optional(lib_name, with_header_files)) } /// The include dir `clang_arg` bindgen builder argument. - /// + /// /// # Panics + /// - If the library was probe without requiring header files. /// - If the library specifies a header file does not have a parent directory. /// - If the library specifies a header file path that contain invalid utf-8 characters. pub fn include_dir_arg(&self) -> String { self.header_file + .as_ref() + .unwrap_or_else(|| panic!("No header file present for `{}`.", self.name)) .parent() .unwrap_or_else(|| panic!("Inconsistent `{}` header file path.", self.name)) .as_os_str() .to_str() - .map_or_else(|| { - panic!("Error converting OsString to &str when processing `{}` include dir.", self.name); - }, |v| format!("-I{}", v)) + .map_or_else( + || { + panic!( + "Error converting OsString to &str when processing `{}` include dir.", + self.name + ); + }, + |v| format!("-I{}", v), + ) } /// The header file path to a `header` bindgen argument. - /// + /// /// # Panics /// - If the library specifies a header file path that contain invalid utf-8 characters. pub fn header_file_arg(&self) -> &str { - self.header_file - .as_os_str() - .to_str() - .unwrap_or_else(|| panic!("Error converting OsString to &str when processing `{}` include dir.", self.name)) - } + self.header_file.as_ref().map_or_else( + || { + panic!("No header file present for `{}`.", self.name); + }, + |v| { + v.as_os_str().to_str().unwrap_or_else(|| { + panic!( + "Error converting OsString to &str when processing `{}` include dir.", + self.name + ) + }) + }, + ) + } /// Probe the system for an optional library using pkg-config. - fn probe_pkg_config_optional(lib_name: &str) -> Option { + /// + /// # Args + /// `lib_name` - The name of the library. + /// `with_header_files` - Flag indicating if header files are required. + fn probe_pkg_config_optional(lib_name: &str, with_header_files: bool) -> Option { pkg_config::Config::new() .atleast_version(MINIMUM_VERSION) .probe(lib_name) .ok() .map(|pkg_config| { - let header_file = pkg_config.include_paths[0] - .join("tss2") - .join(lib_name.replace("-", "_") + ".h"); - if !header_file.is_file() { - panic!("Header file `{}` does not exist.", header_file.to_string_lossy()); + if !with_header_files { + return Self { + header_file: None, + version: pkg_config.version, + name: lib_name.to_string(), + }; + } + let include_path = pkg_config.include_paths[0].join("tss2"); + let header_file = Self::header_file(lib_name, &include_path, with_header_files); + Self { + header_file, + version: pkg_config.version, + name: lib_name.to_string(), } - Self {header_file, version: pkg_config.version, name: lib_name.to_string()} }) } /// Probe the install path for a library. - /// + /// /// # Arguments /// `lib_name` - The name of the library to probe for. /// `path` - The path to probe for the library. /// `version` - The version of the library. - /// + /// /// # Returns /// A `Library` object containing the information retrieved. - /// + /// /// # Panics /// - If no `.lib` file for the library was found. /// - If no `.h` file for the library was found. - fn probe_install_path(lib_name: &str, path: &Path, version: &str) -> Self { - let file_name = PathBuf::from(lib_name.replace("-", "_")); - let lib_file = path.join("lib").join(file_name.with_extension("lib")); + fn probe_install_path( + lib_name: &str, + path: &Path, + version: &str, + with_header_files: bool, + ) -> Self { + let lib_path = path.join("lib"); + Self::ensure_lib_file_exist(lib_name, &lib_path); + + let include_path = path.join("include/tss2"); + let header_file = Self::header_file(lib_name, &include_path, with_header_files); + + Self { + header_file, + version: version.to_string(), + name: lib_name.to_string(), + } + } + + fn ensure_lib_file_exist(lib_name: &str, lib_path: &Path) { + let lib_file = lib_path.join(PathBuf::from(lib_name).with_extension("lib")); if !lib_file.is_file() { - panic!("Lib file `{}`, does not exist.", lib_file.to_string_lossy()); + panic!( + "Lib name: {}\n Lib file `{}`, does not exist.", + lib_name, + lib_file.to_string_lossy() + ); + } + } + + /// Creates a PathBuf object for the header file. + /// + /// # Args + /// `lib_name` - Name of the library. + /// `include_path` - The include path to the header file. + /// `with_header_files` - Flag indicating if header files are required. + /// + /// # Returns + /// An optional PathBuf object. + /// + /// # Panics + /// - If `with_header_files` but the combination of `file_name` and `include_path` + /// does not point to an existing file. + fn header_file( + lib_name: &str, + include_path: &Path, + with_header_files: bool, + ) -> Option { + if !with_header_files { + return None; } - let header_file = path.join("include").join(file_name.with_extension("h")); - if !header_file.is_file() { - panic!("Header file `{}`, does not exist.", header_file.to_string_lossy()); + let file_name = PathBuf::from(lib_name.replace('-', "_")); + let header_file = include_path.join(file_name.with_extension("h")); + if with_header_files && !header_file.is_file() { + panic!( + "Header file `{}`, does not exist.", + header_file.to_string_lossy() + ); } - Self {header_file, version: version.to_string(), name: lib_name.to_string()} + Some(header_file) } } } diff --git a/tss-esapi/tests/Dockerfile-fedora b/tss-esapi/tests/Dockerfile-fedora index f52d95da..79a9cf0a 100644 --- a/tss-esapi/tests/Dockerfile-fedora +++ b/tss-esapi/tests/Dockerfile-fedora @@ -1,4 +1,4 @@ -FROM fedora:35 +FROM fedora:36 RUN dnf install -y \ tpm2-tss-devel tpm2-abrmd tpm2-tools \