From 4be65b9593e0c7fdbf2c8c4ee7aacc67f8b1b808 Mon Sep 17 00:00:00 2001 From: Steph Sinyakov Date: Thu, 2 Apr 2026 18:44:41 +0200 Subject: [PATCH 1/2] chore: move attestation-provider-server crate from proxy repo --- Cargo.lock | 262 ++++++++++++++++++ Cargo.toml | 1 + crates/attestation-provider-server/Cargo.toml | 25 ++ crates/attestation-provider-server/build.rs | 50 ++++ crates/attestation-provider-server/src/lib.rs | 109 ++++++++ .../attestation-provider-server/src/main.rs | 103 +++++++ 6 files changed, 550 insertions(+) create mode 100644 crates/attestation-provider-server/Cargo.toml create mode 100644 crates/attestation-provider-server/build.rs create mode 100644 crates/attestation-provider-server/src/lib.rs create mode 100644 crates/attestation-provider-server/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 204efe6..2503f31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,12 +164,56 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -283,6 +327,23 @@ dependencies = [ "x509-parser", ] +[[package]] +name = "attestation-provider-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "attestation", + "axum", + "clap", + "hex", + "parity-scale-codec", + "reqwest", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "attested-tls" version = "0.0.1" @@ -315,6 +376,58 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "az-cvm-vtpm" version = "0.7.4" @@ -629,6 +742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -637,8 +751,22 @@ version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -662,6 +790,12 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12170080f3533d6f09a19f81596f836854d0fa4867dc32c8172b8474b4e9de61" +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "configfs-tsm" version = "0.0.2" @@ -1574,6 +1708,7 @@ dependencies = [ "http 1.4.0", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -1810,6 +1945,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1955,6 +2096,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "mbox" version = "0.7.1" @@ -2055,6 +2211,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2156,6 +2321,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -3077,6 +3248,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3126,6 +3308,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3212,6 +3403,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3334,6 +3531,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.47" @@ -3494,6 +3700,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -3556,6 +3763,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -3677,6 +3927,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.22.0" @@ -3689,6 +3945,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 8067bbd..e29e3e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/attested-tls", "crates/nested-tls", "crates/attestation", + "crates/attestation-provider-server", ] [workspace.lints.rust] diff --git a/crates/attestation-provider-server/Cargo.toml b/crates/attestation-provider-server/Cargo.toml new file mode 100644 index 0000000..64e990a --- /dev/null +++ b/crates/attestation-provider-server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "attestation-provider-server" +description = "An HTTP server which provides attestations" +version = "0.1.0" +edition = "2024" +license = "MIT" +publish = false +repository = "https://github.com/flashbots/attested-tls" + +[lints] +workspace = true + +[dependencies] +attestation = { path = "../attestation" } +tokio.workspace = true + +axum = "0.8.6" +clap = { version = "4.5.51", features = ["derive", "env"] } +anyhow = "1.0.100" +hex = "0.4.3" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json"] } +parity-scale-codec = "3.7.5" +reqwest = { version = "0.12.23", default-features = false } +thiserror = "2.0.17" \ No newline at end of file diff --git a/crates/attestation-provider-server/build.rs b/crates/attestation-provider-server/build.rs new file mode 100644 index 0000000..a233f4a --- /dev/null +++ b/crates/attestation-provider-server/build.rs @@ -0,0 +1,50 @@ +use std::{env, path::PathBuf, process::Command}; + +/// Run a git command and return trimmed stdout +fn git_output(args: &[&str]) -> Option { + let output = Command::new("git").args(args).output().ok()?; + if !output.status.success() { + return None; + } + + let value = String::from_utf8(output.stdout).ok()?; + let value = value.trim(); + if value.is_empty() { None } else { Some(value.to_owned()) } +} + +/// Resolve version as tag then branch-sha then sha then unknown +fn compute_git_rev() -> String { + if let Some(tag) = git_output(&["describe", "--tags", "--exact-match"]) { + return tag; + } + + let Some(sha) = git_output(&["rev-parse", "--short=12", "HEAD"]) else { + return "unknown".to_owned(); + }; + + match git_output(&["rev-parse", "--abbrev-ref", "HEAD"]) { + Some(branch) if branch != "HEAD" => format!("{branch}@{sha}"), + _ => sha, + } +} + +/// Emit build rerun hints for git metadata changes +fn emit_git_rerun_hints() { + let manifest_dir = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_owned())); + + for git_dir in [manifest_dir.join(".git"), manifest_dir.join("..").join(".git")] { + if git_dir.exists() { + println!("cargo:rerun-if-changed={}", git_dir.join("HEAD").display()); + println!("cargo:rerun-if-changed={}", git_dir.join("packed-refs").display()); + break; + } + } + + println!("cargo:rerun-if-env-changed=GIT_DIR"); +} + +fn main() { + println!("cargo:rustc-env=GIT_REV={}", compute_git_rev()); + emit_git_rerun_hints(); +} diff --git a/crates/attestation-provider-server/src/lib.rs b/crates/attestation-provider-server/src/lib.rs new file mode 100644 index 0000000..244290e --- /dev/null +++ b/crates/attestation-provider-server/src/lib.rs @@ -0,0 +1,109 @@ +use std::net::SocketAddr; + +pub use attestation::AttestationGenerator; +use attestation::{AttestationError, AttestationExchangeMessage, AttestationVerifier}; +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use parity_scale_codec::{Decode, Encode}; +use tokio::net::TcpListener; + +#[derive(Clone)] +struct SharedState { + attestation_generator: AttestationGenerator, +} + +/// An HTTP server which provides attestations +pub async fn attestation_provider_server( + listener: TcpListener, + attestation_generator: AttestationGenerator, +) -> anyhow::Result<()> { + let app = axum::Router::new() + .route("/attest/{input_data}", axum::routing::get(get_attest)) + .with_state(SharedState { attestation_generator }); + + axum::serve(listener, app).await?; + + Ok(()) +} + +/// Handler for the GET `/attest/{input_data}` route +/// Input data should be 64 bytes hex +async fn get_attest( + State(shared_state): State, + Path(input_data): Path, +) -> Result<(StatusCode, Vec), ServerError> { + let input_data: [u8; 64] = + hex::decode(input_data)?.try_into().map_err(|_| ServerError::InvalidLength)?; + + let attestation = + shared_state.attestation_generator.generate_attestation(input_data).await?.encode(); + + Ok((StatusCode::OK, attestation)) +} + +/// A client helper which makes a request to `/attest` +pub async fn attestation_provider_client( + server_addr: SocketAddr, + attestation_verifier: AttestationVerifier, +) -> anyhow::Result { + let input_data = [0; 64]; + let response = reqwest::get(format!("http://{server_addr}/attest/{}", hex::encode(input_data))) + .await? + .bytes() + .await?; + + let remote_attestation_message = AttestationExchangeMessage::decode(&mut &response[..])?; + let remote_attestation_type = remote_attestation_message.attestation_type; + + println!("Remote attestation type: {remote_attestation_type}"); + + attestation_verifier.verify_attestation(remote_attestation_message.clone(), input_data).await?; + + Ok(remote_attestation_message) +} + +#[derive(Debug, thiserror::Error)] +enum ServerError { + #[error(transparent)] + InvalidHex(#[from] hex::FromHexError), + #[error("Input data must be 64 bytes")] + InvalidLength, + #[error(transparent)] + AttestationFailed(#[from] AttestationError), +} + +impl IntoResponse for ServerError { + fn into_response(self) -> Response { + let (status, message) = match &self { + ServerError::InvalidHex(_) | ServerError::InvalidLength => { + (StatusCode::BAD_REQUEST, self.to_string()) + } + ServerError::AttestationFailed(_) => { + tracing::error!("{self:?}"); + (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string()) + } + }; + (status, message).into_response() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_attestation_provider_server() { + let attestation_generator = AttestationGenerator::with_no_attestation(); + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let server_addr = listener.local_addr().unwrap(); + + tokio::spawn(async move { + attestation_provider_server(listener, attestation_generator).await.unwrap(); + }); + attestation_provider_client(server_addr, AttestationVerifier::expect_none()).await.unwrap(); + } +} diff --git a/crates/attestation-provider-server/src/main.rs b/crates/attestation-provider-server/src/main.rs new file mode 100644 index 0000000..66d3f5d --- /dev/null +++ b/crates/attestation-provider-server/src/main.rs @@ -0,0 +1,103 @@ +use std::{net::SocketAddr, path::PathBuf}; + +use attestation::{AttestationGenerator, AttestationVerifier, measurements::MeasurementPolicy}; +use attestation_provider_server::{attestation_provider_client, attestation_provider_server}; +use clap::{Parser, Subcommand}; +use tokio::net::TcpListener; +use tracing::level_filters::LevelFilter; + +const GIT_REV: &str = match option_env!("GIT_REV") { + Some(rev) => rev, + None => "unknown", +}; + +#[derive(Parser, Debug, Clone)] +#[command(version = GIT_REV, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + command: CliCommand, + /// Log debug messages + #[arg(long, global = true)] + log_debug: bool, + /// Log in JSON format + #[arg(long, global = true)] + log_json: bool, + /// Log DCAP quotes to folder `quotes/` + #[arg(long, global = true)] + log_dcap_quote: bool, +} +#[derive(Subcommand, Debug, Clone)] +enum CliCommand { + Server { + /// Socket address to listen on + #[arg(short, long, default_value = "0.0.0.0:0", env = "LISTEN_ADDR")] + listen_addr: SocketAddr, + /// Type of attestation to present (will attempt to detect if not + /// given) + #[arg(long)] + server_attestation_type: Option, + }, + Client { + /// Socket address of a attestation provider server + server_addr: SocketAddr, + /// Optional path to file containing JSON measurements to be + /// enforced on the remote party + #[arg(long, global = true, env = "MEASUREMENTS_FILE")] + measurements_file: Option, + }, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let level_filter = if cli.log_debug { LevelFilter::DEBUG } else { LevelFilter::WARN }; + + let env_filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(level_filter.into()) + .from_env_lossy(); + + let subscriber = tracing_subscriber::fmt::Subscriber::builder().with_env_filter(env_filter); + + if cli.log_json { + subscriber.json().init(); + } else { + subscriber.pretty().init(); + } + + if cli.log_dcap_quote { + tokio::fs::create_dir_all("quotes").await?; + } + + match cli.command { + CliCommand::Server { listen_addr, server_attestation_type } => { + let attestation_generator = + AttestationGenerator::new_with_detection(server_attestation_type, None).await?; + + let listener = TcpListener::bind(listen_addr).await?; + + println!("Listening on {}", listener.local_addr()?); + attestation_provider_server(listener, attestation_generator).await?; + } + CliCommand::Client { server_addr, measurements_file } => { + let measurement_policy = match measurements_file { + Some(measurements_file) => MeasurementPolicy::from_file(measurements_file).await?, + None => MeasurementPolicy::accept_anything(), + }; + + let attestation_verifier = AttestationVerifier { + measurement_policy, + pccs_url: None, + override_azure_outdated_tcb: false, + dump_dcap_quotes: cli.log_dcap_quote, + }; + + let attestation_message = + attestation_provider_client(server_addr, attestation_verifier).await?; + + println!("{attestation_message:?}") + } + } + + Ok(()) +} From 3db69f3ba908a6b201dc9272d496e02ccb4b3f9c Mon Sep 17 00:00:00 2001 From: Steph Sinyakov Date: Thu, 2 Apr 2026 19:03:36 +0200 Subject: [PATCH 2/2] doc: readme update --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 209642c..d8b56e6 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,8 @@ Provided crates: session for attestation. - [`attestation`](./crates/attestation) - provides attestation generation, verification and measurement handling. +- [`attestation-provider-server`](./crates/attestation-provider-server) - + HTTP server and client for attestation generation and verification. The included `shell.nix` file can be used with `nix-shell`, `direnv`, or `nix develop` to add the dependencies needed by the optional `azure` feature of the