refactor(tlsn-examples): update hyper and use http prover (#434)

* add notary function to examples lib

* use hyper 1.1 version in examples

* update twitter example to use HTTP prover

* use deferred decryption in twitter example
This commit is contained in:
sinu.eth
2024-02-13 06:50:30 -08:00
committed by GitHub
parent 98a3c4d754
commit 0d269ed023
5 changed files with 269 additions and 448 deletions

View File

@@ -4,15 +4,25 @@ version = "0.0.0"
edition = "2021"
publish = false
[dev-dependencies]
tlsn-prover = { workspace = true, features = ["tracing"] }
tlsn-verifier.workspace = true
[dependencies]
tlsn-core.workspace = true
tlsn-tls-core.workspace = true
tlsn-tls-client.workspace = true
notary-server = { path = "../../notary-server" }
mpz-core.workspace = true
tlsn-prover = { workspace = true, features = ["tracing"] }
tlsn-verifier.workspace = true
p256 = { workspace = true, features = ["ecdsa"] }
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
webpki-roots.workspace = true
rustls = { version = "0.21" }
rustls-pemfile = { version = "1.0.2" }
tokio-rustls = { version = "0.24.1" }
async-tls = { version = "0.12", default-features = false, features = [
"client",
] }
futures.workspace = true
tokio = { workspace = true, features = [
"rt",
@@ -23,26 +33,16 @@ tokio = { workspace = true, features = [
"fs",
] }
tokio-util.workspace = true
hyper = { version = "1.1", features = ["client", "http1"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }
chrono = "0.4"
tracing.workspace = true
tracing-subscriber.workspace = true
hyper = { version = "0.14", features = ["client", "http1"] }
chrono = "0.4"
p256 = { workspace = true, features = ["ecdsa"] }
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
webpki-roots.workspace = true
async-tls = { version = "0.12", default-features = false, features = [
"client",
] }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0"
eyre = "0.6.8"
rustls = { version = "0.21" }
rustls-pemfile = { version = "1.0.2" }
tokio-rustls = { version = "0.24.1" }
dotenv = "0.15.0"
[[example]]

View File

@@ -1,14 +1,14 @@
/// This example shows how to notarize Discord DMs.
///
/// The example uses the notary server implemented in ../../../notary-server
use futures::AsyncWriteExt;
use hyper::{body::to_bytes, client::conn::Parts, Body, Request, StatusCode};
use rustls::{Certificate, ClientConfig, RootCertStore};
use serde::{Deserialize, Serialize};
use std::{env, ops::Range, str, sync::Arc};
// This example shows how to notarize Discord DMs.
//
// The example uses the notary server implemented in ../../../notary-server
use http_body_util::{BodyExt, Empty};
use hyper::{body::Bytes, Request, StatusCode};
use hyper_util::rt::TokioIo;
use std::{env, ops::Range, str};
use tlsn_core::proof::TlsProof;
use tokio::{io::AsyncWriteExt as _, net::TcpStream};
use tokio_rustls::TlsConnector;
use tlsn_examples::request_notarization;
use tokio::io::AsyncWriteExt as _;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tracing::debug;
@@ -24,31 +24,6 @@ const NOTARY_PORT: u16 = 7047;
// Configuration of notarization
const NOTARY_MAX_TRANSCRIPT_SIZE: usize = 16384;
/// Response object of the /session API
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotarizationSessionResponse {
pub session_id: String,
}
/// Request object of the /session API
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotarizationSessionRequest {
pub client_type: ClientType,
/// Maximum transcript size in bytes
pub max_transcript_size: Option<usize>,
}
/// Types of client that the prover is using
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ClientType {
/// Client that has access to the transport layer
Tcp,
/// Client that cannot directly access transport layer, e.g. browser extension
Websocket,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
@@ -59,7 +34,8 @@ async fn main() {
let auth_token = env::var("AUTHORIZATION").unwrap();
let user_agent = env::var("USER_AGENT").unwrap();
let (notary_tls_socket, session_id) = setup_notary_connection().await;
let (notary_tls_socket, session_id) =
request_notarization(NOTARY_HOST, NOTARY_PORT, Some(NOTARY_MAX_TRANSCRIPT_SIZE)).await;
// Basic default prover config using the session_id returned from /session endpoint just now
let config = ProverConfig::builder()
@@ -85,12 +61,13 @@ async fn main() {
let prover_task = tokio::spawn(prover_fut);
// Attach the hyper HTTP client to the TLS connection
let (mut request_sender, connection) = hyper::client::conn::handshake(tls_connection.compat())
.await
.unwrap();
let (mut request_sender, connection) =
hyper::client::conn::http1::handshake(TokioIo::new(tls_connection.compat()))
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
tokio::spawn(connection);
// Build the HTTP request to fetch the DMs
let request = Request::builder()
@@ -104,7 +81,7 @@ async fn main() {
.header("User-Agent", user_agent)
.header("Authorization", &auth_token)
.header("Connection", "close")
.body(Body::empty())
.body(Empty::<Bytes>::new())
.unwrap();
debug!("Sending request");
@@ -118,15 +95,11 @@ async fn main() {
debug!("Request OK");
// Pretty printing :)
let payload = to_bytes(response.into_body()).await.unwrap().to_vec();
let payload = response.into_body().collect().await.unwrap().to_bytes();
let parsed =
serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload)).unwrap();
debug!("{}", serde_json::to_string_pretty(&parsed).unwrap());
// Close the connection to the server
let mut client_socket = connection_task.await.unwrap().unwrap().io.into_inner();
client_socket.close().await.unwrap();
// The Prover task should be done now, so we can grab it.
let prover = prover_task.await.unwrap().unwrap();
@@ -193,121 +166,6 @@ async fn main() {
.unwrap();
}
async fn setup_notary_connection() -> (tokio_rustls::client::TlsStream<TcpStream>, String) {
// Connect to the Notary via TLS-TCP
let pem_file = str::from_utf8(include_bytes!(
"../../../notary-server/fixture/tls/rootCA.crt"
))
.unwrap();
let mut reader = std::io::BufReader::new(pem_file.as_bytes());
let mut certificates: Vec<Certificate> = rustls_pemfile::certs(&mut reader)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let certificate = certificates.remove(0);
let mut root_store = RootCertStore::empty();
root_store.add(&certificate).unwrap();
let client_notary_config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
let notary_connector = TlsConnector::from(Arc::new(client_notary_config));
let notary_socket = tokio::net::TcpStream::connect((NOTARY_HOST, NOTARY_PORT))
.await
.unwrap();
let notary_tls_socket = notary_connector
// Require the domain name of notary server to be the same as that in the server cert
.connect("tlsnotaryserver.io".try_into().unwrap(), notary_socket)
.await
.unwrap();
// Attach the hyper HTTP client to the notary TLS connection to send request to the /session endpoint to configure notarization and obtain session id
let (mut request_sender, connection) = hyper::client::conn::handshake(notary_tls_socket)
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
// Build the HTTP request to configure notarization
let payload = serde_json::to_string(&NotarizationSessionRequest {
client_type: ClientType::Tcp,
max_transcript_size: Some(NOTARY_MAX_TRANSCRIPT_SIZE),
})
.unwrap();
let request = Request::builder()
.uri(format!("https://{NOTARY_HOST}:{NOTARY_PORT}/session"))
.method("POST")
.header("Host", NOTARY_HOST)
// Need to specify application/json for axum to parse it as json
.header("Content-Type", "application/json")
.body(Body::from(payload))
.unwrap();
debug!("Sending configuration request");
let configuration_response = request_sender.send_request(request).await.unwrap();
debug!("Sent configuration request");
assert!(configuration_response.status() == StatusCode::OK);
debug!("Response OK");
// Pretty printing :)
let payload = to_bytes(configuration_response.into_body())
.await
.unwrap()
.to_vec();
let notarization_response =
serde_json::from_str::<NotarizationSessionResponse>(&String::from_utf8_lossy(&payload))
.unwrap();
debug!("Notarization response: {:?}", notarization_response,);
// Send notarization request via HTTP, where the underlying TCP connection will be extracted later
let request = Request::builder()
// Need to specify the session_id so that notary server knows the right configuration to use
// as the configuration is set in the previous HTTP call
.uri(format!(
"https://{}:{}/notarize?sessionId={}",
NOTARY_HOST,
NOTARY_PORT,
notarization_response.session_id.clone()
))
.method("GET")
.header("Host", NOTARY_HOST)
.header("Connection", "Upgrade")
// Need to specify this upgrade header for server to extract tcp connection later
.header("Upgrade", "TCP")
.body(Body::empty())
.unwrap();
debug!("Sending notarization request");
let response = request_sender.send_request(request).await.unwrap();
debug!("Sent notarization request");
assert!(response.status() == StatusCode::SWITCHING_PROTOCOLS);
debug!("Switched protocol OK");
// Claim back the TLS socket after HTTP exchange is done
let Parts {
io: notary_tls_socket,
..
} = connection_task.await.unwrap().unwrap();
(notary_tls_socket, notarization_response.session_id)
}
/// Find the ranges of the public and private parts of a sequence.
///
/// Returns a tuple of `(public, private)` ranges.

View File

@@ -1,25 +1,23 @@
/// Runs a simple Prover which connects to the Notary and notarizes a request/response from
/// example.com. The Prover then generates a proof and writes it to disk.
///
/// The example uses the notary server implemented in ./simple_notary.rs
use futures::AsyncWriteExt;
use hyper::{Body, Request, StatusCode};
// Runs a simple Prover which connects to the Notary and notarizes a request/response from
// example.com. The Prover then generates a proof and writes it to disk.
use http_body_util::Empty;
use hyper::{body::Bytes, Request, StatusCode};
use hyper_util::rt::TokioIo;
use std::ops::Range;
use tlsn_core::proof::TlsProof;
use tokio::io::{AsyncWriteExt as _, DuplexStream};
use tokio::io::AsyncWriteExt as _;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tlsn_examples::run_notary;
use tlsn_prover::tls::{state::Notarize, Prover, ProverConfig};
// Setting of the application server
const SERVER_DOMAIN: &str = "example.com";
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";
use p256::pkcs8::DecodePrivateKey;
use std::str;
use tlsn_verifier::tls::{Verifier, VerifierConfig};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
@@ -27,7 +25,7 @@ async fn main() {
let (prover_socket, notary_socket) = tokio::io::duplex(1 << 16);
// Start a local simple notary service
start_notary_thread(prover_socket).await;
tokio::spawn(run_notary(notary_socket.compat()));
// A Prover configuration
let config = ProverConfig::builder()
@@ -39,7 +37,7 @@ async fn main() {
// Create a Prover and set it up with the Notary
// This will set up the MPC backend prior to connecting to the server.
let prover = Prover::new(config)
.setup(notary_socket.compat())
.setup(prover_socket.compat())
.await
.unwrap();
@@ -52,18 +50,19 @@ async fn main() {
// 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.unwrap();
let mpc_tls_connection = TokioIo::new(mpc_tls_connection.compat());
// Spawn the Prover task to be run concurrently
let prover_task = tokio::spawn(prover_fut);
// Attach the hyper HTTP client to the MPC TLS connection
let (mut request_sender, connection) =
hyper::client::conn::handshake(mpc_tls_connection.compat())
hyper::client::conn::http1::handshake(mpc_tls_connection)
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
tokio::spawn(connection);
// Build a simple HTTP request with common headers
let request = Request::builder()
@@ -75,7 +74,7 @@ async fn main() {
.header("Accept-Encoding", "identity")
.header("Connection", "close")
.header("User-Agent", USER_AGENT)
.body(Body::empty())
.body(Empty::<Bytes>::new())
.unwrap();
println!("Starting an MPC TLS connection with the server");
@@ -87,10 +86,6 @@ async fn main() {
assert!(response.status() == StatusCode::OK);
// Close the connection to the server
let mut client_socket = connection_task.await.unwrap().unwrap().io.into_inner();
client_socket.close().await.unwrap();
// The Prover task should be done now, so we can grab the Prover.
let prover = prover_task.await.unwrap().unwrap();
@@ -226,26 +221,3 @@ async fn build_proof_with_redactions(mut prover: Prover<Notarize>) -> TlsProof {
substrings: substrings_proof,
}
}
async fn start_notary_thread(socket: DuplexStream) {
tokio::spawn(async {
// Load the notary signing key
let signing_key_str = str::from_utf8(include_bytes!(
"../../../notary-server/fixture/notary/notary.key"
))
.unwrap();
let signing_key = p256::ecdsa::SigningKey::from_pkcs8_pem(signing_key_str).unwrap();
// Spawn notarization task to be run concurrently
tokio::spawn(async move {
// Setup default config. Normally a different ID would be generated
// for each notarization.
let config = VerifierConfig::builder().id("example").build().unwrap();
Verifier::new(config)
.notarize::<_, p256::ecdsa::Signature>(socket.compat(), &signing_key)
.await
.unwrap();
});
});
}

155
tlsn/examples/src/lib.rs Normal file
View File

@@ -0,0 +1,155 @@
use std::sync::Arc;
use elliptic_curve::pkcs8::DecodePrivateKey;
use futures::{AsyncRead, AsyncWrite};
use http_body_util::{BodyExt as _, Either, Empty, Full};
use hyper::{client::conn::http1::Parts, Request, StatusCode};
use hyper_util::rt::TokioIo;
use notary_server::{ClientType, NotarizationSessionRequest, NotarizationSessionResponse};
use rustls::{Certificate, ClientConfig, RootCertStore};
use tlsn_verifier::tls::{Verifier, VerifierConfig};
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
use tokio_util::bytes::Bytes;
use tracing::debug;
/// Runs a simple Notary with the provided connection to the Prover.
pub async fn run_notary<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(conn: T) {
// Load the notary signing key
let signing_key_str = std::str::from_utf8(include_bytes!(
"../../../notary-server/fixture/notary/notary.key"
))
.unwrap();
let signing_key = p256::ecdsa::SigningKey::from_pkcs8_pem(signing_key_str).unwrap();
// Setup default config. Normally a different ID would be generated
// for each notarization.
let config = VerifierConfig::builder().id("example").build().unwrap();
Verifier::new(config)
.notarize::<_, p256::ecdsa::Signature>(conn, &signing_key)
.await
.unwrap();
}
/// Requests notarization from the Notary server.
pub async fn request_notarization(
host: &str,
port: u16,
max_transcript_size: Option<usize>,
) -> (tokio_rustls::client::TlsStream<TcpStream>, String) {
// Connect to the Notary via TLS-TCP
let pem_file = std::str::from_utf8(include_bytes!(
"../../../notary-server/fixture/tls/rootCA.crt"
))
.unwrap();
let mut reader = std::io::BufReader::new(pem_file.as_bytes());
let mut certificates: Vec<Certificate> = rustls_pemfile::certs(&mut reader)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let certificate = certificates.remove(0);
let mut root_store = RootCertStore::empty();
root_store.add(&certificate).unwrap();
let client_notary_config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
let notary_connector = TlsConnector::from(Arc::new(client_notary_config));
let notary_socket = tokio::net::TcpStream::connect((host, port)).await.unwrap();
let notary_tls_socket = notary_connector
// Require the domain name of notary server to be the same as that in the server cert
.connect("tlsnotaryserver.io".try_into().unwrap(), notary_socket)
.await
.unwrap();
// Attach the hyper HTTP client to the notary TLS connection to send request to the /session endpoint to configure notarization and obtain session id
let (mut request_sender, connection) =
hyper::client::conn::http1::handshake(TokioIo::new(notary_tls_socket))
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
// Build the HTTP request to configure notarization
let payload = serde_json::to_string(&NotarizationSessionRequest {
client_type: ClientType::Tcp,
max_transcript_size,
})
.unwrap();
let request = Request::builder()
.uri(format!("https://{host}:{port}/session"))
.method("POST")
.header("Host", host)
// Need to specify application/json for axum to parse it as json
.header("Content-Type", "application/json")
.body(Either::Left(Full::new(Bytes::from(payload))))
.unwrap();
debug!("Sending configuration request");
let configuration_response = request_sender.send_request(request).await.unwrap();
debug!("Sent configuration request");
assert!(configuration_response.status() == StatusCode::OK);
debug!("Response OK");
// Pretty printing :)
let payload = configuration_response
.into_body()
.collect()
.await
.unwrap()
.to_bytes();
let notarization_response =
serde_json::from_str::<NotarizationSessionResponse>(&String::from_utf8_lossy(&payload))
.unwrap();
debug!("Notarization response: {:?}", notarization_response,);
// Send notarization request via HTTP, where the underlying TCP connection will be extracted later
let request = Request::builder()
// Need to specify the session_id so that notary server knows the right configuration to use
// as the configuration is set in the previous HTTP call
.uri(format!(
"https://{host}:{port}/notarize?sessionId={}",
notarization_response.session_id.clone()
))
.method("GET")
.header("Host", host)
.header("Connection", "Upgrade")
// Need to specify this upgrade header for server to extract tcp connection later
.header("Upgrade", "TCP")
.body(Either::Right(Empty::<Bytes>::new()))
.unwrap();
debug!("Sending notarization request");
let response = request_sender.send_request(request).await.unwrap();
debug!("Sent notarization request");
assert!(response.status() == StatusCode::SWITCHING_PROTOCOLS);
debug!("Switched protocol OK");
// Claim back the TLS socket after HTTP exchange is done
let Parts {
io: notary_tls_socket,
..
} = connection_task.await.unwrap().unwrap();
(
notary_tls_socket.into_inner(),
notarization_response.session_id,
)
}

View File

@@ -1,14 +1,14 @@
/// This example shows how to notarize Twitter DMs.
///
/// The example uses the notary server implemented in ../../../notary-server
use futures::AsyncWriteExt;
use hyper::{body::to_bytes, client::conn::Parts, Body, Request, StatusCode};
use rustls::{Certificate, ClientConfig, RootCertStore};
use serde::{Deserialize, Serialize};
use std::{env, ops::Range, str, sync::Arc};
use tlsn_core::proof::TlsProof;
use tokio::{io::AsyncWriteExt as _, net::TcpStream};
use tokio_rustls::TlsConnector;
// This example shows how to notarize Twitter DMs.
//
// The example uses the notary server implemented in ../../../notary-server
use http_body_util::{BodyExt, Empty};
use hyper::{body::Bytes, Request, StatusCode};
use hyper_util::rt::TokioIo;
use std::{env, str};
use tlsn_core::{commitment::CommitmentKind, proof::TlsProof};
use tlsn_examples::request_notarization;
use tokio::io::AsyncWriteExt as _;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tracing::debug;
@@ -26,31 +26,6 @@ const NOTARY_PORT: u16 = 7047;
// Configuration of notarization
const NOTARY_MAX_TRANSCRIPT_SIZE: usize = 16384;
/// Response object of the /session API
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotarizationSessionResponse {
pub session_id: String,
}
/// Request object of the /session API
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotarizationSessionRequest {
pub client_type: ClientType,
/// Maximum transcript size in bytes
pub max_transcript_size: Option<usize>,
}
/// Types of client that the prover is using
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ClientType {
/// Client that has access to the transport layer
Tcp,
/// Client that cannot directly access transport layer, e.g. browser extension
Websocket,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
@@ -58,12 +33,12 @@ async fn main() {
// Load secret variables frome environment for twitter server connection
dotenv::dotenv().ok();
let conversation_id = env::var("CONVERSATION_ID").unwrap();
let client_uuid = env::var("CLIENT_UUID").unwrap();
let auth_token = env::var("AUTH_TOKEN").unwrap();
let access_token = env::var("ACCESS_TOKEN").unwrap();
let csrf_token = env::var("CSRF_TOKEN").unwrap();
let (notary_tls_socket, session_id) = setup_notary_connection().await;
let (notary_tls_socket, session_id) =
request_notarization(NOTARY_HOST, NOTARY_PORT, Some(NOTARY_MAX_TRANSCRIPT_SIZE)).await;
// Basic default prover config using the session_id returned from /session endpoint just now
let config = ProverConfig::builder()
@@ -84,17 +59,21 @@ async fn main() {
// Bind the Prover to server connection
let (tls_connection, prover_fut) = prover.connect(client_socket.compat()).await.unwrap();
let tls_connection = TokioIo::new(tls_connection.compat());
// Grab a control handle to the Prover
let prover_ctrl = prover_fut.control();
// Spawn the Prover to be run concurrently
let prover_task = tokio::spawn(prover_fut);
// Attach the hyper HTTP client to the TLS connection
let (mut request_sender, connection) = hyper::client::conn::handshake(tls_connection.compat())
let (mut request_sender, connection) = hyper::client::conn::http1::handshake(tls_connection)
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
tokio::spawn(connection);
// Build the HTTP request to fetch the DMs
let request = Request::builder()
@@ -114,13 +93,16 @@ async fn main() {
.header("Authority", SERVER_DOMAIN)
.header("X-Twitter-Auth-Type", "OAuth2Session")
.header("x-twitter-active-user", "yes")
.header("X-Client-Uuid", client_uuid.clone())
.header("X-Csrf-Token", csrf_token.clone())
.body(Body::empty())
.body(Empty::<Bytes>::new())
.unwrap();
debug!("Sending request");
// Because we don't need to decrypt the response right away, we can defer decryption
// until after the connection is closed. This will speed up the proving process!
prover_ctrl.defer_decryption().await.unwrap();
let response = request_sender.send_request(request).await.unwrap();
debug!("Sent request");
@@ -130,50 +112,21 @@ async fn main() {
debug!("Request OK");
// Pretty printing :)
let payload = to_bytes(response.into_body()).await.unwrap().to_vec();
let payload = response.into_body().collect().await.unwrap().to_bytes();
let parsed =
serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload)).unwrap();
debug!("{}", serde_json::to_string_pretty(&parsed).unwrap());
// Close the connection to the server
let mut client_socket = connection_task.await.unwrap().unwrap().io.into_inner();
client_socket.close().await.unwrap();
// The Prover task should be done now, so we can grab it.
let prover = prover_task.await.unwrap().unwrap();
// Prepare for notarization
let mut prover = prover.start_notarize();
// Upgrade the prover to an HTTP prover, and start notarization.
let mut prover = prover.to_http().unwrap().start_notarize();
// Identify the ranges in the transcript that contain secrets
let (public_ranges, private_ranges) = find_ranges(
prover.sent_transcript().data(),
&[
access_token.as_bytes(),
auth_token.as_bytes(),
csrf_token.as_bytes(),
client_uuid.as_bytes(),
],
);
// Commit to the transcript with the default committer, which will commit using BLAKE3.
prover.commit().unwrap();
let recv_len = prover.recv_transcript().data().len();
let builder = prover.commitment_builder();
// Commit to send public data and collect commitment ids for the outbound transcript
let mut commitment_ids = public_ranges
.iter()
.map(|range| builder.commit_sent(range).unwrap())
.collect::<Vec<_>>();
// Commit to private data. This is not needed for proof creation but ensures the data
// is in the notarized session file for optional future disclosure.
private_ranges.iter().for_each(|range| {
builder.commit_sent(range).unwrap();
});
// Commit to the received (public) data.
commitment_ids.push(builder.commit_recv(&(0..recv_len)).unwrap());
// Finalize, returning the notarized session
// Finalize, returning the notarized HTTP session
let notarized_session = prover.finalize().await.unwrap();
debug!("Notarization complete!");
@@ -181,7 +134,7 @@ async fn main() {
// Dump the notarized session to a file
let mut file = tokio::fs::File::create("twitter_dm.json").await.unwrap();
file.write_all(
serde_json::to_string_pretty(&notarized_session)
serde_json::to_string_pretty(notarized_session.session())
.unwrap()
.as_bytes(),
)
@@ -190,10 +143,40 @@ async fn main() {
let session_proof = notarized_session.session_proof();
let mut proof_builder = notarized_session.data().build_substrings_proof();
for commitment_id in commitment_ids {
proof_builder.reveal_by_id(commitment_id).unwrap();
let mut proof_builder = notarized_session.session().data().build_substrings_proof();
// Prove the request, while redacting the secrets from it.
let request = &notarized_session.transcript().requests[0];
proof_builder
.reveal_sent(&request.without_data(), CommitmentKind::Blake3)
.unwrap();
proof_builder
.reveal_sent(&request.request.target, CommitmentKind::Blake3)
.unwrap();
for header in &request.headers {
// Only reveal the host header
if header.name.as_str().eq_ignore_ascii_case("Host") {
proof_builder
.reveal_sent(header, CommitmentKind::Blake3)
.unwrap();
} else {
proof_builder
.reveal_sent(&header.without_value(), CommitmentKind::Blake3)
.unwrap();
}
}
// Prove the entire response, as we don't need to redact anything
let response = &notarized_session.transcript().responses[0];
proof_builder
.reveal_recv(response, CommitmentKind::Blake3)
.unwrap();
// Build the proof
let substrings_proof = proof_builder.build().unwrap();
let proof = TlsProof {
@@ -209,150 +192,3 @@ async fn main() {
.await
.unwrap();
}
async fn setup_notary_connection() -> (tokio_rustls::client::TlsStream<TcpStream>, String) {
// Connect to the Notary via TLS-TCP
let pem_file = str::from_utf8(include_bytes!(
"../../../notary-server/fixture/tls/rootCA.crt"
))
.unwrap();
let mut reader = std::io::BufReader::new(pem_file.as_bytes());
let mut certificates: Vec<Certificate> = rustls_pemfile::certs(&mut reader)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let certificate = certificates.remove(0);
let mut root_store = RootCertStore::empty();
root_store.add(&certificate).unwrap();
let client_notary_config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
let notary_connector = TlsConnector::from(Arc::new(client_notary_config));
let notary_socket = tokio::net::TcpStream::connect((NOTARY_HOST, NOTARY_PORT))
.await
.unwrap();
let notary_tls_socket = notary_connector
// Require the domain name of notary server to be the same as that in the server cert
.connect("tlsnotaryserver.io".try_into().unwrap(), notary_socket)
.await
.unwrap();
// Attach the hyper HTTP client to the notary TLS connection to send request to the /session endpoint to configure notarization and obtain session id
let (mut request_sender, connection) = hyper::client::conn::handshake(notary_tls_socket)
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
// Build the HTTP request to configure notarization
let payload = serde_json::to_string(&NotarizationSessionRequest {
client_type: ClientType::Tcp,
max_transcript_size: Some(NOTARY_MAX_TRANSCRIPT_SIZE),
})
.unwrap();
let request = Request::builder()
.uri(format!("https://{NOTARY_HOST}:{NOTARY_PORT}/session"))
.method("POST")
.header("Host", NOTARY_HOST)
// Need to specify application/json for axum to parse it as json
.header("Content-Type", "application/json")
.body(Body::from(payload))
.unwrap();
debug!("Sending configuration request");
let configuration_response = request_sender.send_request(request).await.unwrap();
debug!("Sent configuration request");
assert!(configuration_response.status() == StatusCode::OK);
debug!("Response OK");
// Pretty printing :)
let payload = to_bytes(configuration_response.into_body())
.await
.unwrap()
.to_vec();
let notarization_response =
serde_json::from_str::<NotarizationSessionResponse>(&String::from_utf8_lossy(&payload))
.unwrap();
debug!("Notarization response: {:?}", notarization_response,);
// Send notarization request via HTTP, where the underlying TCP connection will be extracted later
let request = Request::builder()
// Need to specify the session_id so that notary server knows the right configuration to use
// as the configuration is set in the previous HTTP call
.uri(format!(
"https://{}:{}/notarize?sessionId={}",
NOTARY_HOST,
NOTARY_PORT,
notarization_response.session_id.clone()
))
.method("GET")
.header("Host", NOTARY_HOST)
.header("Connection", "Upgrade")
// Need to specify this upgrade header for server to extract tcp connection later
.header("Upgrade", "TCP")
.body(Body::empty())
.unwrap();
debug!("Sending notarization request");
let response = request_sender.send_request(request).await.unwrap();
debug!("Sent notarization request");
assert!(response.status() == StatusCode::SWITCHING_PROTOCOLS);
debug!("Switched protocol OK");
// Claim back the TLS socket after HTTP exchange is done
let Parts {
io: notary_tls_socket,
..
} = connection_task.await.unwrap().unwrap();
(notary_tls_socket, notarization_response.session_id)
}
/// Find the ranges of the public and private parts of a sequence.
///
/// Returns a tuple of `(public, private)` ranges.
fn find_ranges(seq: &[u8], sub_seq: &[&[u8]]) -> (Vec<Range<usize>>, Vec<Range<usize>>) {
let mut private_ranges = Vec::new();
for s in sub_seq {
for (idx, w) in seq.windows(s.len()).enumerate() {
if w == *s {
private_ranges.push(idx..(idx + w.len()));
}
}
}
let mut sorted_ranges = private_ranges.clone();
sorted_ranges.sort_by_key(|r| r.start);
let mut public_ranges = Vec::new();
let mut last_end = 0;
for r in sorted_ranges {
if r.start > last_end {
public_ranges.push(last_end..r.start);
}
last_end = r.end;
}
if last_end < seq.len() {
public_ranges.push(last_end..seq.len());
}
(public_ranges, private_ranges)
}