From 64e0d8c3aa313a488b223a35104b2ecb71596c31 Mon Sep 17 00:00:00 2001 From: th4s Date: Mon, 28 Aug 2023 13:43:36 +0200 Subject: [PATCH 01/11] Check tlsn-verifier WASM compatibility --- tlsn/Cargo.toml | 4 ++ tlsn/tlsn-verifier/Cargo.toml | 30 +++++++++ tlsn/tlsn-verifier/src/lib.rs | 115 ++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 tlsn/tlsn-verifier/Cargo.toml create mode 100644 tlsn/tlsn-verifier/src/lib.rs diff --git a/tlsn/Cargo.toml b/tlsn/Cargo.toml index a957f2ab53..9a793e14d4 100644 --- a/tlsn/Cargo.toml +++ b/tlsn/Cargo.toml @@ -3,6 +3,7 @@ members = [ "tlsn-core", "tlsn-notary", "tlsn-prover", + "tlsn-verifier", "tests-integration", "examples", ] @@ -43,11 +44,14 @@ rand_chacha = "0.3" rand = "0.8" rand_core = "0.6" webpki-roots = "0.23" +elliptic-curve = "0.13" derive_builder = "0.12" thiserror = "1" serde = "1" +serde_json = "1" bincode = "1" + hex = "0.4" bytes = "1.4" opaque-debug = "0.3" diff --git a/tlsn/tlsn-verifier/Cargo.toml b/tlsn/tlsn-verifier/Cargo.toml new file mode 100644 index 0000000000..7ce826e2d2 --- /dev/null +++ b/tlsn/tlsn-verifier/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tlsn-verifier" +authors = ["TLSNotary Team"] +description = "Contains the verifier library" +keywords = ["tls", "mpc", "2pc", "verifier"] +categories = ["cryptography"] +license = "MIT OR Apache-2.0" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +tlsn-prover.workspace = true +tlsn-notary.workspace = true +tlsn-core.workspace = true +tlsn-tls-core.workspace = true +tlsn-tls-client.workspace = true + +mpz-core.workspace = true + +elliptic-curve.workspace = true +webpki-roots.workspace = true +p256.workspace = true +serde_json.workspace = true + +getrandom = { version = "0.2", features = ["js"] } diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs new file mode 100644 index 0000000000..e59ff8a0e0 --- /dev/null +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -0,0 +1,115 @@ +use elliptic_curve::pkcs8::DecodePublicKey; +use p256::ecdsa::{signature::Verifier, VerifyingKey}; +use std::time::{Duration, UNIX_EPOCH}; + +use mpz_core::serialize::CanonicalSerialize; +use tls_core::{ + anchors::{OwnedTrustAnchor, RootCertStore}, + dns::ServerName, + verify::ServerCertVerifier, +}; +use tlsn_core::{signature::Signature, substrings::proof::SubstringsProof, SessionProof}; + +/// 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. +pub fn verify() { + // Deserialize the proof + let proof = std::fs::read_to_string("proof.json").unwrap(); + let (session_proof, substrings_proof, domain): (SessionProof, SubstringsProof, String) = + serde_json::from_str(proof.as_str()).unwrap(); + + // Destructure + let SessionProof { + header, + signature, + handshake_data_decommitment, + } = session_proof; + + // Notary signature type must be correct + #[allow(irrefutable_let_patterns)] + let Signature::P256(signature) = signature.unwrap() else { + panic!("Notary signature is not P256"); + }; + + // Verify the signed header against a trusted Notary's public key + notary_pubkey() + .verify(&header.to_bytes(), &signature) + .unwrap(); + + // Verify the decommitment + handshake_data_decommitment + .verify(header.handshake_summary().handshake_commitment()) + .unwrap(); + + // Verify TLS handshake data. This verifies the server's certificate chain and the server's + // signature against the provided server name. + handshake_data_decommitment + .data() + .verify( + &cert_verifier(), + UNIX_EPOCH + Duration::from_secs(header.handshake_summary().time()), + &ServerName::try_from(domain.as_str()).unwrap(), + ) + .unwrap(); + + // Verify the proof + let (sent_slices, recv_slices) = substrings_proof.verify(&header).unwrap(); + + // Flatten transcript slices into a bytestring, filling the bytes which the Prover chose not + // to disclose with 'X' + let mut transcript_tx = vec![b'X'; header.sent_len() as usize]; + for slice in sent_slices { + transcript_tx[slice.range().start as usize..slice.range().end as usize] + .copy_from_slice(slice.data()) + } + + let mut transcript_rx = vec![b'X'; header.recv_len() as usize]; + for slice in recv_slices { + transcript_rx[slice.range().start as usize..slice.range().end as usize] + .copy_from_slice(slice.data()) + } + + println!("-------------------------------------------------------------------"); + println!( + "Successfully verified that the bytes below came from a session with {:?}.", + domain + ); + 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(transcript_tx).unwrap()); + println!(); + println!("Bytes received:"); + println!(); + println!("{}", String::from_utf8(transcript_rx).unwrap()); + println!("-------------------------------------------------------------------"); +} + +/// Returns a default certificate verifier. +fn cert_verifier() -> impl ServerCertVerifier { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); + + tls_core::verify::WebPkiVerifier::new(root_store, None) +} + +/// Returns a Notary pubkey trusted by this Verifier +fn notary_pubkey() -> VerifyingKey { + // from https://github.com/tlsnotary/notary-server/tree/main/src/fixture/notary/notary.key + // converted with `openssl ec -in notary.key -pubout -outform PEM` + + let pem = "-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr +cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg== +-----END PUBLIC KEY-----"; + + VerifyingKey::from_public_key_pem(pem).unwrap() +} + From 2c64473e51fb1a6024ba606adf29fc5064539112 Mon Sep 17 00:00:00 2001 From: th4s Date: Mon, 28 Aug 2023 18:25:32 +0200 Subject: [PATCH 02/11] Added verifier code and improved api --- tlsn/tlsn-verifier/Cargo.toml | 11 +- tlsn/tlsn-verifier/src/lib.rs | 217 ++++++++++++++++++++-------------- 2 files changed, 130 insertions(+), 98 deletions(-) diff --git a/tlsn/tlsn-verifier/Cargo.toml b/tlsn/tlsn-verifier/Cargo.toml index 7ce826e2d2..5e014269bf 100644 --- a/tlsn/tlsn-verifier/Cargo.toml +++ b/tlsn/tlsn-verifier/Cargo.toml @@ -10,21 +10,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["cdylib", "rlib"] - [dependencies] -tlsn-prover.workspace = true -tlsn-notary.workspace = true tlsn-core.workspace = true tlsn-tls-core.workspace = true -tlsn-tls-client.workspace = true mpz-core.workspace = true -elliptic-curve.workspace = true webpki-roots.workspace = true p256.workspace = true -serde_json.workspace = true - -getrandom = { version = "0.2", features = ["js"] } +thiserror.workspace = true diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index e59ff8a0e0..695896c246 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -1,93 +1,130 @@ -use elliptic_curve::pkcs8::DecodePublicKey; -use p256::ecdsa::{signature::Verifier, VerifyingKey}; +use mpz_core::{commit::CommitmentError, serialize::CanonicalSerialize}; +use p256::ecdsa::{signature::Verifier as SignatureVerifier, VerifyingKey}; use std::time::{Duration, UNIX_EPOCH}; - -use mpz_core::serialize::CanonicalSerialize; +use thiserror::Error; use tls_core::{ anchors::{OwnedTrustAnchor, RootCertStore}, dns::ServerName, - verify::ServerCertVerifier, + verify::{ServerCertVerifier, WebPkiVerifier}, }; use tlsn_core::{signature::Signature, substrings::proof::SubstringsProof, SessionProof}; -/// 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. -pub fn verify() { - // Deserialize the proof - let proof = std::fs::read_to_string("proof.json").unwrap(); - let (session_proof, substrings_proof, domain): (SessionProof, SubstringsProof, String) = - serde_json::from_str(proof.as_str()).unwrap(); - - // Destructure - let SessionProof { - header, - signature, - handshake_data_decommitment, - } = session_proof; - - // Notary signature type must be correct - #[allow(irrefutable_let_patterns)] - let Signature::P256(signature) = signature.unwrap() else { - panic!("Notary signature is not P256"); - }; - - // Verify the signed header against a trusted Notary's public key - notary_pubkey() - .verify(&header.to_bytes(), &signature) - .unwrap(); - - // Verify the decommitment - handshake_data_decommitment - .verify(header.handshake_summary().handshake_commitment()) - .unwrap(); - - // Verify TLS handshake data. This verifies the server's certificate chain and the server's - // signature against the provided server name. - handshake_data_decommitment - .data() - .verify( - &cert_verifier(), - UNIX_EPOCH + Duration::from_secs(header.handshake_summary().time()), - &ServerName::try_from(domain.as_str()).unwrap(), - ) - .unwrap(); +pub struct Verifier { + server_name: ServerName, + notary_pubkey: Option, + session_proof: SessionProof, +} + +impl Verifier { + pub fn new( + server_name: impl TryInto, + notary_pubkey: Option, + session_proof: SessionProof, + ) -> Result { + let server_name = server_name + .try_into() + .map_err(|_| VerifierError::Servername)?; + + let verifier = Verifier { + server_name, + notary_pubkey, + session_proof, + }; + + verifier.verify()?; + + Ok(verifier) + } + + pub fn set_new_session_proof( + &mut self, + session_proof: SessionProof, + ) -> Result<(), VerifierError> { + self.session_proof = session_proof; + self.verify() + } + + pub fn verify_substring_proof( + &mut self, + proof: SubstringsProof, + ) -> Result<(String, String), VerifierError> { + let header = self.session_proof.header(); + let (sent_slices, received_slices) = proof + .verify(header) + .map_err(|_| VerifierError::InvalidSubstringProof)?; + + let mut sent_transcript = vec![b'X'; header.sent_len() as usize]; + let mut received_transcript = vec![b'X'; header.recv_len() as usize]; + + for slice in sent_slices { + sent_transcript[slice.range().start as usize..slice.range().end as usize] + .copy_from_slice(slice.data()) + } + + for slice in received_slices { + received_transcript[slice.range().start as usize..slice.range().end as usize] + .copy_from_slice(slice.data()) + } + + Ok(( + String::from_utf8(sent_transcript).map_err(VerifierError::Utf8Error)?, + String::from_utf8(received_transcript).map_err(VerifierError::Utf8Error)?, + )) + } + + fn verify(&self) -> Result<(), VerifierError> { + if let Some(notary_pk) = self.notary_pubkey { + self.verify_notary_signature(notary_pk)?; + } - // Verify the proof - let (sent_slices, recv_slices) = substrings_proof.verify(&header).unwrap(); + self.verify_handshake_data_decommitment()?; + self.verify_cert_chain()?; + + Ok(()) + } - // Flatten transcript slices into a bytestring, filling the bytes which the Prover chose not - // to disclose with 'X' - let mut transcript_tx = vec![b'X'; header.sent_len() as usize]; - for slice in sent_slices { - transcript_tx[slice.range().start as usize..slice.range().end as usize] - .copy_from_slice(slice.data()) + fn verify_notary_signature(&self, notary_pubkey: VerifyingKey) -> Result<(), VerifierError> { + match self.session_proof.signature { + Some(Signature::P256(sig)) => notary_pubkey + .verify(&self.session_proof.header.to_bytes(), &sig) + .map_err(|_| VerifierError::InvalidNotarySignature), + None => Err(VerifierError::MissingNotarySignature), + Some(_) => unreachable!(), + } } - let mut transcript_rx = vec![b'X'; header.recv_len() as usize]; - for slice in recv_slices { - transcript_rx[slice.range().start as usize..slice.range().end as usize] - .copy_from_slice(slice.data()) + fn verify_handshake_data_decommitment(&self) -> Result<(), VerifierError> { + let hs_commitment = self + .session_proof + .header() + .handshake_summary() + .handshake_commitment(); + let hs_decommitment = self.session_proof.handshake_data_decommitment(); + + hs_decommitment + .verify(hs_commitment) + .map_err(|_| VerifierError::InvalidNotarySignature) } - println!("-------------------------------------------------------------------"); - println!( - "Successfully verified that the bytes below came from a session with {:?}.", - domain - ); - 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(transcript_tx).unwrap()); - println!(); - println!("Bytes received:"); - println!(); - println!("{}", String::from_utf8(transcript_rx).unwrap()); - println!("-------------------------------------------------------------------"); + fn verify_cert_chain(&self) -> Result<(), VerifierError> { + let cert_verifier = &default_cert_verifier(); + let header = self.session_proof.header(); + let server_name = &self.server_name; + + let hs_data = self.session_proof.handshake_data_decommitment().data(); + let hs_time = header.handshake_summary().time(); + + hs_data + .verify( + cert_verifier, + UNIX_EPOCH + Duration::from_secs(hs_time), + server_name, + ) + .map_err(|_| VerifierError::InvalidCertChain) + } } -/// Returns a default certificate verifier. -fn cert_verifier() -> impl ServerCertVerifier { +fn default_cert_verifier() -> impl ServerCertVerifier { let mut root_store = RootCertStore::empty(); root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { OwnedTrustAnchor::from_subject_spki_name_constraints( @@ -97,19 +134,23 @@ fn cert_verifier() -> impl ServerCertVerifier { ) })); - tls_core::verify::WebPkiVerifier::new(root_store, None) + WebPkiVerifier::new(root_store, None) } -/// Returns a Notary pubkey trusted by this Verifier -fn notary_pubkey() -> VerifyingKey { - // from https://github.com/tlsnotary/notary-server/tree/main/src/fixture/notary/notary.key - // converted with `openssl ec -in notary.key -pubout -outform PEM` - - let pem = "-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr -cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg== ------END PUBLIC KEY-----"; - - VerifyingKey::from_public_key_pem(pem).unwrap() +#[derive(Debug, Error)] +pub enum VerifierError { + #[error("Invalid server name")] + Servername, + #[error("Missing notary signature")] + MissingNotarySignature, + #[error("Invalid notary signature")] + InvalidNotarySignature, + #[error("Invalid certificate chain")] + InvalidCertChain, + #[error("Invalid substring proof")] + InvalidSubstringProof, + #[error(transparent)] + Utf8Error(#[from] std::string::FromUtf8Error), + #[error(transparent)] + CommitmentError(#[from] CommitmentError), } - From b6332e6a2ae63181b102034b996369e5f041da46 Mon Sep 17 00:00:00 2001 From: th4s Date: Tue, 29 Aug 2023 14:54:37 +0200 Subject: [PATCH 03/11] Improve error handling --- tlsn/tlsn-verifier/src/lib.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index 695896c246..d032a1e8a2 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -6,8 +6,11 @@ use tls_core::{ anchors::{OwnedTrustAnchor, RootCertStore}, dns::ServerName, verify::{ServerCertVerifier, WebPkiVerifier}, + Error as TlsCoreError, +}; +use tlsn_core::{ + signature::Signature, substrings::proof::SubstringsProof, Error as TlsnCoreError, SessionProof, }; -use tlsn_core::{signature::Signature, substrings::proof::SubstringsProof, SessionProof}; pub struct Verifier { server_name: ServerName, @@ -45,13 +48,13 @@ impl Verifier { } pub fn verify_substring_proof( - &mut self, + &self, proof: SubstringsProof, ) -> Result<(String, String), VerifierError> { let header = self.session_proof.header(); let (sent_slices, received_slices) = proof .verify(header) - .map_err(|_| VerifierError::InvalidSubstringProof)?; + .map_err(VerifierError::InvalidSubstringProof)?; let mut sent_transcript = vec![b'X'; header.sent_len() as usize]; let mut received_transcript = vec![b'X'; header.recv_len() as usize]; @@ -87,7 +90,7 @@ impl Verifier { match self.session_proof.signature { Some(Signature::P256(sig)) => notary_pubkey .verify(&self.session_proof.header.to_bytes(), &sig) - .map_err(|_| VerifierError::InvalidNotarySignature), + .map_err(VerifierError::InvalidNotarySignature), None => Err(VerifierError::MissingNotarySignature), Some(_) => unreachable!(), } @@ -103,7 +106,7 @@ impl Verifier { hs_decommitment .verify(hs_commitment) - .map_err(|_| VerifierError::InvalidNotarySignature) + .map_err(VerifierError::CommitmentError) } fn verify_cert_chain(&self) -> Result<(), VerifierError> { @@ -120,7 +123,7 @@ impl Verifier { UNIX_EPOCH + Duration::from_secs(hs_time), server_name, ) - .map_err(|_| VerifierError::InvalidCertChain) + .map_err(VerifierError::InvalidCertChain) } } @@ -143,12 +146,12 @@ pub enum VerifierError { Servername, #[error("Missing notary signature")] MissingNotarySignature, - #[error("Invalid notary signature")] - InvalidNotarySignature, - #[error("Invalid certificate chain")] - InvalidCertChain, - #[error("Invalid substring proof")] - InvalidSubstringProof, + #[error(transparent)] + InvalidNotarySignature(#[from] p256::ecdsa::Error), + #[error(transparent)] + InvalidCertChain(#[from] TlsCoreError), + #[error(transparent)] + InvalidSubstringProof(#[from] TlsnCoreError), #[error(transparent)] Utf8Error(#[from] std::string::FromUtf8Error), #[error(transparent)] From 6b3d7d4c8c08edb3cb650d1717d391b9302cc58f Mon Sep 17 00:00:00 2001 From: th4s Date: Tue, 29 Aug 2023 15:02:14 +0200 Subject: [PATCH 04/11] Improve api --- tlsn/tlsn-verifier/src/lib.rs | 49 +++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index d032a1e8a2..90e046a1d2 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -15,14 +15,13 @@ use tlsn_core::{ pub struct Verifier { server_name: ServerName, notary_pubkey: Option, - session_proof: SessionProof, + session_proof: Option, } impl Verifier { pub fn new( server_name: impl TryInto, notary_pubkey: Option, - session_proof: SessionProof, ) -> Result { let server_name = server_name .try_into() @@ -31,19 +30,14 @@ impl Verifier { let verifier = Verifier { server_name, notary_pubkey, - session_proof, + session_proof: None, }; - verifier.verify()?; - Ok(verifier) } - pub fn set_new_session_proof( - &mut self, - session_proof: SessionProof, - ) -> Result<(), VerifierError> { - self.session_proof = session_proof; + pub fn set_session_proof(&mut self, session_proof: SessionProof) -> Result<(), VerifierError> { + self.session_proof = Some(session_proof); self.verify() } @@ -51,7 +45,12 @@ impl Verifier { &self, proof: SubstringsProof, ) -> Result<(String, String), VerifierError> { - let header = self.session_proof.header(); + let header = self + .session_proof + .as_ref() + .ok_or(VerifierError::MissingSessionProof)? + .header(); + let (sent_slices, received_slices) = proof .verify(header) .map_err(VerifierError::InvalidSubstringProof)?; @@ -87,9 +86,14 @@ impl Verifier { } fn verify_notary_signature(&self, notary_pubkey: VerifyingKey) -> Result<(), VerifierError> { - match self.session_proof.signature { + let session_proof = self + .session_proof + .as_ref() + .ok_or(VerifierError::MissingSessionProof)?; + + match session_proof.signature { Some(Signature::P256(sig)) => notary_pubkey - .verify(&self.session_proof.header.to_bytes(), &sig) + .verify(&session_proof.header.to_bytes(), &sig) .map_err(VerifierError::InvalidNotarySignature), None => Err(VerifierError::MissingNotarySignature), Some(_) => unreachable!(), @@ -97,12 +101,16 @@ impl Verifier { } fn verify_handshake_data_decommitment(&self) -> Result<(), VerifierError> { - let hs_commitment = self + let session_proof = self .session_proof + .as_ref() + .ok_or(VerifierError::MissingSessionProof)?; + + let hs_commitment = session_proof .header() .handshake_summary() .handshake_commitment(); - let hs_decommitment = self.session_proof.handshake_data_decommitment(); + let hs_decommitment = session_proof.handshake_data_decommitment(); hs_decommitment .verify(hs_commitment) @@ -110,11 +118,16 @@ impl Verifier { } fn verify_cert_chain(&self) -> Result<(), VerifierError> { + let session_proof = self + .session_proof + .as_ref() + .ok_or(VerifierError::MissingSessionProof)?; + let cert_verifier = &default_cert_verifier(); - let header = self.session_proof.header(); + let header = session_proof.header(); let server_name = &self.server_name; - let hs_data = self.session_proof.handshake_data_decommitment().data(); + let hs_data = session_proof.handshake_data_decommitment().data(); let hs_time = header.handshake_summary().time(); hs_data @@ -146,6 +159,8 @@ pub enum VerifierError { Servername, #[error("Missing notary signature")] MissingNotarySignature, + #[error("Missing session proof")] + MissingSessionProof, #[error(transparent)] InvalidNotarySignature(#[from] p256::ecdsa::Error), #[error(transparent)] From e9c572d5e86206a4cd5a88ff23b97ec17fd89baf Mon Sep 17 00:00:00 2001 From: th4s Date: Tue, 29 Aug 2023 16:00:30 +0200 Subject: [PATCH 05/11] Add doc --- tlsn/tlsn-verifier/src/lib.rs | 75 ++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index 90e046a1d2..beca0010ba 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -1,3 +1,20 @@ +//! The verifier library +//! +//! The [Verifier] is used to verify [session proofs](SessionProof) and [substrings proofs](SubstringsProof) for a given domain. +//! When doing a notarization with the TLSNotary protocol, the output will be a [notarized session](tlsn_core::NotarizedSession), +//! which contains a session proof. This session proof can be used by the verifier to verify parts +//! of a notarized session's traffic data which he accepts int the form of substring proofs. +//! +//! So the usual workflow for a verifier is as follows: +//! 1. Create a [new verifier](Verifier::new). +//! 2. [Set the session proof](Verifier::set_session_proof) which should be used for substring +//! verification. This will also verify the session proof itself. +//! 3. [Verify substring proofs](Verifier::verify_substring_proof). + +#![deny(missing_docs, unreachable_pub, unused_must_use)] +#![deny(clippy::all)] +#![forbid(unsafe_code)] + use mpz_core::{commit::CommitmentError, serialize::CanonicalSerialize}; use p256::ecdsa::{signature::Verifier as SignatureVerifier, VerifyingKey}; use std::time::{Duration, UNIX_EPOCH}; @@ -12,6 +29,9 @@ use tlsn_core::{ signature::Signature, substrings::proof::SubstringsProof, Error as TlsnCoreError, SessionProof, }; +/// The Verifier +/// +/// The Verifier is used to verify session proofs and substrings proofs for some domain. pub struct Verifier { server_name: ServerName, notary_pubkey: Option, @@ -19,28 +39,41 @@ pub struct Verifier { } impl Verifier { + /// Create a new verifier + /// + /// Creates a new verifier for the given server name and notary public key used for + /// verification. pub fn new( server_name: impl TryInto, - notary_pubkey: Option, + notary_pubkey: VerifyingKey, ) -> Result { - let server_name = server_name - .try_into() - .map_err(|_| VerifierError::Servername)?; - - let verifier = Verifier { - server_name, - notary_pubkey, - session_proof: None, - }; + Self::new_internal(server_name, Some(notary_pubkey), None) + } - Ok(verifier) + /// Create a new verifier without providing a notary public key for verification + /// + /// # Attention + /// This means that the verifier will **NOT CHECK** the notary signature for session proofs so + /// that they can easily be forged. This mode is useful if you also run a notary server + /// yourself and **ONLY** pass session proofs created by this notary server to the verifier. + pub fn new_without_pubkey( + server_name: impl TryInto, + ) -> Result { + Self::new_internal(server_name, None, None) } + /// Set the session proof + /// + /// Sets a new session proof and verifies it. pub fn set_session_proof(&mut self, session_proof: SessionProof) -> Result<(), VerifierError> { self.session_proof = Some(session_proof); self.verify() } + /// Verify a substring proof against the current session proof + /// + /// Checks that the given substring proof is valid and returns the sent and received + /// transcripts of the traffic with redaction applied. pub fn verify_substring_proof( &self, proof: SubstringsProof, @@ -138,6 +171,24 @@ impl Verifier { ) .map_err(VerifierError::InvalidCertChain) } + + fn new_internal( + server_name: impl TryInto, + notary_pubkey: Option, + session_proof: Option, + ) -> Result { + let server_name = server_name + .try_into() + .map_err(|_| VerifierError::Servername)?; + + let verifier = Verifier { + server_name, + notary_pubkey, + session_proof, + }; + + Ok(verifier) + } } fn default_cert_verifier() -> impl ServerCertVerifier { @@ -153,6 +204,8 @@ fn default_cert_verifier() -> impl ServerCertVerifier { WebPkiVerifier::new(root_store, None) } +/// Errors that can occur during verification +#[allow(missing_docs)] #[derive(Debug, Error)] pub enum VerifierError { #[error("Invalid server name")] From 8fbe0cace85da1c16945d21842d7bd1e87c25e8c Mon Sep 17 00:00:00 2001 From: th4s Date: Tue, 29 Aug 2023 16:06:06 +0200 Subject: [PATCH 06/11] Remove empty line --- tlsn/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/tlsn/Cargo.toml b/tlsn/Cargo.toml index 9a793e14d4..2b14dc13c6 100644 --- a/tlsn/Cargo.toml +++ b/tlsn/Cargo.toml @@ -51,7 +51,6 @@ thiserror = "1" serde = "1" serde_json = "1" bincode = "1" - hex = "0.4" bytes = "1.4" opaque-debug = "0.3" From 956e79b1806952d186cca3753c8cb11a1277fdb1 Mon Sep 17 00:00:00 2001 From: th4s Date: Tue, 29 Aug 2023 19:48:11 +0200 Subject: [PATCH 07/11] Bugfix for verify --- tlsn/tlsn-verifier/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index beca0010ba..ba5a3db64d 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -67,7 +67,12 @@ impl Verifier { /// Sets a new session proof and verifies it. pub fn set_session_proof(&mut self, session_proof: SessionProof) -> Result<(), VerifierError> { self.session_proof = Some(session_proof); - self.verify() + let verify_result = self.verify(); + + if verify_result.is_err() { + self.session_proof = None; + } + verify_result } /// Verify a substring proof against the current session proof From 142a8ba053c74b4f9b14e616a9e2eddec7b786eb Mon Sep 17 00:00:00 2001 From: th4s Date: Mon, 4 Sep 2023 12:29:38 +0200 Subject: [PATCH 08/11] WIP: Started adapting `verify_transcript` function... --- tlsn/tlsn-verifier/src/lib.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index ba5a3db64d..6fd1bc8e49 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -3,7 +3,7 @@ //! The [Verifier] is used to verify [session proofs](SessionProof) and [substrings proofs](SubstringsProof) for a given domain. //! When doing a notarization with the TLSNotary protocol, the output will be a [notarized session](tlsn_core::NotarizedSession), //! which contains a session proof. This session proof can be used by the verifier to verify parts -//! of a notarized session's traffic data which he accepts int the form of substring proofs. +//! of a notarized session's traffic data which he accepts in the form of substring proofs. //! //! So the usual workflow for a verifier is as follows: //! 1. Create a [new verifier](Verifier::new). @@ -27,6 +27,7 @@ use tls_core::{ }; use tlsn_core::{ signature::Signature, substrings::proof::SubstringsProof, Error as TlsnCoreError, SessionProof, + Transcript, }; /// The Verifier @@ -79,10 +80,12 @@ impl Verifier { /// /// Checks that the given substring proof is valid and returns the sent and received /// transcripts of the traffic with redaction applied. - pub fn verify_substring_proof( + pub fn verify_transcript( &self, proof: SubstringsProof, - ) -> Result<(String, String), VerifierError> { + (sent, received): (Transcript, Transcript), + ) -> Result<(), VerifierError> { + // Verify session proof against session header let header = self .session_proof .as_ref() @@ -93,23 +96,13 @@ impl Verifier { .verify(header) .map_err(VerifierError::InvalidSubstringProof)?; - let mut sent_transcript = vec![b'X'; header.sent_len() as usize]; - let mut received_transcript = vec![b'X'; header.recv_len() as usize]; + // Check redacted transcript + // - check length + // - check that decommitments match transcript parts + // - check that redacted parts only contain valid characters - for slice in sent_slices { - sent_transcript[slice.range().start as usize..slice.range().end as usize] - .copy_from_slice(slice.data()) - } - - for slice in received_slices { - received_transcript[slice.range().start as usize..slice.range().end as usize] - .copy_from_slice(slice.data()) - } - - Ok(( - String::from_utf8(sent_transcript).map_err(VerifierError::Utf8Error)?, - String::from_utf8(received_transcript).map_err(VerifierError::Utf8Error)?, - )) + // Return a parsed/deserialized variant somehow + Ok(()) } fn verify(&self) -> Result<(), VerifierError> { From 91f2d01a8288b5d01a60ecedc647fdd76617c947 Mon Sep 17 00:00:00 2001 From: th4s Date: Mon, 4 Sep 2023 16:33:37 +0200 Subject: [PATCH 09/11] WIP: Adding checks for transcript... --- tlsn/tlsn-verifier/src/lib.rs | 61 +++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index 6fd1bc8e49..e2d7d071af 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -26,10 +26,12 @@ use tls_core::{ Error as TlsCoreError, }; use tlsn_core::{ - signature::Signature, substrings::proof::SubstringsProof, Error as TlsnCoreError, SessionProof, - Transcript, + signature::Signature, substrings::proof::SubstringsProof, Direction, Error as TlsnCoreError, + SessionProof, Transcript, }; +const VALID_REDACTMENT_CHARS: &[u8] = b"x"; + /// The Verifier /// /// The Verifier is used to verify session proofs and substrings proofs for some domain. @@ -76,32 +78,63 @@ impl Verifier { verify_result } - /// Verify a substring proof against the current session proof + /// Checks that the given `transcript` and substring `proof` are valid. /// - /// Checks that the given substring proof is valid and returns the sent and received - /// transcripts of the traffic with redaction applied. + /// This function checks that + /// * the substring proof is valid against the session proof + /// * the length of the redacted transcript is correct + /// * the redacted transcript matches the decommitments + /// * the redacted transcript only contains valid characters pub fn verify_transcript( &self, proof: SubstringsProof, - (sent, received): (Transcript, Transcript), + transcript: Transcript, + direction: Direction, ) -> Result<(), VerifierError> { - // Verify session proof against session header let header = self .session_proof .as_ref() .ok_or(VerifierError::MissingSessionProof)? .header(); - let (sent_slices, received_slices) = proof + // Verify the session proof against the session header + let (verified_sent, verified_received) = proof .verify(header) .map_err(VerifierError::InvalidSubstringProof)?; - // Check redacted transcript - // - check length - // - check that decommitments match transcript parts - // - check that redacted parts only contain valid characters + let (expected_len, decommitment_slices) = match direction { + Direction::Sent => (header.sent_len() as usize, verified_sent), + Direction::Received => (header.recv_len() as usize, verified_received), + }; + + // Check the redacted transcript lengths + if expected_len != transcript.data().len() { + return Err(VerifierError::InvalidRedactedTranscript); + } + + // Check that the transcript matches the decommitments + if decommitment_slices.iter().any(|el| { + *el.data() != transcript.data()[el.range().start as usize..el.range().end as usize] + }) { + return Err(VerifierError::InvalidRedactedTranscript); + } + + // Check that redacted transcript only uses valid characters + let redacted_ranges = invert_ranges( + decommitment_slices + .iter() + .map(|el| el.range().start as usize..el.range().end as usize), + transcript.data().len(), + ); + + if redacted_ranges.iter().any(|el| { + transcript.data()[el.range().start as usize..el.range().end as usize] + .iter() + .any(|el| !VALID_REDACTMENT_CHARS.contains(el)) + }) { + return Err(VerifierError::InvalidRedactedTranscript); + } - // Return a parsed/deserialized variant somehow Ok(()) } @@ -212,6 +245,8 @@ pub enum VerifierError { MissingNotarySignature, #[error("Missing session proof")] MissingSessionProof, + #[error("The redacted transcript does not match the decommitments")] + InvalidRedactedTranscript, #[error(transparent)] InvalidNotarySignature(#[from] p256::ecdsa::Error), #[error(transparent)] From 8759c2d312997d22e72982c47b8a64e08d8e9a34 Mon Sep 17 00:00:00 2001 From: th4s Date: Mon, 4 Sep 2023 18:17:37 +0200 Subject: [PATCH 10/11] Added check for valid redacting characters --- tlsn/Cargo.toml | 4 ++++ tlsn/tlsn-verifier/Cargo.toml | 1 + tlsn/tlsn-verifier/src/lib.rs | 27 ++++++++++++++++++--------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tlsn/Cargo.toml b/tlsn/Cargo.toml index 2b14dc13c6..7fbcc2f537 100644 --- a/tlsn/Cargo.toml +++ b/tlsn/Cargo.toml @@ -58,3 +58,7 @@ opaque-debug = "0.3" tracing = "0.1" tracing-subscriber = "0.3" rstest = "0.17" + +[patch.'https://github.com/tlsnotary/tlsn-utils'] +tlsn-utils = { git = "https://github.com/tlsnotary//tlsn-utils", branch = "invert_ranges" } + diff --git a/tlsn/tlsn-verifier/Cargo.toml b/tlsn/tlsn-verifier/Cargo.toml index 5e014269bf..d7e05333a8 100644 --- a/tlsn/tlsn-verifier/Cargo.toml +++ b/tlsn/tlsn-verifier/Cargo.toml @@ -13,6 +13,7 @@ edition = "2021" [dependencies] tlsn-core.workspace = true tlsn-tls-core.workspace = true +tlsn-utils.workspace = true mpz-core.workspace = true diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index e2d7d071af..c4b7c36936 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -17,7 +17,10 @@ use mpz_core::{commit::CommitmentError, serialize::CanonicalSerialize}; use p256::ecdsa::{signature::Verifier as SignatureVerifier, VerifyingKey}; -use std::time::{Duration, UNIX_EPOCH}; +use std::{ + ops::Range, + time::{Duration, UNIX_EPOCH}, +}; use thiserror::Error; use tls_core::{ anchors::{OwnedTrustAnchor, RootCertStore}, @@ -29,8 +32,10 @@ use tlsn_core::{ signature::Signature, substrings::proof::SubstringsProof, Direction, Error as TlsnCoreError, SessionProof, Transcript, }; +use utils::invert_range::invert_range; -const VALID_REDACTMENT_CHARS: &[u8] = b"x"; +/// Valid characters for redacted parts in transcripts +pub const VALID_REDACTMENT_CHARS: &[u8] = b"x"; /// The Verifier /// @@ -78,7 +83,7 @@ impl Verifier { verify_result } - /// Checks that the given `transcript` and substring `proof` are valid. + /// Checks that the given `transcript` and substring `proof` are valid /// /// This function checks that /// * the substring proof is valid against the session proof @@ -120,15 +125,17 @@ impl Verifier { } // Check that redacted transcript only uses valid characters - let redacted_ranges = invert_ranges( + let redacted_ranges = invert_range( + &(0..transcript.data().len()), decommitment_slices .iter() - .map(|el| el.range().start as usize..el.range().end as usize), - transcript.data().len(), - ); + .map(|el| el.range().start as usize..el.range().end as usize) + .collect::>>() + .as_slice(), + )?; - if redacted_ranges.iter().any(|el| { - transcript.data()[el.range().start as usize..el.range().end as usize] + if redacted_ranges.into_iter().any(|range| { + transcript.data()[range] .iter() .any(|el| !VALID_REDACTMENT_CHARS.contains(el)) }) { @@ -257,4 +264,6 @@ pub enum VerifierError { Utf8Error(#[from] std::string::FromUtf8Error), #[error(transparent)] CommitmentError(#[from] CommitmentError), + #[error(transparent)] + Range(#[from] utils::invert_range::RangeError), } From facfa81eb94ca64826050181497bea3dfb2ef50b Mon Sep 17 00:00:00 2001 From: th4s Date: Tue, 5 Sep 2023 16:20:50 +0200 Subject: [PATCH 11/11] Add spansy and start with assertion on `VerifiedTranscript` --- tlsn/Cargo.toml | 1 + tlsn/tlsn-verifier/Cargo.toml | 2 ++ tlsn/tlsn-verifier/src/assert.rs | 3 +++ tlsn/tlsn-verifier/src/lib.rs | 15 +++++++++++---- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 tlsn/tlsn-verifier/src/assert.rs diff --git a/tlsn/Cargo.toml b/tlsn/Cargo.toml index 7fbcc2f537..43639469f8 100644 --- a/tlsn/Cargo.toml +++ b/tlsn/Cargo.toml @@ -31,6 +31,7 @@ mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "a98fd8a" } mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "a98fd8a" } +spansy = {git = "https://github.com/sinui0/spansy", rev = "23a6b0a"} futures = "0.3" tokio-util = "0.7" diff --git a/tlsn/tlsn-verifier/Cargo.toml b/tlsn/tlsn-verifier/Cargo.toml index d7e05333a8..2728e2be7a 100644 --- a/tlsn/tlsn-verifier/Cargo.toml +++ b/tlsn/tlsn-verifier/Cargo.toml @@ -17,6 +17,8 @@ tlsn-utils.workspace = true mpz-core.workspace = true +spansy.workspace = true + webpki-roots.workspace = true p256.workspace = true thiserror.workspace = true diff --git a/tlsn/tlsn-verifier/src/assert.rs b/tlsn/tlsn-verifier/src/assert.rs new file mode 100644 index 0000000000..f3ec2c7caa --- /dev/null +++ b/tlsn/tlsn-verifier/src/assert.rs @@ -0,0 +1,3 @@ +pub struct VerifiedTranscript { + pub(crate) data: Vec, +} diff --git a/tlsn/tlsn-verifier/src/lib.rs b/tlsn/tlsn-verifier/src/lib.rs index c4b7c36936..26071f371b 100644 --- a/tlsn/tlsn-verifier/src/lib.rs +++ b/tlsn/tlsn-verifier/src/lib.rs @@ -34,6 +34,9 @@ use tlsn_core::{ }; use utils::invert_range::invert_range; +mod assert; +use assert::VerifiedTranscript; + /// Valid characters for redacted parts in transcripts pub const VALID_REDACTMENT_CHARS: &[u8] = b"x"; @@ -75,7 +78,7 @@ impl Verifier { /// Sets a new session proof and verifies it. pub fn set_session_proof(&mut self, session_proof: SessionProof) -> Result<(), VerifierError> { self.session_proof = Some(session_proof); - let verify_result = self.verify(); + let verify_result = self.verify_session_proof(); if verify_result.is_err() { self.session_proof = None; @@ -95,7 +98,7 @@ impl Verifier { proof: SubstringsProof, transcript: Transcript, direction: Direction, - ) -> Result<(), VerifierError> { + ) -> Result { let header = self .session_proof .as_ref() @@ -142,10 +145,14 @@ impl Verifier { return Err(VerifierError::InvalidRedactedTranscript); } - Ok(()) + let verified_transcript = VerifiedTranscript { + data: transcript.data().to_vec(), + }; + + Ok(verified_transcript) } - fn verify(&self) -> Result<(), VerifierError> { + fn verify_session_proof(&self) -> Result<(), VerifierError> { if let Some(notary_pk) = self.notary_pubkey { self.verify_notary_signature(notary_pk)?; }