mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 13:27:59 -05:00
* refactor: extract attestation functionality into dedicated crate * commit lock * fix integration test * clippy * fix docs * fix import * fix wasm types * fix doctest * verifier config default rootstore * fix integration test * fix notary integration tests
245 lines
9.2 KiB
Rust
245 lines
9.2 KiB
Rust
// This example demonstrates how to use the Prover to acquire an attestation for
|
|
// an HTTP request sent to example.com. The attestation and secrets are saved to
|
|
// disk.
|
|
|
|
use std::env;
|
|
|
|
use clap::Parser;
|
|
use http_body_util::Empty;
|
|
use hyper::{body::Bytes, Request, StatusCode};
|
|
use hyper_util::rt::TokioIo;
|
|
use spansy::Spanned;
|
|
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
|
use tracing::debug;
|
|
|
|
use notary_client::{Accepted, NotarizationRequest, NotaryClient};
|
|
use tls_server_fixture::{CA_CERT_DER, SERVER_DOMAIN};
|
|
use tlsn::{
|
|
attestation::request::RequestConfig,
|
|
config::ProtocolConfig,
|
|
prover::{Prover, ProverConfig, TlsConfig},
|
|
transcript::TranscriptCommitConfig,
|
|
};
|
|
use tlsn_examples::ExampleType;
|
|
use tlsn_formats::http::{DefaultHttpCommitter, HttpCommit, HttpTranscript};
|
|
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
|
|
use tlsn_server_fixture_certs::{CLIENT_CERT, CLIENT_KEY};
|
|
|
|
// Setting of the application server.
|
|
const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about, long_about = None)]
|
|
struct Args {
|
|
/// What data to notarize.
|
|
#[clap(default_value_t, value_enum)]
|
|
example_type: ExampleType,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let args = Args::parse();
|
|
|
|
let (uri, extra_headers) = match args.example_type {
|
|
ExampleType::Json => ("/formats/json", vec![]),
|
|
ExampleType::Html => ("/formats/html", vec![]),
|
|
ExampleType::Authenticated => ("/protected", vec![("Authorization", "random_auth_token")]),
|
|
};
|
|
|
|
notarize(uri, extra_headers, &args.example_type).await
|
|
}
|
|
|
|
async fn notarize(
|
|
uri: &str,
|
|
extra_headers: Vec<(&str, &str)>,
|
|
example_type: &ExampleType,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let notary_host: String = env::var("NOTARY_HOST").unwrap_or("127.0.0.1".into());
|
|
let notary_port: u16 = env::var("NOTARY_PORT")
|
|
.map(|port| port.parse().expect("port should be valid integer"))
|
|
.unwrap_or(7047);
|
|
let server_host: String = env::var("SERVER_HOST").unwrap_or("127.0.0.1".into());
|
|
let server_port: u16 = env::var("SERVER_PORT")
|
|
.map(|port| port.parse().expect("port should be valid integer"))
|
|
.unwrap_or(DEFAULT_FIXTURE_PORT);
|
|
|
|
// Build a client to connect to the notary server.
|
|
let notary_client = NotaryClient::builder()
|
|
.host(notary_host)
|
|
.port(notary_port)
|
|
// WARNING: Always use TLS to connect to notary server, except if notary is running locally
|
|
// e.g. this example, hence `enable_tls` is set to False (else it always defaults to True).
|
|
.enable_tls(false)
|
|
.build()
|
|
.unwrap();
|
|
|
|
// Send requests for configuration and notarization to the notary server.
|
|
let notarization_request = NotarizationRequest::builder()
|
|
// We must configure the amount of data we expect to exchange beforehand, which will
|
|
// be preprocessed prior to the connection. Reducing these limits will improve
|
|
// performance.
|
|
.max_sent_data(tlsn_examples::MAX_SENT_DATA)
|
|
.max_recv_data(tlsn_examples::MAX_RECV_DATA)
|
|
.build()?;
|
|
|
|
let Accepted {
|
|
io: notary_connection,
|
|
id: _session_id,
|
|
..
|
|
} = notary_client
|
|
.request_notarization(notarization_request)
|
|
.await
|
|
.expect("Could not connect to notary. Make sure it is running.");
|
|
|
|
// Create a crypto provider accepting the server-fixture's self-signed
|
|
// root certificate.
|
|
//
|
|
// This is only required for offline testing with the server-fixture. In
|
|
// production, use `CryptoProvider::default()` instead.
|
|
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
|
root_store
|
|
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
|
.unwrap();
|
|
|
|
// Set up protocol configuration for prover.
|
|
let mut prover_config_builder = ProverConfig::builder();
|
|
prover_config_builder
|
|
.server_name(SERVER_DOMAIN)
|
|
.protocol_config(
|
|
ProtocolConfig::builder()
|
|
// We must configure the amount of data we expect to exchange beforehand, which will
|
|
// be preprocessed prior to the connection. Reducing these limits will improve
|
|
// performance.
|
|
.max_sent_data(tlsn_examples::MAX_SENT_DATA)
|
|
.max_recv_data(tlsn_examples::MAX_RECV_DATA)
|
|
.build()?,
|
|
);
|
|
|
|
// (Optional) Set up TLS client authentication if required by the server.
|
|
prover_config_builder.tls_config(
|
|
TlsConfig::builder()
|
|
.client_auth_pem((vec![CLIENT_CERT.to_vec()], CLIENT_KEY.to_vec()))
|
|
.unwrap()
|
|
.build()?,
|
|
);
|
|
|
|
let prover_config = prover_config_builder.build()?;
|
|
|
|
// Create a new prover and perform necessary setup.
|
|
let prover = Prover::new(prover_config)
|
|
.setup(notary_connection.compat())
|
|
.await?;
|
|
|
|
// Open a TCP connection to the server.
|
|
let client_socket = tokio::net::TcpStream::connect((server_host, server_port)).await?;
|
|
|
|
// Bind the prover to the server connection.
|
|
// The returned `mpc_tls_connection` is an MPC TLS connection to the server: all
|
|
// data written to/read from it will be encrypted/decrypted using MPC with
|
|
// the notary.
|
|
let (mpc_tls_connection, prover_fut) = prover.connect(client_socket.compat()).await?;
|
|
let mpc_tls_connection = TokioIo::new(mpc_tls_connection.compat());
|
|
|
|
// Spawn the prover task to be run concurrently in the background.
|
|
let prover_task = tokio::spawn(prover_fut);
|
|
|
|
// Attach the hyper HTTP client to the connection.
|
|
let (mut request_sender, connection) =
|
|
hyper::client::conn::http1::handshake(mpc_tls_connection).await?;
|
|
|
|
// Spawn the HTTP task to be run concurrently in the background.
|
|
tokio::spawn(connection);
|
|
|
|
// Build a simple HTTP request with common headers.
|
|
let request_builder = Request::builder()
|
|
.uri(uri)
|
|
.header("Host", SERVER_DOMAIN)
|
|
.header("Accept", "*/*")
|
|
// Using "identity" instructs the Server not to use compression for its HTTP response.
|
|
// TLSNotary tooling does not support compression.
|
|
.header("Accept-Encoding", "identity")
|
|
.header("Connection", "close")
|
|
.header("User-Agent", USER_AGENT);
|
|
let mut request_builder = request_builder;
|
|
for (key, value) in extra_headers {
|
|
request_builder = request_builder.header(key, value);
|
|
}
|
|
let request = request_builder.body(Empty::<Bytes>::new())?;
|
|
|
|
println!("Starting an MPC TLS connection with the server");
|
|
|
|
// Send the request to the server and wait for the response.
|
|
let response = request_sender.send_request(request).await?;
|
|
|
|
println!("Got a response from the server: {}", response.status());
|
|
|
|
assert!(response.status() == StatusCode::OK);
|
|
|
|
// The prover task should be done now, so we can await it.
|
|
let mut prover = prover_task.await??;
|
|
|
|
// Parse the HTTP transcript.
|
|
let transcript = HttpTranscript::parse(prover.transcript())?;
|
|
|
|
let body_content = &transcript.responses[0].body.as_ref().unwrap().content;
|
|
let body = String::from_utf8_lossy(body_content.span().as_bytes());
|
|
|
|
match body_content {
|
|
tlsn_formats::http::BodyContent::Json(_json) => {
|
|
let parsed = serde_json::from_str::<serde_json::Value>(&body)?;
|
|
debug!("{}", serde_json::to_string_pretty(&parsed)?);
|
|
}
|
|
tlsn_formats::http::BodyContent::Unknown(_span) => {
|
|
debug!("{}", &body);
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// Commit to the transcript.
|
|
let mut builder = TranscriptCommitConfig::builder(prover.transcript());
|
|
|
|
// This commits to various parts of the transcript separately (e.g. request
|
|
// headers, response headers, response body and more). See https://docs.tlsnotary.org//protocol/commit_strategy.html
|
|
// for other strategies that can be used to generate commitments.
|
|
DefaultHttpCommitter::default().commit_transcript(&mut builder, &transcript)?;
|
|
|
|
let transcript_commit = builder.build()?;
|
|
|
|
// Build an attestation request.
|
|
let mut builder = RequestConfig::builder();
|
|
|
|
builder.transcript_commit(transcript_commit);
|
|
|
|
// Optionally, add an extension to the attestation if the notary supports it.
|
|
// builder.extension(Extension {
|
|
// id: b"example.name".to_vec(),
|
|
// value: b"Bobert".to_vec(),
|
|
// });
|
|
|
|
let request_config = builder.build()?;
|
|
|
|
#[allow(deprecated)]
|
|
let (attestation, secrets) = prover.notarize(&request_config).await?;
|
|
|
|
println!("Notarization complete!");
|
|
|
|
// Write the attestation to disk.
|
|
let attestation_path = tlsn_examples::get_file_path(example_type, "attestation");
|
|
let secrets_path = tlsn_examples::get_file_path(example_type, "secrets");
|
|
|
|
tokio::fs::write(&attestation_path, bincode::serialize(&attestation)?).await?;
|
|
|
|
// Write the secrets to disk.
|
|
tokio::fs::write(&secrets_path, bincode::serialize(&secrets)?).await?;
|
|
|
|
println!("Notarization completed successfully!");
|
|
println!(
|
|
"The attestation has been written to `{attestation_path}` and the \
|
|
corresponding secrets to `{secrets_path}`."
|
|
);
|
|
|
|
Ok(())
|
|
}
|