mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-10 07:07:57 -05:00
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:
@@ -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]]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
155
tlsn/examples/src/lib.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
@@ -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(¬arized_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 = ¬arized_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 = ¬arized_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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user