From e62aff3511238e8843bd2a2a303b9d7311daeeec Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 25 Mar 2025 10:57:33 +0100 Subject: [PATCH 1/2] feat(config): update OTLP endpoint and protocol handling - Change default OTLP endpoint to match the HTTP/JSON spec. - Add dynamic protocol-based exporter configuration. - Support both gRPC and HTTP/JSON transports for logging. Signed-off-by: Harald Hoyer --- crates/teepot/src/config/mod.rs | 36 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/teepot/src/config/mod.rs b/crates/teepot/src/config/mod.rs index 0132c62..100317a 100644 --- a/crates/teepot/src/config/mod.rs +++ b/crates/teepot/src/config/mod.rs @@ -147,8 +147,8 @@ impl Default for TelemetryOtlpConfig { fn default() -> Self { Self { enable: true, - endpoint: "http://127.0.0.1:4317".to_string(), - protocol: "grpc".to_string(), + endpoint: "http://127.0.0.1:4318/v1/logs".to_string(), + protocol: "http/json".to_string(), } } } @@ -269,15 +269,31 @@ fn init_telemetry( ) .build(); - // Configure the OTLP exporter - let logging_provider = SdkLoggerProvider::builder() - .with_batch_exporter( - opentelemetry_otlp::LogExporter::builder() - .with_tonic() + // Parse the protocol from the configuration + let protocol = protocol_from_string(&config.otlp.protocol)?; + + // Configure the OTLP exporter based on the protocol + let exporter_builder = opentelemetry_otlp::LogExporter::builder(); + + // Choose transport based on protocol + let exporter = match protocol { + opentelemetry_otlp::Protocol::Grpc => exporter_builder + .with_tonic() + .with_endpoint(&config.otlp.endpoint) + .with_protocol(protocol) + .build()?, + opentelemetry_otlp::Protocol::HttpBinary | opentelemetry_otlp::Protocol::HttpJson => { + exporter_builder + .with_http() .with_endpoint(&config.otlp.endpoint) - .with_protocol(protocol_from_string(&config.otlp.protocol)?) - .build()?, - ) + .with_protocol(protocol) + .build()? + } + }; + + // Configure the logging provider with the exporter + let logging_provider = SdkLoggerProvider::builder() + .with_batch_exporter(exporter) .with_resource(resource) .build(); From fa2ecee4bddbb91e005e9e766fa9af03172e76fb Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Wed, 26 Mar 2025 14:31:16 +0100 Subject: [PATCH 2/2] feat(sha384-extend): enhance SHA384 extend utility with padding and tests - Refactor `sha384-extend` to include digest padding and validation. - Add `extend_sha384` function for hex-string-based digest extension. - Introduce comprehensive test coverage for edge cases and errors. Signed-off-by: Harald Hoyer --- Cargo.lock | 1 + bin/sha384-extend/Cargo.toml | 1 + bin/sha384-extend/src/main.rs | 163 ++++++++++++++++++++++++++++++---- bin/tdx-extend/src/main.rs | 6 +- crates/teepot/src/lib.rs | 13 +-- crates/teepot/src/util/mod.rs | 83 +++++++++++++++++ 6 files changed, 234 insertions(+), 33 deletions(-) create mode 100644 crates/teepot/src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index fdb34a6..d5628a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5189,6 +5189,7 @@ dependencies = [ "clap 4.5.30", "hex", "sha2", + "teepot", ] [[package]] diff --git a/bin/sha384-extend/Cargo.toml b/bin/sha384-extend/Cargo.toml index f9c58e6..85e8974 100644 --- a/bin/sha384-extend/Cargo.toml +++ b/bin/sha384-extend/Cargo.toml @@ -12,3 +12,4 @@ anyhow.workspace = true clap.workspace = true hex.workspace = true sha2.workspace = true +teepot.workspace = true diff --git a/bin/sha384-extend/src/main.rs b/bin/sha384-extend/src/main.rs index 2835a3e..f2ace5e 100644 --- a/bin/sha384-extend/src/main.rs +++ b/bin/sha384-extend/src/main.rs @@ -1,7 +1,26 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2024 Matter Labs +// Copyright (c) 2024-2025 Matter Labs -//! Extend the TDX measurement +//! A tool for extending SHA384 digests, commonly used in TPM and TDX operations +//! +//! # Overview +//! This utility implements the extend operation used in Trusted Platform Module (TPM) +//! Platform Configuration Registers (PCRs) and Intel Trust Domain Extensions (TDX) +//! Runtime Measurement Registers (RTMRs). The extend operation combines two SHA384 +//! digests by concatenating and then hashing them. +//! +//! # Usage +//! ```shell +//! sha384-extend [--base ] +//! ``` +//! Where: +//! - `extend-value`: SHA384 digest in hex format to extend with +//! - `initial-value`: Optional initial SHA384 digest in hex format (defaults to "00") +//! +//! # Example +//! ```shell +//! sha384-extend --base 01 26bb0c +//! ``` #![deny(missing_docs)] #![deny(clippy::all)] @@ -9,31 +28,139 @@ use anyhow::{Context, Result}; use clap::Parser; use sha2::Digest; +use teepot::util::pad; -/// Calculate a TDX rtmr or TPM pcr sha384 value by extending it +/// Calculate e.g. a TDX RTMR or TPM PCR SHA384 digest by extending it with another #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Arguments { - /// digest in hex to extend with - #[arg(long)] + /// The SHA384 digest (in hex format) to extend the base value with. + /// Must be a valid hex string that can be padded to 48 bytes (384 bits). extend: String, - /// initial digest in hex - #[arg(long)] - digest: String, + + /// The initial SHA384 digest (in hex format) to extend from. + /// Must be a valid hex string that can be padded to 48 bytes (384 bits). + #[arg(long, default_value = "00", required = false)] + base: String, +} + +/// Extends a base SHA384 digest with another digest +/// +/// # Arguments +/// * `base` - Base hex string to extend from +/// * `extend` - Hex string to extend with +/// +/// # Returns +/// * `Result` - The resulting SHA384 digest as a hex string +/// +/// # Examples +/// ``` +/// let result = extend_sha384("00", "aa").unwrap(); +/// ``` +pub fn extend_sha384(base: &str, extend: &str) -> Result { + let mut hasher = sha2::Sha384::new(); + + hasher.update(pad::<48>(&hex::decode(base).context(format!( + "Failed to decode base digest '{}' - expected hex string", + base + ))?)?); + + hasher.update(pad::<48>(&hex::decode(extend).context(format!( + "Failed to decode extend digest '{}' - expected hex string", + extend + ))?)?); + + Ok(hex::encode(hasher.finalize())) } fn main() -> Result<()> { let args = Arguments::parse(); - - // Parse the digest string as a hex array - let extend_bytes = hex::decode(&args.extend).context("Invalid digest format")?; - let mut digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?; - - digest_bytes.extend(extend_bytes); - - let bytes = sha2::Sha384::digest(&digest_bytes); - let hex = hex::encode(bytes); - + let hex = extend_sha384(&args.base, &args.extend)?; println!("{hex}"); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_BASE: &str = "00"; + const TEST_EXTEND: &str = "d3a665eb2bf8a6c4e6cee0ccfa663ee4098fc4903725b1823d8d0316126bb0cb"; + const EXPECTED_RESULT: &str = "971fb52f90ec98a234301ca9b8fc30b613c33e3dd9c0cc42dcb8003d4a95d8fb218b75baf028b70a3cabcb947e1ca453"; + + const EXPECTED_RESULT_00: &str = "f57bb7ed82c6ae4a29e6c9879338c592c7d42a39135583e8ccbe3940f2344b0eb6eb8503db0ffd6a39ddd00cd07d8317"; + + #[test] + fn test_extend_sha384_with_test_vectors() { + let result = extend_sha384(TEST_BASE, TEST_EXTEND).unwrap(); + assert_eq!( + result, EXPECTED_RESULT, + "SHA384 extend result didn't match expected value" + ); + + // Test with empty base + let result = extend_sha384("", TEST_EXTEND).unwrap(); + assert_eq!( + result, EXPECTED_RESULT, + "SHA384 extend result didn't match expected value" + ); + + // Test with empty base + let result = extend_sha384("", "").unwrap(); + assert_eq!( + result, EXPECTED_RESULT_00, + "SHA384 extend result didn't match expected value" + ); + } + + #[test] + fn test_extend_sha384_with_invalid_base() { + // Test with invalid hex in base + let result = extend_sha384("not_hex", TEST_EXTEND); + assert!(result.is_err(), "Should fail with invalid base hex"); + + // Test with odd length hex string + let result = extend_sha384("0", TEST_EXTEND); + assert!(result.is_err(), "Should fail with odd-length hex string"); + } + + #[test] + fn test_extend_sha384_with_invalid_extend() { + // Test with invalid hex in extend + let result = extend_sha384(TEST_BASE, "not_hex"); + assert!(result.is_err(), "Should fail with invalid extend hex"); + + // Test with odd length hex string + let result = extend_sha384(TEST_BASE, "0"); + assert!(result.is_err(), "Should fail with odd-length hex string"); + } + + #[test] + fn test_extend_sha384_with_oversized_input() { + // Create a hex string that's too long (more than 48 bytes when decoded) + let oversized = "00".repeat(49); // 49 bytes when decoded + + let result = extend_sha384(TEST_BASE, &oversized); + assert!(result.is_err(), "Should fail with oversized extend value"); + + let result = extend_sha384(&oversized, TEST_EXTEND); + assert!(result.is_err(), "Should fail with oversized base value"); + } + + #[test] + fn test_extend_sha384_idempotent() { + // Test that extending with the same values produces the same result + let result1 = extend_sha384(TEST_BASE, TEST_EXTEND).unwrap(); + let result2 = extend_sha384(TEST_BASE, TEST_EXTEND).unwrap(); + assert_eq!(result1, result2, "Same inputs should produce same output"); + } + + #[test] + fn test_extend_sha384_case_sensitivity() { + // Test that upper and lower case hex strings produce the same result + let upper_extend = TEST_EXTEND.to_uppercase(); + let result1 = extend_sha384(TEST_BASE, TEST_EXTEND).unwrap(); + let result2 = extend_sha384(TEST_BASE, &upper_extend).unwrap(); + assert_eq!(result1, result2, "Case should not affect the result"); + } +} diff --git a/bin/tdx-extend/src/main.rs b/bin/tdx-extend/src/main.rs index 1a4056c..5a43f59 100644 --- a/bin/tdx-extend/src/main.rs +++ b/bin/tdx-extend/src/main.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright (c) 2024 Matter Labs +// Copyright (c) 2024-2025 Matter Labs //! Extend the TDX measurement @@ -10,8 +10,8 @@ use anyhow::{Context, Result}; use clap::Parser; use teepot::{ log::{setup_logging, LogLevelParser}, - pad, tdx::rtmr::TdxRtmrEvent, + util::pad, }; use tracing::{error, level_filters::LevelFilter}; @@ -40,7 +40,7 @@ fn main_with_error() -> Result<()> { // Parse the digest string as a hex array let digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?; - let extend_data: [u8; 48] = pad(&digest_bytes); + let extend_data: [u8; 48] = pad(&digest_bytes).context("Invalid digest length")?; // Extend the TDX measurement with the extend data TdxRtmrEvent::default() diff --git a/crates/teepot/src/lib.rs b/crates/teepot/src/lib.rs index 1860eb3..89a7ab2 100644 --- a/crates/teepot/src/lib.rs +++ b/crates/teepot/src/lib.rs @@ -16,15 +16,4 @@ pub mod quote; pub mod server; pub mod sgx; pub mod tdx; - -/// pad a byte slice to a fixed sized array -pub fn pad(input: &[u8]) -> [u8; T] { - let mut output = [0; T]; - let len = input.len(); - if len > T { - output.copy_from_slice(&input[..T]); - } else { - output[..len].copy_from_slice(input); - } - output -} +pub mod util; diff --git a/crates/teepot/src/util/mod.rs b/crates/teepot/src/util/mod.rs new file mode 100644 index 0000000..1951142 --- /dev/null +++ b/crates/teepot/src/util/mod.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2023-2025 Matter Labs + +//! utility functions. + +use thiserror::Error; + +/// Errors that can occur when padding byte vectors to fixed-size arrays. +#[derive(Error, Debug)] +pub enum PadError { + /// Indicates that the input vector's length exceeds the target array size. + /// + /// # Fields + /// * `expected` - The target size of the array in bytes + /// * `actual` - The actual length of the input vector in bytes + /// + /// # Example + /// ```rust + /// # use teepot::util::{pad, PadError}; + /// let long_input = vec![1, 2, 3, 4]; + /// let result = pad::<2>(&long_input); + /// assert!(matches!( + /// result, + /// Err(PadError::InputTooLong { expected: 2, actual: 4 }) + /// )); + /// ``` + #[error("Input vector is too long - expected {expected} bytes, got {actual}")] + InputTooLong { + /// The expected length (target array size) + expected: usize, + /// The actual length of the provided input + actual: usize, + }, +} + +/// Pad a byte vector to a fixed-size array by appending zeros. If the input is longer +/// than the target size, returns an error. +/// +/// # Arguments +/// * `input` - Input byte vector to be padded with zeros +/// +/// # Returns +/// * `Result<[u8; T], PadError>` - A fixed-size array of length T, or a PadError if input is too long +/// +/// # Errors +/// Returns `PadError::InputTooLong` if the input vector length exceeds the target array size T, +/// containing both the expected and actual sizes +/// +/// # Examples +/// ```rust +/// # use teepot::util::{pad, PadError}; +/// let input = vec![1, 2, 3]; +/// let padded: [u8; 5] = pad(&input)?; +/// assert_eq!(padded, [1, 2, 3, 0, 0]); +/// +/// // Error case: input too long +/// let long_input = vec![1, 2, 3, 4, 5, 6]; +/// assert!(matches!( +/// pad::<5>(&long_input), +/// Err(PadError::InputTooLong { expected: 5, actual: 6 }) +/// )); +/// # Ok::<(), PadError>(()) +/// ``` +/// +/// # Type Parameters +/// * `T` - The fixed size of the output array in bytes +pub fn pad(input: &[u8]) -> Result<[u8; T], PadError> { + let mut output = [0u8; T]; + match input.len().cmp(&T) { + std::cmp::Ordering::Greater => Err(PadError::InputTooLong { + expected: T, + actual: input.len(), + }), + std::cmp::Ordering::Equal => { + output.copy_from_slice(input); + Ok(output) + } + std::cmp::Ordering::Less => { + output[..input.len()].copy_from_slice(input); + Ok(output) + } + } +}