Skip to content

Commit 962e28c

Browse files
committed
Add dummy attestation server/client
1 parent 28665f2 commit 962e28c

File tree

5 files changed

+262
-0
lines changed

5 files changed

+262
-0
lines changed

Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[workspace]
2+
members = [".", "dummy-attestation-server"]
3+
14
[package]
25
name = "attested-tls-proxy"
36
version = "0.1.0"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "dummy-attestation-server"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "MIT"
6+
publish = false
7+
8+
[dependencies]
9+
attested-tls-proxy = { path = ".." }
10+
tokio = { version = "1.48.0", features = ["full"] }
11+
axum = "0.8.6"
12+
tokio-rustls = { version = "0.26.4", default-features = false, features = ["ring"] }
13+
thiserror = "2.0.17"
14+
clap = { version = "4.5.51", features = ["derive", "env"] }
15+
webpki-roots = "1.0.4"
16+
rustls-pemfile = "2.2.0"
17+
anyhow = "1.0.100"
18+
configfs-tsm = "0.0.2"
19+
hex = "0.4.3"
20+
serde_json = "1.0.145"
21+
serde = "1.0.228"
22+
tracing = "0.1.41"
23+
tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json"] }
24+
rcgen = "0.14.5"
25+
parity-scale-codec = "3.7.5"
26+
reqwest = { version = "0.12.23", default-features = false }
27+
28+
[dev-dependencies]
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use std::{
2+
net::{IpAddr, SocketAddr},
3+
sync::Arc,
4+
};
5+
6+
use attested_tls_proxy::{attestation::AttestationExchangeMessage, QuoteGenerator};
7+
use axum::{
8+
extract::{Path, State},
9+
http::StatusCode,
10+
response::{IntoResponse, Response},
11+
};
12+
use parity_scale_codec::{Decode, Encode};
13+
use tokio::net::TcpListener;
14+
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
15+
16+
#[derive(Clone)]
17+
struct SharedState {
18+
attestation_generator: Arc<dyn QuoteGenerator>,
19+
}
20+
21+
pub async fn dummy_attestation_server(
22+
listener: TcpListener,
23+
attestation_generator: Arc<dyn QuoteGenerator>,
24+
) -> anyhow::Result<SocketAddr> {
25+
let addr = listener.local_addr()?;
26+
27+
let app = axum::Router::new()
28+
.route("/attest/{input_data}", axum::routing::get(get_attest))
29+
.with_state(SharedState {
30+
attestation_generator,
31+
});
32+
33+
tokio::spawn(async move {
34+
axum::serve(listener, app).await.unwrap();
35+
});
36+
37+
Ok(addr)
38+
}
39+
40+
async fn get_attest(
41+
State(shared_state): State<SharedState>,
42+
Path(input_data): Path<String>,
43+
) -> Result<(StatusCode, Vec<u8>), ServerError> {
44+
let (cert_chain, _) = generate_certificate_chain("0.0.0.0".parse().unwrap());
45+
let input_data: [u8; 64] = hex::decode(input_data).unwrap().try_into().unwrap();
46+
47+
let attestation = AttestationExchangeMessage::from_attestation_generator(
48+
&cert_chain,
49+
input_data[..32].try_into().unwrap(),
50+
shared_state.attestation_generator,
51+
)?
52+
.encode();
53+
54+
Ok((StatusCode::OK, attestation))
55+
}
56+
57+
pub async fn dummy_attestation_client(server_addr: SocketAddr) -> anyhow::Result<()> {
58+
let input_data = [0; 64];
59+
let response = reqwest::get(format!(
60+
"http://{server_addr}/attest/{}",
61+
hex::encode(input_data)
62+
))
63+
.await
64+
.unwrap()
65+
.bytes()
66+
.await
67+
.unwrap();
68+
69+
let remote_attestation_message = AttestationExchangeMessage::decode(&mut &response[..])?;
70+
let remote_attestation_type = remote_attestation_message.attestation_type;
71+
println!("{remote_attestation_type}");
72+
73+
// TODO validate the attestation
74+
Ok(())
75+
}
76+
77+
struct ServerError(pub anyhow::Error);
78+
79+
impl<E> From<E> for ServerError
80+
where
81+
E: Into<anyhow::Error>,
82+
{
83+
fn from(err: E) -> Self {
84+
ServerError(err.into())
85+
}
86+
}
87+
88+
impl IntoResponse for ServerError {
89+
fn into_response(self) -> Response {
90+
eprintln!("{:?}", self.0);
91+
(StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
92+
}
93+
}
94+
95+
/// Helper to generate a self-signed certificate for testing
96+
fn generate_certificate_chain(
97+
ip: IpAddr,
98+
) -> (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>) {
99+
let mut params = rcgen::CertificateParams::new(vec![]).unwrap();
100+
params.subject_alt_names.push(rcgen::SanType::IpAddress(ip));
101+
params
102+
.distinguished_name
103+
.push(rcgen::DnType::CommonName, ip.to_string());
104+
105+
let keypair = rcgen::KeyPair::generate().unwrap();
106+
let cert = params.self_signed(&keypair).unwrap();
107+
108+
let certs = vec![CertificateDer::from(cert)];
109+
let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(keypair.serialize_der()));
110+
(certs, key)
111+
}
112+
113+
#[cfg(test)]
114+
mod tests {
115+
116+
use attested_tls_proxy::attestation::AttestationType;
117+
118+
use super::*;
119+
120+
#[tokio::test]
121+
async fn test_dummy_server() {
122+
let attestation_generator = AttestationType::None.get_quote_generator().unwrap();
123+
124+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
125+
let server_addr = listener.local_addr().unwrap();
126+
dummy_attestation_server(listener, attestation_generator)
127+
.await
128+
.unwrap();
129+
dummy_attestation_client(server_addr).await.unwrap();
130+
}
131+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use attested_tls_proxy::attestation::AttestationType;
2+
use clap::{Parser, Subcommand};
3+
use dummy_attestation_server::{dummy_attestation_client, dummy_attestation_server};
4+
use std::net::SocketAddr;
5+
use tokio::net::TcpListener;
6+
use tracing::level_filters::LevelFilter;
7+
8+
#[derive(Parser, Debug, Clone)]
9+
#[clap(version, about, long_about = None)]
10+
struct Cli {
11+
#[clap(subcommand)]
12+
command: CliCommand,
13+
/// Log debug messages
14+
#[arg(long, global = true)]
15+
log_debug: bool,
16+
/// Log in JSON format
17+
#[arg(long, global = true)]
18+
log_json: bool,
19+
}
20+
#[derive(Subcommand, Debug, Clone)]
21+
enum CliCommand {
22+
Server {
23+
/// Socket address to listen on
24+
#[arg(short, long, default_value = "0.0.0.0:0", env = "LISTEN_ADDR")]
25+
listen_addr: SocketAddr,
26+
/// Type of attestation to present (defaults to none)
27+
#[arg(long)]
28+
server_attestation_type: Option<String>,
29+
},
30+
Client {
31+
/// Socket address of a dummy attestation server
32+
server_addr: SocketAddr,
33+
},
34+
}
35+
36+
#[tokio::main]
37+
async fn main() -> anyhow::Result<()> {
38+
let cli = Cli::parse();
39+
40+
let level_filter = if cli.log_debug {
41+
LevelFilter::DEBUG
42+
} else {
43+
LevelFilter::WARN
44+
};
45+
46+
let env_filter = tracing_subscriber::EnvFilter::builder()
47+
.with_default_directive(level_filter.into())
48+
.from_env_lossy();
49+
50+
let subscriber = tracing_subscriber::fmt::Subscriber::builder().with_env_filter(env_filter);
51+
52+
if cli.log_json {
53+
subscriber.json().init();
54+
} else {
55+
subscriber.pretty().init();
56+
}
57+
58+
match cli.command {
59+
CliCommand::Server {
60+
listen_addr,
61+
server_attestation_type,
62+
} => {
63+
let server_attestation_type: AttestationType = serde_json::from_value(
64+
serde_json::Value::String(server_attestation_type.unwrap_or("none".to_string())),
65+
)?;
66+
67+
let attestation_generator = server_attestation_type.get_quote_generator()?;
68+
69+
let listener = TcpListener::bind(listen_addr).await?;
70+
dummy_attestation_server(listener, attestation_generator).await?;
71+
}
72+
CliCommand::Client { server_addr } => dummy_attestation_client(server_addr).await?,
73+
}
74+
75+
Ok(())
76+
}

0 commit comments

Comments
 (0)