diff --git a/libuta_rust/.gitignore b/libuta_rust/.gitignore new file mode 100644 index 0000000..816d7e8 --- /dev/null +++ b/libuta_rust/.gitignore @@ -0,0 +1,17 @@ +# Unified Trust Anchor API Rust Wrapper +# +# Copyright (c) Siemens Mobility GmbH, 2026 +# +# Authors: +# Christian P. Feist +# Hermann Seuschek +# +# This work is licensed under the terms of the Apache Software License +# 2.0. See the COPYING file in the top-level directory. +# +# SPDX-FileCopyrightText: Copyright 2026 Siemens +# SPDX-License-Identifier: Apache-2.0 +# +/target +**/*.rs.bk +Cargo.lock diff --git a/libuta_rust/Cargo.toml b/libuta_rust/Cargo.toml new file mode 100644 index 0000000..de01067 --- /dev/null +++ b/libuta_rust/Cargo.toml @@ -0,0 +1,39 @@ +# Unified Trust Anchor API Rust Wrapper +# +# Copyright (c) Siemens Mobility GmbH, 2026 +# +# Authors: +# Christian P. Feist +# Hermann Seuschek +# +# This work is licensed under the terms of the Apache Software License +# 2.0. See the COPYING file in the top-level directory. +# +# SPDX-FileCopyrightText: Copyright 2026 Siemens +# SPDX-License-Identifier: Apache-2.0 +# +[package] +name = "libuta_rust" +license = "Apache-2.0" +description = "Rust wrapper for the Unified Trust Anchor API (libuta)" +repository = "https://github.com/siemens/libuta" +readme = "README.md" +version = "1.2.0" +authors = ["Christian P. Feist ", "Hermann Seuschek "] +categories = ["api-bindings", "cryptography"] +keywords = ["tpm", "trust-anchor", "libuta", "hardware-security", "cryptography"] +edition = "2021" +build = "build.rs" +rust-version = "1.85" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +bindgen = "0.71.1" + +[lib] +path = "src/lib.rs" + +[profile.release] +opt-level = 3 +lto = true diff --git a/libuta_rust/README.md b/libuta_rust/README.md new file mode 100644 index 0000000..6243465 --- /dev/null +++ b/libuta_rust/README.md @@ -0,0 +1,137 @@ +# Rust Bindings for the Unified Trust Anchor API + +This crate is part of the Unified Trust Anchor API (libuta) and provides +lightweight Rust bindings for the C implementation of the library. The +low-level C bindings are generated using +[bindgen](https://rust-lang.github.io/rust-bindgen/introduction.html), while +additional Rust code exposes these low-level interfaces through a more +idiomatic Rust API. + +## Licensing + +This work is licensed under the terms of the Apache License, Version 2.0. +Copyright (c) 2026 Siemens Mobility GmbH. + +* SPDX-FileCopyrightText: Copyright 2026 Siemens +* SPDX-License-Identifier: Apache-2.0 + +## Prerequisites + +* **libuta C library** properly installed: + * Header file `uta.h` must be in the compiler's include path (e.g., `/usr/local/include/uta.h`) + * Shared library must be in the system library path (e.g., `/usr/lib` or `/lib`) + * **Note:** On Debian-based systems, `/usr/local/lib` is not in the default search path. Add it with: `export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH` +* **Rust toolchain** installed (on Debian-based systems: `apt install rustc`) + * Tested with Rust `1.85.0 (4d91de4e4 2025-02-17)` or later +* **LLVM** installed (required by bindgen), see [bindgen requirements](https://rust-lang.github.io/rust-bindgen/requirements.html) + +## Architecture + +.The Rust bindings for libuta use a two-layer architecture. The **lower layer** +(`mod bindings`) provides a direct mapping of the C API to Rust and is primarily +generated using bindgen, exposing all available symbols. The **upper layer** (`mod api`) +builds on top of the lower layer bindings, importing only the necessary components and +presenting them through an idiomatic Rust interface. These high-level bindings +enhance usability by incorporating Rust-style error handling and memory +management. The following diagram illustrates the architecture from the native +libuta library up to its integration in a Rust application: + +``` + +--------------------+ + | application | + | e.g., examples | + | (Rust) | + +--------------------+ + | crate: libuta_rust | + | mod api | + | mod bindings | + | (Rust) | + +--------------------+ + | libuta | + | (C) | + +--------------------+ +``` + +## Wrapper Library + +The directory `libuta_rust` contains the primary wrapper crate designed for use in +Rust applications through an idiomatic Rust interface. Its purpose is to +encapsulate the data structures, types, and functions defined in the low-level +C implementation exposed by the generated bindings. This includes replacing +patterns such as "output parameters" with Rust-native constructs like +`Result` for error handling, and managing context within an instance of the +`UtaApiV1` structure. These design choices significantly reduce the risk of +misuse and provide a safer, more ergonomic API for developers. + +Basic unit tests have been implemented to verify the core functionality of the +library. Please note that the expected outputs for certain functions rely on +default keys provided by the software simulation embedded in libuta. When +executed against a real hardware secure element, these keys differ, resulting +in mismatches between actual and expected values. Consequently, some unit tests +may fail under hardware conditions even though the underlying functionality is +correct. Since the keys used by hardware implementations are unknown in +advance, the test code cannot be adapted to produce matching results. + +**Note:** An `UtaApiV1` instance must always be declared as mutable because +most methods require `&mut self`. While this may initially seem +counterintuitive, it reflects the fact that the object’s internal state changes +frequently, particularly when managing context, such as acquiring or releasing +locks. This behavior is mandated by the underlying C library interface, and +therefore cannot be avoided. + +### File Structure + +``` +├── Cargo.toml # Crate configuration and dependencies +├── build.rs # Build script (invokes bindgen) +├── README.md # This file +├── examples +│   └── all.rs # Example code +└── src/ + ├── lib.rs # High-level idiomatic Rust API + ├── bindings.h # C header input for bindgen + └── bindings.rs # Imports auto-generated C bindings +``` + +### Building and Testing + +```bash +cargo build +cargo test +``` + +### Example Code + +The example code can be found in the examples directory and serves as a starting point for +integrating `libuta_rust` into your own projects. Build and run the example code as follows: + +``` +cargo run --example all +``` + +### Low-Level Bindings + +The file bindings.rc imports the low-level Rust bindings for the libuta +library. These bindings are generated using bindgen and supplemented +with additional code to suppress warnings caused by Rust naming conventions. +The bindgen tool is invoked automatically by Cargo during the build process, so +manual execution is not required. The build steps are implemented in build.rs, +which translates the input header file bindings.h (including the main libuta +header) into Rust bindings accessible through src/bindings.rs. Because this +layer is a direct one-to-one mapping of the C API, it is not intended for +direct use in Rust applications. Instead, it serves as the foundation for the +higher-level wrapper located in the lib.rs file. + +**Note:** The Rust compiler emits warnings regarding the use of 128-bit integers +because **bindings for this data type are not yet fully stable and may break +compatibility in future releases.** These warnings are explicitly suppressed in +the code. Additional warnings related to naming conventions are also +suppressed, as the original libuta naming is preserved to maintain consistency +with the underlying C API. Since this low-level binding layer is not intended +for direct use in Rust applications, but rather serves as the foundation for +the higher-level wrapper, these warnings do not pose a practical issue. + +**Note:** Rust introduces several C-based dependencies through libuta. +Vulnerabilities in any of these linked C libraries could potentially be +exploited, even when accessed via a Rust abstraction layer. At present, these +dependencies cannot be removed because they are integral to libuta. diff --git a/libuta_rust/build.rs b/libuta_rust/build.rs new file mode 100644 index 0000000..c21c20e --- /dev/null +++ b/libuta_rust/build.rs @@ -0,0 +1,44 @@ +/** @file build.rs +* +* @brief This code orchestrates the bindgen tool to create Rust-bindings. +* +* @copyright Copyright (c) Siemens Mobility GmbH, 2026 +* +* @author Christian P. Feist +* @author Hermann Seuschek +* +* @license This work is licensed under the terms of the Apache Software License +* 2.0. See the COPYING file in the top-level directory. +* +* SPDX-FileCopyrightText: Copyright 2026 Siemens +* SPDX-License-Identifier: Apache-2.0 +*/ +extern crate bindgen; +use std::env; +use std::path::PathBuf; + +fn main() { + println!("cargo:rerun-if-changed=src/bindings.h"); + + // The bindgen::Builder is the main entry point to bindgen + let bindings = bindgen::Builder::default() + // The input header we would like to generate bindings for. + .header("src/bindings.h") + // Invalidate the built crate whenever included header files change. + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to file $OUT_DIR/bindings.rs + let out_path = PathBuf::from( + env::var("OUT_DIR").expect("OUT_DIR environment variable not set by cargo") + ); + bindings + .write_to_file(out_path.join("bindgen_bindings.rs")) + .expect("Couldn't write bindings!"); + + // Ensure the crate links against the libuta C library + println!("cargo:rustc-link-lib=uta"); +} diff --git a/libuta_rust/examples/all.rs b/libuta_rust/examples/all.rs new file mode 100644 index 0000000..226767e --- /dev/null +++ b/libuta_rust/examples/all.rs @@ -0,0 +1,58 @@ +/* Unified Trust Anchor API Rust Wrapper +* +* Example code that demonstrates the use of the libuta Rust wrapper. +* +* Copyright (c) Siemens Mobility GmbH, 2026 +* +* Authors: +* Christian P. Feist +* Hermann Seuschek +* +* This work is licensed under the terms of the Apache Software License +* 2.0. See the COPYING file in the top-level directory. +* +* SPDX-FileCopyrightText: Copyright 2026 Siemens +* SPDX-License-Identifier: Apache-2.0 +*/ +extern crate libuta_rust; +use libuta_rust::UtaApiV1; + +fn main() { + + match UtaApiV1::new() { + Ok(ref mut uta) => { + + println!("Execute uta.get_version() ..."); + match uta.get_version() { + Ok(uuid) => println!("Library version: {:?}\n", uuid), + Err(e) => println!("Error calling uta.get_version(), got error code {:?}\n", e.get_rc()) + } + + println!("Execute uta.self_test() ..."); + match uta.self_test() { + Ok(()) => println!("Success!\n"), + Err(e) => println!("Error calling uta.self_test(), got error code {:?}\n", e.get_rc()) + } + + println!("Execute uta.get_device_uuid() ..."); + match uta.get_device_uuid() { + Ok(uuid) => println!("Device UUID bytes: {:?}\n", uuid), + Err(e) => println!("Error calling uta.get_device_uuid(), got error code {:?}\n", e.get_rc()) + } + + println!("Execute uta.derive_key(32, &dv, 0) ..."); + let dv = vec![1u8; 8]; + match uta.derive_key(32, &dv, 0) { + Ok(key) => println!("Derived key: {:?}\n", key), + Err(e) => println!("Error calling uta.derive_key(), got error code {:?}\n", e.get_rc()) + } + + println!("Execute uta.get_random(32) ..."); + match uta.get_random(32) { + Ok(random) => println!("Random bytes: {:?}\n", random), + Err(e) => println!("Error calling uta.get_random(), got error code {:?}\n", e.get_rc()) + } + }, + Err(e) => println!("Error on uta.init(), got error code {:?}", e.get_rc()) + } +} diff --git a/libuta_rust/src/bindings.h b/libuta_rust/src/bindings.h new file mode 100644 index 0000000..ee28d3b --- /dev/null +++ b/libuta_rust/src/bindings.h @@ -0,0 +1,21 @@ +/* Unified Trust Anchor API Rust Wrapper +* +* Includes uta.h from include path +* +* Copyright (c) Siemens Mobility GmbH, 2026 +* +* Authors: +* Christian P. Feist +* Hermann Seuschek +* +* This work is licensed under the terms of the Apache Software License +* 2.0. See the COPYING file in the top-level directory. +* +* SPDX-FileCopyrightText: Copyright 2026 Siemens +* SPDX-License-Identifier: Apache-2.0 +*/ + +/* For this include, we assume that libuta is properly installed on the system +* and that uta.h can be found in the system's include path. +*/ +#include diff --git a/libuta_rust/src/bindings.rs b/libuta_rust/src/bindings.rs new file mode 100644 index 0000000..24bbe27 --- /dev/null +++ b/libuta_rust/src/bindings.rs @@ -0,0 +1,29 @@ +/* Unified Trust Anchor API Rust Wrapper +* +* This code includes the low-level C-bindings generated by bindgen and +* suppresses some warnings caused by naming conventions. +* +* Copyright (c) Siemens Mobility GmbH, 2026 +* +* Authors: +* Christian P. Feist +* Hermann Seuschek +* +* This work is licensed under the terms of the Apache Software License +* 2.0. See the COPYING file in the top-level directory. +* +* SPDX-FileCopyrightText: Copyright 2026 Siemens +* SPDX-License-Identifier: Apache-2.0 +*/ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +// NOTE: Handling unsigned 128 bit integers is not stable yet! +// Be careful, the interface could break in future! +#![allow(improper_ctypes)] + +// Here we include the libuta bindings generated by bindgen +include!(concat!(env!("OUT_DIR"), "/bindgen_bindings.rs")); + diff --git a/libuta_rust/src/lib.rs b/libuta_rust/src/lib.rs new file mode 100644 index 0000000..2689747 --- /dev/null +++ b/libuta_rust/src/lib.rs @@ -0,0 +1,481 @@ +//! Rust Bindings for the Unified Trust Anchor API +//! +//! This crate is part of the Unified Trust Anchor API (libuta) and provides +//! lightweight idiomatic Rust bindings for the C implementation of the library. +//! +//! For code examples please refer to the example directory in the root of the repository. +// +// Copyright (c) Siemens Mobility GmbH, 2026 +// +// Authors: +// Christian P. Feist +// Hermann Seuschek +// +// This work is licensed under the terms of the Apache Software License +// 2.0. See the COPYING file in the top-level directory. +// +// SPDX-FileCopyrightText: Copyright 2026 Siemens +// SPDX-License-Identifier: Apache-2.0 + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +mod bindings; + +pub use crate::api::UtaApiV1; + +pub mod api { + // Note: Here we only use the necessary symbols from the low-level wrapper + use crate::bindings::{UTA_SUCCESS, + UTA_INVALID_KEY_LENGTH, + UTA_INVALID_DV_LENGTH, + UTA_INVALID_KEY_SLOT, + uta_version_t, + uta_context_v1_t, + uta_api_v1_t, + uta_rc, + uta_init_v1 }; + use std::error::Error; + use std::fmt; + + pub const UUID_SZ: usize = 16; + pub type DeviceUuid = [u8; UUID_SZ]; + pub type UtaVersion = uta_version_t; + + // Return codes from UTA operations + #[derive(Debug, PartialEq, Copy, Clone)] + pub enum UtaRc { + SUCCESS, + INVALID_KEY_LENGTH, + INVALID_DV_LENGTH, + INVALID_KEY_SLOT, + TA_ERROR, + UNINITIALIZED_FUNCTION + } + + fn encode_uta_rc(rc: uta_rc) -> UtaRc { + match rc { + UTA_SUCCESS => UtaRc::SUCCESS, + UTA_INVALID_KEY_LENGTH => UtaRc::INVALID_KEY_LENGTH, + UTA_INVALID_DV_LENGTH => UtaRc::INVALID_DV_LENGTH, + UTA_INVALID_KEY_SLOT => UtaRc::INVALID_KEY_SLOT, + _ => UtaRc::TA_ERROR + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct UtaError { + rc: UtaRc + } + + impl UtaError { + fn new(err_rc: uta_rc) -> UtaError { + UtaError {rc: encode_uta_rc(err_rc)} + } + + fn uninitialized() -> UtaError { + UtaError {rc: UtaRc::UNINITIALIZED_FUNCTION} + } + + pub fn get_rc(&self) -> UtaRc { + self.rc + } + } + + impl fmt::Display for UtaError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.rc { + UtaRc::SUCCESS => write!(f, "Operation succeeded"), + UtaRc::INVALID_KEY_LENGTH => write!(f, "Invalid key length specified"), + UtaRc::INVALID_DV_LENGTH => write!(f, "Invalid derivation value length"), + UtaRc::INVALID_KEY_SLOT => write!(f, "Invalid key slot specified"), + UtaRc::TA_ERROR => write!(f, "Trust anchor error occurred"), + UtaRc::UNINITIALIZED_FUNCTION => write!(f, "Function pointer not initialized"), + } + } + } + + impl Error for UtaError { + } + + /// RAII guard that ensures the UTA context is always closed. + /// + /// This guard automatically calls close() when dropped, ensuring + /// proper resource cleanup even if an error occurs. + struct ContextGuard { + context_ptr: *const uta_context_v1_t, + close_fn: unsafe extern "C" fn(*const uta_context_v1_t) -> uta_rc, + } + + impl ContextGuard { + fn new( + context_ptr: *const uta_context_v1_t, + close_fn: unsafe extern "C" fn(*const uta_context_v1_t) -> uta_rc, + ) -> Self { + ContextGuard { + context_ptr, + close_fn, + } + } + } + + impl Drop for ContextGuard { + fn drop(&mut self) { + // SAFETY: The context was successfully opened and close_fn is a valid + // function pointer obtained from the initialized API. The context_ptr + // remains valid for the lifetime of the guard. + unsafe { (self.close_fn)(self.context_ptr) }; + } + } + + /// Main interface to the Unified Trust Anchor API version 1. + /// + /// This struct provides safe, idiomatic Rust access to trust anchor functionality + pub struct UtaApiV1 { + api: uta_api_v1_t, + context: Vec, + } + + impl UtaApiV1 { + + /// Helper method that handles context open/close with RAII pattern. + /// + /// # Safety + /// + /// The context pointer is valid and properly allocated. + fn with_open_context(&mut self, f: F) -> Result + where + F: FnOnce(*const uta_context_v1_t) -> Result, + { + let open_fn = self.api.open.ok_or_else(|| UtaError::uninitialized())?; + let close_fn = self.api.close.ok_or_else(|| UtaError::uninitialized())?; + let context_ptr = self.context.as_mut_ptr() as *mut uta_context_v1_t; + + // SAFETY: context_ptr is valid for the lifetime of self.context, properly + // aligned, and initialized. The open_fn was obtained from the successfully + // initialized API structure and is safe to call with a valid context pointer. + let rc = unsafe { open_fn(context_ptr) }; + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + + // Create guard to ensure close is called even on error + let _guard = ContextGuard::new(context_ptr, close_fn); + + // Execute the provided closure + f(context_ptr) + } + + /// Creates a new UTA API instance. + /// + /// Initializes the UTA library and allocates the necessary context. + /// + /// # Returns + /// + /// * `Ok(UtaApiV1)` - Successfully initialized API instance + /// * `Err(UtaError)` - Initialization failed + /// + pub fn new() -> Result { + let mut api = uta_api_v1_t { + context_v1_size: None, + len_key_max: None, + open: None, + close: None, + derive_key: None, + get_device_uuid: None, + get_random: None, + get_version: None, + self_test: None + }; + + // SAFETY: We pass a valid mutable pointer to a properly initialized + // uta_api_v1_t structure. The C function uta_init_v1 is designed to + // initialize this structure and is safe to call. + let rc = unsafe { uta_init_v1(&mut api as *mut uta_api_v1_t) }; + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + + // SAFETY: The api.context_v1_size function pointer was initialized by uta_init_v1 + // and returns the required context size. + let context_size_fn = api.context_v1_size.ok_or_else(|| UtaError::uninitialized())?; + let context_size = unsafe { (context_size_fn)() }; + Ok(UtaApiV1{api, context: vec![0u8; context_size]}) + } + + /// Derives a cryptographic key from device-specific secrets. + /// + /// # Arguments + /// + /// * `len_key` - Length of the key to derive in bytes + /// * `dv` - Derivation value (label) used to derive the key + /// * `key_slot` - Slot number for key derivation (hardware-specific) + /// + /// # Returns + /// + /// * `Ok(Vec)` - The derived key as a byte vector + /// * `Err(UtaError)` - Key derivation failed (invalid length, slot, or TA error) + /// + pub fn derive_key(&mut self, len_key: usize, dv: &[u8], key_slot: u8) -> Result, UtaError> { + let derive_key_fn = self.api.derive_key.ok_or_else(|| UtaError::uninitialized())?; + + self.with_open_context(|context_ptr| { + let mut key: Vec = vec![0; len_key]; + + // SAFETY: context_ptr is valid and the context is open. key.as_mut_ptr() + // points to a valid, initialized buffer of len_key bytes. dv.as_ptr() points + // to a valid slice of dv.len() bytes. All pointers and lengths are valid. + let rc = unsafe { + derive_key_fn(context_ptr, key.as_mut_ptr(), len_key, dv.as_ptr(), dv.len(), key_slot) + }; + + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + Ok(key) + }) + } + + /// Generates random bytes using the trust anchor's random number generator. + /// + /// # Arguments + /// + /// * `len_random` - Number of random bytes to generate + /// + /// # Returns + /// + /// * `Ok(Vec)` - Random bytes generated by the trust anchor + /// * `Err(UtaError)` - Random generation failed + /// + pub fn get_random(&mut self, len_random: usize) -> Result, UtaError> { + let get_random_fn = self.api.get_random.ok_or_else(|| UtaError::uninitialized())?; + + self.with_open_context(|context_ptr| { + let mut random: Vec = vec![0; len_random]; + + // SAFETY: context_ptr is valid and the context is open. random.as_mut_ptr() + // points to a valid, initialized buffer of random.len() bytes. + let rc = unsafe { get_random_fn(context_ptr, random.as_mut_ptr(), random.len()) }; + + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + Ok(random) + }) + } + + /// Retrieves the unique device identifier (UUID). + /// + /// The UUID is a 16-byte identifier unique to the device. In simulation mode, + /// this is typically derived from `/etc/machine-id`. + /// + /// # Returns + /// + /// * `Ok(DeviceUuid)` - 16-byte device UUID + /// * `Err(UtaError)` - UUID retrieval failed + /// + pub fn get_device_uuid(&mut self) -> Result { + let get_device_uuid_fn = self.api.get_device_uuid.ok_or_else(|| UtaError::uninitialized())?; + + self.with_open_context(|context_ptr| { + let mut uuid = [0u8; UUID_SZ]; + + // SAFETY: context_ptr is valid and the context is open. uuid.as_mut_ptr() + // points to a valid, initialized buffer of UUID_SZ (16) bytes. + let rc = unsafe { get_device_uuid_fn(context_ptr, uuid.as_mut_ptr()) }; + + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + Ok(uuid) + }) + } + + /// Performs a self-test of the trust anchor. + /// + /// Verifies that the trust anchor is functioning correctly. + /// + /// # Returns + /// + /// * `Ok(())` - Self-test passed + /// * `Err(UtaError)` - Self-test failed + /// + pub fn self_test(&mut self) -> Result<(), UtaError> { + let self_test_fn = self.api.self_test.ok_or_else(|| UtaError::uninitialized())?; + + self.with_open_context(|context_ptr| { + // SAFETY: context_ptr is valid and the context is open. The self_test + // function only requires a valid open context pointer. + let rc = unsafe { self_test_fn(context_ptr) }; + + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + Ok(()) + }) + } + + /// Retrieves version information for the UTA library and device. + /// + /// Returns information about the trust anchor type and version numbers. + /// + /// # Returns + /// + /// * `Ok(UtaVersion)` - Version information including type, major, minor, and patch + /// * `Err(UtaError)` - Version retrieval failed + /// + pub fn get_version(&mut self) -> Result { + let get_version_fn = self.api.get_version.ok_or_else(|| UtaError::uninitialized())?; + + self.with_open_context(|context_ptr| { + let mut version = UtaVersion { + uta_type: 0, + major: 0, + minor: 0, + patch: 0, + }; + + // SAFETY: context_ptr is valid and the context is open. The version pointer + // points to a valid, properly aligned uta_version_t structure that will be + // written to by the C function. + let rc = unsafe { get_version_fn(context_ptr, &mut version as *mut uta_version_t) }; + + if rc != UTA_SUCCESS { + return Err(UtaError::new(rc)); + } + Ok(version) + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::api::*; + use std::fs::File; + use std::io::Read; + + #[test] + fn get_key_ok() { + let mut uta = UtaApiV1::new(); + match uta { + Ok(ref mut api) => { + let dv = vec![1u8; 8]; + let ref_key = vec![ 141, 243, 3, 60, + 242, 217, 255, 175, + 133, 63, 236, 185, + 124, 72, 113, 96, + 25, 85, 33, 157, + 11, 96, 53, 225, + 189, 46, 160, 242, + 172, 53, 62, 102 ]; + let res = api.derive_key(32, &dv, 0); + + match res { + Ok(res_key) => assert_eq!(res_key, ref_key), + Err(e) => panic!("Error in get_key, returned {:?}", e) + } + }, + Err(e) => panic!("Error getting UTA API, returned {:?}", e) + } + } + + #[test] + fn get_random_ok() { + let mut uta = UtaApiV1::new(); + match uta { + Ok(ref mut api) => { + let res_rnd = api.get_random(32); + match res_rnd { + Ok(rnd) => { + assert_eq!(rnd.len(), 32); + let first = rnd[0]; + // Check that not all bytes are the same (very basic randomness check) + assert!(rnd.iter().any(|&b| b != first)); + + }, + Err(e) => panic!("Error in get_random, returned {:?}", e) + } + }, + Err(e) => panic!("Error getting UTA API, returned {:?}", e) + } + } + + #[test] + fn get_device_uuid_ok() { + let mut uta = UtaApiV1::new(); + match uta { + Ok(ref mut api) => { + // The UTA_SIM implementation retrieves the device UUID from /etc/machine-id. + // Accordingly, this test reads the same file and compares the resulting value. + let mut file = match File::open("/etc/machine-id") { + Ok(f) => f, + Err(e) => panic!("Error opening /etc/machine-id: {}", e) + }; + + let mut machine_id = String::new(); + if let Err(e) = file.read_to_string(&mut machine_id) { + panic!("Error reading /etc/machine-id: {}", e) + } + machine_id = machine_id.trim().to_string(); + + if machine_id.len() != UUID_SZ * 2 { + panic!("Invalid machine-id length: expected 32 hex characters, got {}", machine_id.len()); + } + + let mut expected_uuid: DeviceUuid = [0u8; UUID_SZ]; + for i in 0..UUID_SZ { + let byte_str = &machine_id[i * 2..i * 2 + 2]; + match u8::from_str_radix(byte_str, 16) { + Ok(b) => expected_uuid[i] = b, + Err(e) => panic!("Failed to parse hex at position {}: {}", i, e) + } + } + + let res_uuid = api.get_device_uuid(); + match res_uuid { + Ok(uuid) => assert_eq!(uuid, expected_uuid), + Err(e) => panic!("Error in get_device_uuid, returned {:?}", e) + } + }, + Err(e) => panic!("Error getting UTA API, returned {:?}", e) + } + } + + #[test] + fn self_test_ok() { + let mut uta = UtaApiV1::new(); + match uta { + Ok(ref mut api) => api.self_test().expect("Error in get_self_test"), + Err(e) => panic!("Error getting UTA API, returned {:?}", e) + } + } + + #[test] + fn get_version_ok() { + let mut uta = UtaApiV1::new(); + match uta { + Ok(ref mut api) => { + let ref_version = UtaVersion { + uta_type: 0, + major: 1, + minor: 2, + patch: 0, + }; + let res_ver = api.get_version(); + + match res_ver { + Ok(ver) => { + assert_eq!(ver.uta_type, ref_version.uta_type); + assert_eq!(ver.major, ref_version.major); + assert_eq!(ver.minor, ref_version.minor); + assert_eq!(ver.patch, ref_version.patch); + }, + Err(e) => panic!("Error in get_version, returned {:?}", e) + } + }, + Err(e) => panic!("Error getting UTA API, returned {:?}", e) + } + } +}