diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 0c54941528..0272cad2b3 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -45,7 +45,7 @@ //! ```no_run //! # use tlsn_core::transcript::{TranscriptCommitConfigBuilder, Transcript, Direction}; //! # use tlsn_core::hash::HashAlgId; -//! # fn main() -> Result<(), Box { +//! # fn main() -> Result<(), Box> { //! # let transcript: Transcript = unimplemented!(); //! let (sent_len, recv_len) = transcript.len(); //! diff --git a/crates/core/src/transcript/proof.rs b/crates/core/src/transcript/proof.rs index 680b275c56..980987dd68 100644 --- a/crates/core/src/transcript/proof.rs +++ b/crates/core/src/transcript/proof.rs @@ -268,6 +268,32 @@ impl<'a> TranscriptProofBuilder<'a> { self.reveal_with_kind(ranges, direction, self.default_kind) } + /// Reveals the given ranges in the sent transcript using the default kind + /// of commitment. + /// + /// # Arguments + /// + /// * `ranges` - The ranges to reveal. + pub fn reveal_sent( + &mut self, + ranges: &dyn ToRangeSet, + ) -> Result<&mut Self, TranscriptProofBuilderError> { + self.reveal(ranges, Direction::Sent) + } + + /// Reveals the given ranges in the received transcript using the default + /// kind of commitment. + /// + /// # Arguments + /// + /// * `ranges` - The ranges to reveal. + pub fn reveal_recv( + &mut self, + ranges: &dyn ToRangeSet, + ) -> Result<&mut Self, TranscriptProofBuilderError> { + self.reveal(ranges, Direction::Received) + } + /// Builds the transcript proof. pub fn build(self) -> Result { let encoding_proof = if !self.encoding_proof_idxs.is_empty() { diff --git a/crates/data-fixtures/src/lib.rs b/crates/data-fixtures/src/lib.rs index 3651e4adea..658c7f70d1 100644 --- a/crates/data-fixtures/src/lib.rs +++ b/crates/data-fixtures/src/lib.rs @@ -4,7 +4,7 @@ macro_rules! define_fixture { ($name:ident, $doc:tt, $path:tt) => { #[doc = $doc] /// - /// ```ignore + /// ```text #[doc = include_str!($path)] /// ``` pub const $name: &[u8] = include_bytes!($path); diff --git a/crates/examples/.gitignore b/crates/examples/.gitignore new file mode 100644 index 0000000000..824f69e762 --- /dev/null +++ b/crates/examples/.gitignore @@ -0,0 +1,2 @@ +// Ignore files from examples. +*.tlsn \ No newline at end of file diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 2696493bf3..59761f1ddc 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -9,13 +9,17 @@ notary-client = { workspace = true } tlsn-common = { workspace = true } tlsn-core = { workspace = true } tlsn-prover = { workspace = true } +tlsn-utils = { workspace = true } tlsn-verifier = { workspace = true } +tlsn-formats = { workspace = true } +bincode = { workspace = true } chrono = { workspace = true } dotenv = { version = "0.15.0" } elliptic-curve = { workspace = true, features = ["pkcs8"] } futures = { workspace = true } http-body-util = { workspace = true } +hex = { workspace = true } hyper = { workspace = true, features = ["client", "http1"] } hyper-util = { workspace = true, features = ["full"] } k256 = { workspace = true, features = ["ecdsa"] } @@ -35,12 +39,16 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } [[example]] -name = "attestation_prover" -path = "attestation/prover.rs" +name = "attestation_prove" +path = "attestation/prove.rs" [[example]] -name = "attestation_verifier" -path = "attestation/verifier.rs" +name = "attestation_present" +path = "attestation/present.rs" + +[[example]] +name = "attestation_verify" +path = "attestation/verify.rs" [[example]] name = "interactive" @@ -49,7 +57,3 @@ path = "interactive/interactive.rs" [[example]] name = "discord_dm" path = "discord/discord_dm.rs" - -[[example]] -name = "discord_dm_verifier" -path = "discord/discord_dm_verifier.rs" diff --git a/crates/examples/attestation/README.md b/crates/examples/attestation/README.md index df7c382465..6cfb1db3de 100644 --- a/crates/examples/attestation/README.md +++ b/crates/examples/attestation/README.md @@ -1,19 +1,16 @@ -## Simple Example: Notarize Public Data from example.com (Rust) +## Simple Attestation Example: Notarize Public Data from example.com (Rust) This example demonstrates the simplest possible use case for TLSNotary: -1. Notarize: Fetch and create a proof of its content. -2. Verify the proof. - -Next, we will redact the content and verify it again: -1. Redact the `USER_AGENT` and titles. -2. Verify the redacted proof. +1. Fetch and acquire an attestation of its content. +2. Create a verifiable presentation using the attestation, while redacting the value of a header. +3. Verify the presentation. ### 1. Notarize -Run a simple prover: +Run the `prove` binary. ```shell -cargo run --release --example simple_prover +cargo run --release --example attestation_prove ``` If the notarization was successful, you should see this output in the console: @@ -22,66 +19,54 @@ If the notarization was successful, you should see this output in the console: Starting an MPC TLS connection with the server Got a response from the server Notarization completed successfully! -The proof has been written to `simple_proof.json` +The attestation has been written to `example.attestation.tlsn` and the corresponding secrets to `example.secrets.tlsn`. ``` -⚠️ In this simple example the `Notary` server is automatically started in the background. Note that this is for demonstration purposes only. In a real work example, the notary should be run by a neutral party or the verifier of the proofs. Consult the [Notary Server Docs](https://docs.tlsnotary.org/developers/notary_server.html) for more details on how to run a notary server. +⚠️ In this simple example the `Notary` server is automatically started in the background. Note that this is for demonstration purposes only. In a real world example, the notary should be run by a trusted party. Consult the [Notary Server Docs](https://docs.tlsnotary.org/developers/notary_server.html) for more details on how to run a notary server. + +### 2. Build a verifiable presentation -### 2. Verify the Proof +This will build a verifiable presentation with the `User-Agent` header redacted from the request. This presentation can be shared with any verifier you wish to present the data to. -When you open `simple_proof.json` in an editor, you will see a JSON file with lots of non-human-readable byte arrays. You can decode this file by running: +Run the `present` binary. ```shell -cargo run --release --example simple_verifier +cargo run --release --example attestation_present ``` -This will output the TLS-transaction in clear text: +If successful, you should see this output in the console: ```log -Successfully verified that the bytes below came from a session with Dns("example.com") at 2023-11-03 08:48:20 UTC. -Note that the bytes which the Prover chose not to disclose are shown as X. - -Bytes sent: -... -``` - -### 3. Redact Information - -Open `simple_prover.rs` and locate the line with: - -```rust -let redact = false; +Presentation built successfully! +The presentation has been written to `example.presentation.tlsn`. ``` -and change it to: +### 3. Verify the presentation -```rust -let redact = true; -``` +This will read the presentation from the previous step, verify it, and print the disclosed data to console. -Next, if you run the `simple_prover` and `simple_verifier` again, you'll notice redacted `X`'s in the output: +Run the `verify` binary. ```shell -cargo run --release --example simple_prover -cargo run --release --example simple_verifier +cargo run --release --example attestation_verify ``` +If successful, you should see this output in the console: + ```log - - - - XXXXXXXXXXXXXX -... -``` +Verifying presentation with {key algorithm} key: { hex encoded key } -You can also use to inspect your proofs. Simply drag and drop `simple_proof.json` from your file explorer into the drop zone. Redacted bytes are marked with X characters. [Notary public key](../../notary/server/fixture/notary/notary.pub) +**Ask yourself, do you trust this key?** -### (Optional) Extra Experiments +------------------------------------------------------------------- +Successfully verified that the data below came from a session with example.com at 2024-10-03 03:01:40 UTC. +Note that the data which the Prover chose not to disclose are shown as X. -Feel free to try these extra challenges: +Data sent: +... +``` -- [ ] Modify the `server_name` (or any other data) in `simple_proof.json` and verify that the proof is no longer valid. -- [ ] Modify the `build_proof_with_redactions` function in `simple_prover.rs` to redact more or different data. +⚠️ Notice that the presentation comes with a "verifying key". This is the key the Notary used when issuing the attestation that the presentation was built from. If you trust the Notary, or more specifically the verifying key, then you can trust that the presented data is authentic. ### Next steps diff --git a/crates/examples/attestation/present.rs b/crates/examples/attestation/present.rs new file mode 100644 index 0000000000..802c2c0d26 --- /dev/null +++ b/crates/examples/attestation/present.rs @@ -0,0 +1,61 @@ +// This example demonstrates how to build a verifiable presentation from an +// attestation and the corresponding connection secrets. See the `prove.rs` +// example to learn how to acquire an attestation from a Notary. + +use tlsn_core::{attestation::Attestation, presentation::Presentation, CryptoProvider, Secrets}; +use tlsn_formats::http::HttpTranscript; + +fn main() -> Result<(), Box> { + // Read attestation from disk. + let attestation: Attestation = + bincode::deserialize(&std::fs::read("example.attestation.tlsn")?)?; + + // Read secrets from disk. + let secrets: Secrets = bincode::deserialize(&std::fs::read("example.secrets.tlsn")?)?; + + // Parse the HTTP transcript. + let transcript = HttpTranscript::parse(secrets.transcript())?; + + // Build a transcript proof. + let mut builder = secrets.transcript_proof_builder(); + + let request = &transcript.requests[0]; + // Reveal the structure of the request without the headers or body. + builder.reveal_sent(&request.without_data())?; + // Reveal the request target. + builder.reveal_sent(&request.request.target)?; + // Reveal all headers except the value of the User-Agent header. + for header in &request.headers { + if !header.name.as_str().eq_ignore_ascii_case("User-Agent") { + builder.reveal_sent(header)?; + } else { + builder.reveal_sent(&header.without_value())?; + } + } + // Reveal the entire response. + builder.reveal_recv(&transcript.responses[0])?; + + let transcript_proof = builder.build()?; + + // Use default crypto provider to build the presentation. + let provider = CryptoProvider::default(); + + let mut builder = attestation.presentation_builder(&provider); + + builder + .identity_proof(secrets.identity_proof()) + .transcript_proof(transcript_proof); + + let presentation: Presentation = builder.build()?; + + // Write the presentation to disk. + std::fs::write( + "example.presentation.tlsn", + bincode::serialize(&presentation)?, + )?; + + println!("Presentation built successfully!"); + println!("The presentation has been written to `example.presentation.tlsn`."); + + Ok(()) +} diff --git a/crates/examples/attestation/prove.rs b/crates/examples/attestation/prove.rs new file mode 100644 index 0000000000..23eb733750 --- /dev/null +++ b/crates/examples/attestation/prove.rs @@ -0,0 +1,125 @@ +// 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 http_body_util::Empty; +use hyper::{body::Bytes, Request, StatusCode}; +use hyper_util::rt::TokioIo; +use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; + +use tlsn_common::config::ProtocolConfig; +use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig}; +use tlsn_examples::run_notary; +use tlsn_formats::http::{DefaultHttpCommitter, HttpCommit, HttpTranscript}; +use tlsn_prover::{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"; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + let (prover_socket, notary_socket) = tokio::io::duplex(1 << 16); + + // Start a local simple notary service + tokio::spawn(run_notary(notary_socket.compat())); + + // Prover configuration. + let config = ProverConfig::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(1024) + .max_recv_data(4096) + .build()?, + ) + .build()?; + + // Create a new prover and perform necessary setup. + let prover = Prover::new(config).setup(prover_socket.compat()).await?; + + // Open a TCP connection to the server. + let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443)).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 = Request::builder() + .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) + .body(Empty::::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"); + + assert!(response.status() == StatusCode::OK); + + // The prover task should be done now, so we can await it. + let prover = prover_task.await??; + + // Prepare for notarization. + let mut prover = prover.start_notarize(); + + // Parse the HTTP transcript. + let transcript = HttpTranscript::parse(prover.transcript())?; + + // Commit to the transcript. + let mut builder = TranscriptCommitConfig::builder(prover.transcript()); + + DefaultHttpCommitter::default().commit_transcript(&mut builder, &transcript)?; + + prover.transcript_commit(builder.build()?); + + // Request an attestation. + let config = RequestConfig::default(); + + let (attestation, secrets) = prover.finalize(&config).await?; + + // Write the attestation to disk. + tokio::fs::write( + "example.attestation.tlsn", + bincode::serialize(&attestation)?, + ) + .await?; + + // Write the secrets to disk. + tokio::fs::write("example.secrets.tlsn", bincode::serialize(&secrets)?).await?; + + println!("Notarization completed successfully!"); + println!( + "The attestation has been written to `example.attestation.tlsn` and the \ + corresponding secrets to `example.secrets.tlsn`." + ); + + Ok(()) +} diff --git a/crates/examples/attestation/prover.rs b/crates/examples/attestation/prover.rs deleted file mode 100644 index 6abbb2ab36..0000000000 --- a/crates/examples/attestation/prover.rs +++ /dev/null @@ -1,236 +0,0 @@ -// 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_common::config::ProtocolConfig; -use tlsn_core::proof::TlsProof; -use tokio::io::AsyncWriteExt as _; -use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; - -use tlsn_examples::run_notary; -use tlsn_prover::{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 std::str; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - let (prover_socket, notary_socket) = tokio::io::duplex(1 << 16); - - // Start a local simple notary service - tokio::spawn(run_notary(notary_socket.compat())); - - // Prover configuration. - let config = ProverConfig::builder() - .id("example") - .server_name(SERVER_DOMAIN) - .protocol_config( - ProtocolConfig::builder() - // Configure the limit of the data sent and received. - .max_sent_data(1024) - .max_recv_data(4096) - .build() - .unwrap(), - ) - .build() - .unwrap(); - - // 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(prover_socket.compat()) - .await - .unwrap(); - - // Connect to the Server via TCP. This is the TLS client socket. - let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443)) - .await - .unwrap(); - - // 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.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::http1::handshake(mpc_tls_connection) - .await - .unwrap(); - - // Spawn the HTTP task to be run concurrently - tokio::spawn(connection); - - // Build a simple HTTP request with common headers - let request = Request::builder() - .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) - .body(Empty::::new()) - .unwrap(); - - println!("Starting an MPC TLS connection with the server"); - - // Send the request to the Server and get a response via the MPC TLS connection - let response = request_sender.send_request(request).await.unwrap(); - - println!("Got a response from the server"); - - assert!(response.status() == StatusCode::OK); - - // The Prover task should be done now, so we can grab the Prover. - let prover = prover_task.await.unwrap().unwrap(); - - // Prepare for notarization. - let prover = prover.start_notarize(); - - // Build proof (with or without redactions) - let redact = false; - let proof = if !redact { - build_proof_without_redactions(prover).await - } else { - build_proof_with_redactions(prover).await - }; - - // Write the proof to a file - let mut file = tokio::fs::File::create("simple_proof.json").await.unwrap(); - file.write_all(serde_json::to_string_pretty(&proof).unwrap().as_bytes()) - .await - .unwrap(); - - println!("Notarization completed successfully!"); - println!("The proof has been written to `simple_proof.json`"); -} - -/// Find the ranges of the public and private parts of a sequence. -/// -/// Returns a tuple of `(public, private)` ranges. -fn find_ranges(seq: &[u8], private_seq: &[&[u8]]) -> (Vec>, Vec>) { - let mut private_ranges = Vec::new(); - for s in private_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) -} - -async fn build_proof_without_redactions(mut prover: Prover) -> TlsProof { - let sent_len = prover.sent_transcript().data().len(); - let recv_len = prover.recv_transcript().data().len(); - - let builder = prover.commitment_builder(); - let sent_commitment = builder.commit_sent(&(0..sent_len)).unwrap(); - let recv_commitment = builder.commit_recv(&(0..recv_len)).unwrap(); - - // Finalize, returning the attestation and secrets. - let (attestation, secrets) = prover.finalize().await.unwrap(); - - // Create a proof for all committed data in this session - let mut proof_builder = notarized_session.data().build_substrings_proof(); - - // Reveal all the public ranges - proof_builder.reveal_by_id(sent_commitment).unwrap(); - proof_builder.reveal_by_id(recv_commitment).unwrap(); - - let substrings_proof = proof_builder.build().unwrap(); - - TlsProof { - session: notarized_session.session_proof(), - substrings: substrings_proof, - } -} - -async fn build_proof_with_redactions(mut prover: Prover) -> TlsProof { - // Identify the ranges in the outbound data which contain data which we want to - // disclose - let (sent_public_ranges, _) = find_ranges( - prover.sent_transcript().data(), - &[ - // Redact the value of the "User-Agent" header. It will NOT be disclosed. - USER_AGENT.as_bytes(), - ], - ); - - // Identify the ranges in the inbound data which contain data which we want to - // disclose - let (recv_public_ranges, _) = find_ranges( - prover.recv_transcript().data(), - &[ - // Redact the value of the title. It will NOT be disclosed. - "Example Domain".as_bytes(), - ], - ); - - let builder = prover.commitment_builder(); - - // Commit to each range of the public outbound data which we want to disclose - let sent_commitments: Vec<_> = sent_public_ranges - .iter() - .map(|range| builder.commit_sent(range).unwrap()) - .collect(); - // Commit to each range of the public inbound data which we want to disclose - let recv_commitments: Vec<_> = recv_public_ranges - .iter() - .map(|range| builder.commit_recv(range).unwrap()) - .collect(); - - // Finalize, returning the notarized session - let notarized_session = prover.finalize().await.unwrap(); - - // Create a proof for all committed data in this session - let mut proof_builder = notarized_session.data().build_substrings_proof(); - - // Reveal all the public ranges - for commitment_id in sent_commitments { - proof_builder.reveal_by_id(commitment_id).unwrap(); - } - for commitment_id in recv_commitments { - proof_builder.reveal_by_id(commitment_id).unwrap(); - } - - let substrings_proof = proof_builder.build().unwrap(); - - TlsProof { - session: notarized_session.session_proof(), - substrings: substrings_proof, - } -} diff --git a/crates/examples/attestation/verifier.rs b/crates/examples/attestation/verifier.rs deleted file mode 100644 index 4028e482a6..0000000000 --- a/crates/examples/attestation/verifier.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{str, time::Duration}; - -use elliptic_curve::pkcs8::DecodePublicKey; - -use tlsn_core::proof::{SessionProof, TlsProof}; - -/// A simple verifier which reads a proof generated by `simple_prover.rs` from -/// "proof.json", verifies it and prints the verified data to the console. -fn main() { - // Deserialize the proof - let proof = std::fs::read_to_string("simple_proof.json").unwrap(); - let proof: TlsProof = serde_json::from_str(proof.as_str()).unwrap(); - - let TlsProof { - // The session proof establishes the identity of the server and the commitments - // to the TLS transcript. - session, - // The substrings proof proves select portions of the transcript, while redacting - // anything the Prover chose not to disclose. - substrings, - } = proof; - - // Verify the session proof against the Notary's public key - // - // This verifies the identity of the server using a default certificate verifier - // which trusts the root certificates from the `webpki-roots` crate. - session - .verify_with_default_cert_verifier(notary_pubkey()) - .unwrap(); - - let SessionProof { - // The session header that was signed by the Notary is a succinct commitment to the TLS - // transcript. - header, - // This is the session_info, which contains the server_name, that is checked against the - // certificate chain shared in the TLS handshake. - session_info, - .. - } = session; - - // The time at which the session was recorded - let time = chrono::DateTime::UNIX_EPOCH + Duration::from_secs(header.time()); - - // Verify the substrings proof against the session header. - // - // This returns the redacted transcripts - let (mut sent, mut recv) = substrings.verify(&header).unwrap(); - - // Replace the bytes which the Prover chose not to disclose with 'X' - sent.set_redacted(b'X'); - recv.set_redacted(b'X'); - - println!("-------------------------------------------------------------------"); - println!( - "Successfully verified that the bytes below came from a session with {:?} at {}.", - session_info.server_name, time - ); - println!("Note that the bytes which the Prover chose not to disclose are shown as X."); - println!(); - println!("Bytes sent:"); - println!(); - print!("{}", String::from_utf8(sent.data().to_vec()).unwrap()); - println!(); - println!("Bytes received:"); - println!(); - println!("{}", String::from_utf8(recv.data().to_vec()).unwrap()); - println!("-------------------------------------------------------------------"); -} - -/// Returns a Notary pubkey trusted by this Verifier -fn notary_pubkey() -> p256::PublicKey { - let pem_file = str::from_utf8(include_bytes!( - "../../notary/server/fixture/notary/notary.pub" - )) - .unwrap(); - p256::PublicKey::from_public_key_pem(pem_file).unwrap() -} diff --git a/crates/examples/attestation/verify.rs b/crates/examples/attestation/verify.rs new file mode 100644 index 0000000000..6b715c924f --- /dev/null +++ b/crates/examples/attestation/verify.rs @@ -0,0 +1,60 @@ +// This example demonstrates how to verify a presentation. See `present.rs` for +// an example of how to build a presentation from an attestation and connection +// secrets. + +use std::time::Duration; + +use tlsn_core::{ + presentation::{Presentation, PresentationOutput}, + signing::VerifyingKey, + CryptoProvider, +}; + +fn main() -> Result<(), Box> { + // Read the presentation from disk. + let presentation: Presentation = + bincode::deserialize(&std::fs::read("example.presentation.tlsn")?)?; + + let provider = CryptoProvider::default(); + + let VerifyingKey { + alg, + data: key_data, + } = presentation.verifying_key(); + + println!( + "Verifying presentation with {alg} key: {}\n\n**Ask yourself, do you trust this key?**\n", + hex::encode(key_data) + ); + + // Verify the presentation. + let PresentationOutput { + server_name, + connection_info, + transcript, + .. + } = presentation.verify(&provider).unwrap(); + + // The time at which the connection was started. + let time = chrono::DateTime::UNIX_EPOCH + Duration::from_secs(connection_info.time); + let server_name = server_name.unwrap(); + let mut partial_transcript = transcript.unwrap(); + // Set the unauthenticated bytes so they are distinguishable. + partial_transcript.set_unauthed(b'X'); + + let sent = String::from_utf8_lossy(partial_transcript.sent_unsafe()); + let recv = String::from_utf8_lossy(partial_transcript.received_unsafe()); + + println!("-------------------------------------------------------------------"); + println!( + "Successfully verified that the data below came from a session with {server_name} at {time}.", + ); + println!("Note that the data which the Prover chose not to disclose are shown as X.\n"); + println!("Data sent:\n"); + println!("{}\n", sent); + println!("Data received:\n"); + println!("{}\n", recv); + println!("-------------------------------------------------------------------"); + + Ok(()) +} diff --git a/crates/examples/discord/README.md b/crates/examples/discord/README.md index 6bad62b1d8..79ce461242 100644 --- a/crates/examples/discord/README.md +++ b/crates/examples/discord/README.md @@ -1,6 +1,6 @@ # Notarize Discord DMs -The `discord_dm.rs` example sets up a TLS connection with Discord and notarizes the requested DMs. The notarized session and the proof are written to local JSON files (`discord_dm_notarized_session.json` and `discord_dm_proof.json`) for easier inspection. +The `discord_dm.rs` example sets up a TLS connection with Discord and notarizes the requested DMs. The attestation and secrets are saved to disk. This involves 3 steps: 1. Configure the inputs @@ -84,16 +84,6 @@ If the transcript was too long, you may encounter the following error. This occu thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: IOError(Custom { kind: InvalidData, error: BackendError(DecryptionError("Other: KOSReceiverActor is not setup")) })', /Users/heeckhau/tlsnotary/tlsn/tlsn/tlsn-prover/src/lib.rs:173:50 ``` -# Verifier +# Verify -The `discord_dm` example also generated a proof of the transcript with the `Authorization` header redacted from the request, saved in `discord_dm_proof.json`. - -We can verify this proof using the `discord_dm_verifier` by running: - -``` -cargo run --release --example discord_dm_verifier -``` - -This will verify the proof and print out the redacted transcript! - -> **_NOTE:_** ℹ️ hosts a generic proof visualizer. Drag and drop your proof into the drop zone to check and render your proof. [Notary public key](../../notary/server/fixture/notary/notary.pub) \ No newline at end of file +See the [`present`](../attestation/present.rs) and [`verify`](../attestation/verify.rs) examples for a demonstration of how to construct a presentation and verify it. \ No newline at end of file diff --git a/crates/examples/discord/discord_dm.rs b/crates/examples/discord/discord_dm.rs index 0f18c69339..dd13198063 100644 --- a/crates/examples/discord/discord_dm.rs +++ b/crates/examples/discord/discord_dm.rs @@ -6,13 +6,14 @@ use http_body_util::{BodyExt, Empty}; use hyper::{body::Bytes, Request, StatusCode}; use hyper_util::rt::TokioIo; use notary_client::{Accepted, NotarizationRequest, NotaryClient}; -use std::{env, ops::Range, str}; -use tlsn_common::config::ProtocolConfig; -use tlsn_core::proof::TlsProof; -use tlsn_prover::{Prover, ProverConfig}; -use tokio::io::AsyncWriteExt as _; +use std::{env, str}; use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tracing::debug; +use utils::range::RangeSet; + +use tlsn_common::config::ProtocolConfig; +use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig}; +use tlsn_prover::{Prover, ProverConfig}; // Setting of the application server const SERVER_DOMAIN: &str = "discord.com"; @@ -56,12 +57,12 @@ async fn main() { let Accepted { io: notary_connection, - id: session_id, + id: _session_id, .. } = notary_client .request_notarization(notarization_request) .await - .unwrap(); + .expect("Could not connect to notary. Make sure it is running."); // Set up protocol configuration for prover. let protocol_config = ProtocolConfig::builder() @@ -70,16 +71,12 @@ async fn main() { .build() .unwrap(); - // Configure a new prover with the unique session id returned from notary - // client. + // Create a new prover and set up the MPC backend. let prover_config = ProverConfig::builder() - .id(session_id) .server_name(SERVER_DOMAIN) .protocol_config(protocol_config) .build() .unwrap(); - - // Create a new prover and set up the MPC backend. let prover = Prover::new(prover_config) .setup(notary_connection.compat()) .await @@ -143,70 +140,50 @@ async fn main() { let mut prover = prover.start_notarize(); // Identify the ranges in the transcript that contain secrets - let (public_ranges, private_ranges) = - find_ranges(prover.sent_transcript().data(), &[auth_token.as_bytes()]); + let sent_transcript = prover.transcript().sent(); + let recv_transcript = prover.transcript().received(); + + // Identify the ranges in the outbound data which contain data which we want to + // disclose + let (sent_public_ranges, _) = find_ranges(sent_transcript, &[auth_token.as_bytes()]); + #[allow(clippy::single_range_in_vec_init)] + let recv_public_ranges = RangeSet::from([0..recv_transcript.len()]); - let recv_len = prover.recv_transcript().data().len(); + let mut builder = TranscriptCommitConfig::builder(prover.transcript()); - let builder = prover.commitment_builder(); + // Commit to public ranges + builder.commit_sent(&sent_public_ranges).unwrap(); + builder.commit_recv(&recv_public_ranges).unwrap(); - // Collect commitment ids for the outbound transcript - let mut commitment_ids = public_ranges - .iter() - .chain(private_ranges.iter()) - .map(|range| builder.commit_sent(range).unwrap()) - .collect::>(); + let config = builder.build().unwrap(); - // Commit to the full received transcript in one shot, as we don't need to - // redact anything - commitment_ids.push(builder.commit_recv(&(0..recv_len)).unwrap()); + prover.transcript_commit(config); // Finalize, returning the notarized session - let notarized_session = prover.finalize().await.unwrap(); + let request_config = RequestConfig::default(); + let (attestation, secrets) = prover.finalize(&request_config).await.unwrap(); debug!("Notarization complete!"); - // Dump the notarized session to a file - let mut file = tokio::fs::File::create("discord_dm_notarized_session.json") - .await - .unwrap(); - file.write_all( - serde_json::to_string_pretty(¬arized_session) - .unwrap() - .as_bytes(), + tokio::fs::write( + "discord.attestation.tlsn", + bincode::serialize(&attestation).unwrap(), ) .await .unwrap(); - let session_proof = notarized_session.session_proof(); - - let mut proof_builder = notarized_session.data().build_substrings_proof(); - - // Reveal everything but the auth token (which was assigned commitment id 2) - proof_builder.reveal_by_id(commitment_ids[0]).unwrap(); - proof_builder.reveal_by_id(commitment_ids[1]).unwrap(); - proof_builder.reveal_by_id(commitment_ids[3]).unwrap(); - - let substrings_proof = proof_builder.build().unwrap(); - - let proof = TlsProof { - session: session_proof, - substrings: substrings_proof, - }; - - // Dump the proof to a file. - let mut file = tokio::fs::File::create("discord_dm_proof.json") - .await - .unwrap(); - file.write_all(serde_json::to_string_pretty(&proof).unwrap().as_bytes()) - .await - .unwrap(); + tokio::fs::write( + "discord.secrets.tlsn", + bincode::serialize(&secrets).unwrap(), + ) + .await + .unwrap(); } /// 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>, Vec>) { +fn find_ranges(seq: &[u8], sub_seq: &[&[u8]]) -> (RangeSet, RangeSet) { let mut private_ranges = Vec::new(); for s in sub_seq { for (idx, w) in seq.windows(s.len()).enumerate() { @@ -232,5 +209,8 @@ fn find_ranges(seq: &[u8], sub_seq: &[&[u8]]) -> (Vec>, Vec p256::PublicKey { - let pem_file = str::from_utf8(include_bytes!( - "../../notary/server/fixture/notary/notary.pub" - )) - .unwrap(); - p256::PublicKey::from_public_key_pem(pem_file).unwrap() -} diff --git a/crates/examples/interactive/interactive.rs b/crates/examples/interactive/interactive.rs index c6daad88b7..dd6fd86a35 100644 --- a/crates/examples/interactive/interactive.rs +++ b/crates/examples/interactive/interactive.rs @@ -2,9 +2,9 @@ use http_body_util::Empty; use hyper::{body::Bytes, Request, StatusCode, Uri}; use hyper_util::rt::TokioIo; use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator}; -use tlsn_core::{proof::SessionInfo, Direction, RedactedTranscript}; +use tlsn_core::transcript::Idx; use tlsn_prover::{state::Prove, Prover, ProverConfig}; -use tlsn_verifier::tls::{Verifier, VerifierConfig}; +use tlsn_verifier::{SessionInfo, Verifier, VerifierConfig}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tracing::instrument; @@ -31,13 +31,10 @@ async fn main() { let (_, (sent, received, _session_info)) = tokio::join!(prover, verifier); println!("Successfully verified {}", &uri); - println!( - "Verified sent data:\n{}", - bytes_to_redacted_string(sent.data()) - ); + println!("Verified sent data:\n{}", bytes_to_redacted_string(&sent)); println!( "Verified received data:\n{}", - bytes_to_redacted_string(received.data()) + bytes_to_redacted_string(&received) ); } @@ -57,7 +54,6 @@ async fn prover( // Perform the setup phase with the verifier. let prover = Prover::new( ProverConfig::builder() - .id(id) .server_name(server_domain) .protocol_config( ProtocolConfig::builder() @@ -112,9 +108,12 @@ async fn prover( // Create proof for the Verifier. let mut prover = prover_task.await.unwrap().unwrap().start_prove(); - redact_and_reveal_received_data(&mut prover); - redact_and_reveal_sent_data(&mut prover); - prover.prove().await.unwrap(); + + let idx_sent = redact_and_reveal_sent_data(&mut prover); + let idx_recv = redact_and_reveal_received_data(&mut prover); + + // Reveal parts of the transcript + prover.prove_transcript(idx_sent, idx_recv).await.unwrap(); // Finalize. prover.finalize().await.unwrap() @@ -124,7 +123,7 @@ async fn prover( async fn verifier( socket: T, id: &str, -) -> (RedactedTranscript, RedactedTranscript, SessionInfo) { +) -> (Vec, Vec, SessionInfo) { // Setup Verifier. let config_validator = ProtocolConfigValidator::builder() .max_sent_data(MAX_SENT_DATA) @@ -133,24 +132,25 @@ async fn verifier( .unwrap(); let verifier_config = VerifierConfig::builder() - .id(id) .protocol_config_validator(config_validator) .build() .unwrap(); let verifier = Verifier::new(verifier_config); // Verify MPC-TLS and wait for (redacted) data. - let (sent, received, session_info) = verifier.verify(socket.compat()).await.unwrap(); + let (mut partial_transcript, session_info) = verifier.verify(socket.compat()).await.unwrap(); + partial_transcript.set_unauthed(0); // Check sent data: check host. - let sent_data = String::from_utf8(sent.data().to_vec()).expect("Verifier expected sent data"); + let sent = partial_transcript.sent_unsafe().to_vec(); + let sent_data = String::from_utf8(sent.clone()).expect("Verifier expected sent data"); sent_data .find(SERVER_DOMAIN) .unwrap_or_else(|| panic!("Verification failed: Expected host {}", SERVER_DOMAIN)); // Check received data: check json and version number. - let response = - String::from_utf8(received.data().to_vec()).expect("Verifier expected received data"); + let received = partial_transcript.received_unsafe().to_vec(); + let response = String::from_utf8(received.clone()).expect("Verifier expected received data"); response .find("Example Domain") .expect("Expected valid data from example.com"); @@ -162,35 +162,35 @@ async fn verifier( } /// Redacts and reveals received data to the verifier. -fn redact_and_reveal_received_data(prover: &mut Prover) { - let recv_transcript_len = prover.recv_transcript().data().len(); +fn redact_and_reveal_received_data(prover: &mut Prover) -> Idx { + let recv_transcript = prover.transcript().received(); + let recv_transcript_len = recv_transcript.len(); // Get the received data as a string. - let received_string = String::from_utf8(prover.recv_transcript().data().to_vec()).unwrap(); + let received_string = String::from_utf8(recv_transcript.to_vec()).unwrap(); // Find the substring "illustrative". let start = received_string .find("illustrative") .expect("Error: The substring 'illustrative' was not found in the received data."); let end = start + "illustrative".len(); - // Reveal everything except for the substring "illustrative". - _ = prover.reveal(0..start, Direction::Received); - _ = prover.reveal(end..recv_transcript_len, Direction::Received); + Idx::new([0..start, end..recv_transcript_len]) } /// Redacts and reveals sent data to the verifier. -fn redact_and_reveal_sent_data(prover: &mut Prover) { - let sent_transcript_len = prover.sent_transcript().data().len(); +fn redact_and_reveal_sent_data(prover: &mut Prover) -> Idx { + let sent_transcript = prover.transcript().sent(); + let sent_transcript_len = sent_transcript.len(); + + let sent_string = String::from_utf8(sent_transcript.to_vec()).unwrap(); - let sent_string = String::from_utf8(prover.sent_transcript().data().to_vec()).unwrap(); let secret_start = sent_string.find(SECRET).unwrap(); // Reveal everything except for the SECRET. - _ = prover.reveal(0..secret_start, Direction::Sent); - _ = prover.reveal( + Idx::new([ + 0..secret_start, secret_start + SECRET.len()..sent_transcript_len, - Direction::Sent, - ); + ]) } /// Render redacted bytes as `🙈`. diff --git a/crates/examples/src/lib.rs b/crates/examples/src/lib.rs index 5d201d2438..f74b9c10d0 100644 --- a/crates/examples/src/lib.rs +++ b/crates/examples/src/lib.rs @@ -1,4 +1,5 @@ use futures::{AsyncRead, AsyncWrite}; +use k256::{pkcs8::DecodePrivateKey, SecretKey}; use tlsn_common::config::ProtocolConfigValidator; use tlsn_core::{attestation::AttestationConfig, signing::SignatureAlgId, CryptoProvider}; use tlsn_verifier::{Verifier, VerifierConfig}; @@ -13,8 +14,11 @@ const MAX_RECV_DATA: usize = 1 << 14; /// Runs a simple Notary with the provided connection to the Prover. pub async fn run_notary(conn: T) { + let pem_data = include_str!("../../notary/server/fixture/notary/notary.key"); + let secret_key = SecretKey::from_pkcs8_pem(pem_data).unwrap().to_bytes(); + let mut provider = CryptoProvider::default(); - provider.signer.set_secp256k1(NOTARY_PRIVATE_KEY).unwrap(); + provider.signer.set_secp256k1(&secret_key).unwrap(); // Setup the config. Normally a different ID would be generated // for each notarization. diff --git a/crates/notary/server/Cargo.toml b/crates/notary/server/Cargo.toml index 856177014e..d4860b0bf8 100644 --- a/crates/notary/server/Cargo.toml +++ b/crates/notary/server/Cargo.toml @@ -43,6 +43,7 @@ tokio-util = { workspace = true, features = ["compat"] } tower = { version = "0.4", features = ["make"] } tower-http = { version = "0.5", features = ["cors"] } tower-service = { version = "0.3" } +tower-util = { version = "0.3.1" } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } uuid = { workspace = true, features = ["v4", "fast-rng"] } diff --git a/crates/notary/server/fixture/notary/notary.key b/crates/notary/server/fixture/notary/notary.key index a88cf51f80..716507fdc3 100644 --- a/crates/notary/server/fixture/notary/notary.key +++ b/crates/notary/server/fixture/notary/notary.key @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEvBc/VMWn3E4PGfe -ETc/ekdTRmRwNN9J6eKDPxJ98ZmhRANCAAQG/foUjhkWzMlrQNAUnfBYJe9UsWtx -HMwbmRpN4cahLMO7pwWrHe4RZikUajoLQQ5SB/6YSBuS0utehy/nIfMq +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgbGCmm+WHxwlKKKRWddfO +02TmpM787BJQuoVrHeCI5v6hRANCAAR7SPGcE5toiPteODpNcsIzUYb9WFjnrnQ6 +tL+OBxsG5+j9AN8W8v+KvMi/UlKaEaJVywIcLCiWENdZyB7u/Yix -----END PRIVATE KEY----- diff --git a/crates/notary/server/fixture/notary/notary.pub b/crates/notary/server/fixture/notary/notary.pub index fa63c8d282..c1d58a4255 100644 --- a/crates/notary/server/fixture/notary/notary.pub +++ b/crates/notary/server/fixture/notary/notary.pub @@ -1,4 +1,4 @@ -----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr -cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg== +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEe0jxnBObaIj7Xjg6TXLCM1GG/VhY5650 +OrS/jgcbBufo/QDfFvL/irzIv1JSmhGiVcsCHCwolhDXWcge7v2IsQ== -----END PUBLIC KEY----- diff --git a/crates/notary/server/src/service/axum_websocket.rs b/crates/notary/server/src/service/axum_websocket.rs index 9a21cb8b85..991391b6f9 100644 --- a/crates/notary/server/src/service/axum_websocket.rs +++ b/crates/notary/server/src/service/axum_websocket.rs @@ -898,7 +898,8 @@ mod tests { use super::*; use axum::{body::Body, routing::get, Router}; use http::{Request, Version}; - use tower::ServiceExt; + // NOTARY_MODIFICATION: use tower_util instead of tower to make clippy happy + use tower_util::ServiceExt; #[tokio::test] async fn rejects_http_1_0_requests() { diff --git a/crates/notary/tests-integration/tests/notary.rs b/crates/notary/tests-integration/tests/notary.rs index 846376fc8e..da067cd58b 100644 --- a/crates/notary/tests-integration/tests/notary.rs +++ b/crates/notary/tests-integration/tests/notary.rs @@ -15,10 +15,7 @@ use std::{string::String, time::Duration}; use tls_core::verify::WebPkiVerifier; use tls_server_fixture::{bind_test_server_hyper, CA_CERT_DER, SERVER_DOMAIN}; use tlsn_common::config::ProtocolConfig; -use tlsn_core::{ - request::RequestConfig, signing::SignatureAlgId, transcript::TranscriptCommitConfig, - CryptoProvider, -}; +use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig, CryptoProvider}; use tlsn_prover::{Prover, ProverConfig}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; @@ -260,11 +257,7 @@ async fn test_tcp_prover( prover.transcript_commit(commit_config); - let mut request_builder = RequestConfig::builder(); - - request_builder.signature_alg(SignatureAlgId::SECP256R1); - - let request = request_builder.build().unwrap(); + let request = RequestConfig::builder().build().unwrap(); _ = prover.finalize(&request).await.unwrap(); @@ -453,11 +446,7 @@ async fn test_websocket_prover() { prover.transcript_commit(commit_config); - let mut request_builder = RequestConfig::builder(); - - request_builder.signature_alg(SignatureAlgId::SECP256R1); - - let request = request_builder.build().unwrap(); + let request = RequestConfig::builder().build().unwrap(); _ = prover.finalize(&request).await.unwrap(); diff --git a/crates/wasm-test-runner/src/chrome_driver.rs b/crates/wasm-test-runner/src/chrome_driver.rs index 1ad585cf02..fc192c8c42 100644 --- a/crates/wasm-test-runner/src/chrome_driver.rs +++ b/crates/wasm-test-runner/src/chrome_driver.rs @@ -16,6 +16,7 @@ use crate::{TestResult, DEFAULT_SERVER_IP, DEFAULT_WASM_PORT}; pub async fn run() -> Result> { let config = BrowserConfig::builder() .request_timeout(Duration::from_secs(60)) + .incognito() // Run in incognito mode to avoid unexplained WS connection errors in chromiumoxide. .build() .map_err(|s| anyhow!(s))?;