mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 23:23:57 +02:00
Merge branch 'main' into teepot_vault
This commit is contained in:
commit
f03a8ba643
7 changed files with 260 additions and 43 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5189,6 +5189,7 @@ dependencies = [
|
||||||
"clap 4.5.30",
|
"clap 4.5.30",
|
||||||
"hex",
|
"hex",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"teepot",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -12,3 +12,4 @@ anyhow.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
|
teepot.workspace = true
|
||||||
|
|
|
@ -1,7 +1,26 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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 <extend-value> [--base <initial-value>]
|
||||||
|
//! ```
|
||||||
|
//! 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(missing_docs)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
@ -9,31 +28,139 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use sha2::Digest;
|
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)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
/// digest in hex to extend with
|
/// The SHA384 digest (in hex format) to extend the base value with.
|
||||||
#[arg(long)]
|
/// Must be a valid hex string that can be padded to 48 bytes (384 bits).
|
||||||
extend: String,
|
extend: String,
|
||||||
/// initial digest in hex
|
|
||||||
#[arg(long)]
|
/// The initial SHA384 digest (in hex format) to extend from.
|
||||||
digest: String,
|
/// 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<String>` - 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<String> {
|
||||||
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Arguments::parse();
|
let args = Arguments::parse();
|
||||||
|
let hex = extend_sha384(&args.base, &args.extend)?;
|
||||||
// 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);
|
|
||||||
|
|
||||||
println!("{hex}");
|
println!("{hex}");
|
||||||
Ok(())
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2024 Matter Labs
|
// Copyright (c) 2024-2025 Matter Labs
|
||||||
|
|
||||||
//! Extend the TDX measurement
|
//! Extend the TDX measurement
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use teepot::{
|
use teepot::{
|
||||||
log::{setup_logging, LogLevelParser},
|
log::{setup_logging, LogLevelParser},
|
||||||
pad,
|
|
||||||
tdx::rtmr::TdxRtmrEvent,
|
tdx::rtmr::TdxRtmrEvent,
|
||||||
|
util::pad,
|
||||||
};
|
};
|
||||||
use tracing::{error, level_filters::LevelFilter};
|
use tracing::{error, level_filters::LevelFilter};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ fn main_with_error() -> Result<()> {
|
||||||
|
|
||||||
// Parse the digest string as a hex array
|
// Parse the digest string as a hex array
|
||||||
let digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?;
|
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
|
// Extend the TDX measurement with the extend data
|
||||||
TdxRtmrEvent::default()
|
TdxRtmrEvent::default()
|
||||||
|
|
|
@ -147,8 +147,8 @@ impl Default for TelemetryOtlpConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enable: true,
|
enable: true,
|
||||||
endpoint: "http://127.0.0.1:4317".to_string(),
|
endpoint: "http://127.0.0.1:4318/v1/logs".to_string(),
|
||||||
protocol: "grpc".to_string(),
|
protocol: "http/json".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,15 +269,31 @@ fn init_telemetry(
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Configure the OTLP exporter
|
// Parse the protocol from the configuration
|
||||||
let logging_provider = SdkLoggerProvider::builder()
|
let protocol = protocol_from_string(&config.otlp.protocol)?;
|
||||||
.with_batch_exporter(
|
|
||||||
opentelemetry_otlp::LogExporter::builder()
|
// 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_tonic()
|
||||||
.with_endpoint(&config.otlp.endpoint)
|
.with_endpoint(&config.otlp.endpoint)
|
||||||
.with_protocol(protocol_from_string(&config.otlp.protocol)?)
|
.with_protocol(protocol)
|
||||||
.build()?,
|
.build()?,
|
||||||
)
|
opentelemetry_otlp::Protocol::HttpBinary | opentelemetry_otlp::Protocol::HttpJson => {
|
||||||
|
exporter_builder
|
||||||
|
.with_http()
|
||||||
|
.with_endpoint(&config.otlp.endpoint)
|
||||||
|
.with_protocol(protocol)
|
||||||
|
.build()?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure the logging provider with the exporter
|
||||||
|
let logging_provider = SdkLoggerProvider::builder()
|
||||||
|
.with_batch_exporter(exporter)
|
||||||
.with_resource(resource)
|
.with_resource(resource)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
@ -14,15 +14,4 @@ pub mod prover;
|
||||||
pub mod quote;
|
pub mod quote;
|
||||||
pub mod sgx;
|
pub mod sgx;
|
||||||
pub mod tdx;
|
pub mod tdx;
|
||||||
|
pub mod util;
|
||||||
/// pad a byte slice to a fixed sized array
|
|
||||||
pub fn pad<const T: usize>(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
|
|
||||||
}
|
|
||||||
|
|
83
crates/teepot/src/util/mod.rs
Normal file
83
crates/teepot/src/util/mod.rs
Normal file
|
@ -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<const T: usize>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue