From eef813712da71e57a33c60ceb211981ab711061b Mon Sep 17 00:00:00 2001 From: "sinu.eth" <65924192+sinui0@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:54:11 -0700 Subject: [PATCH] refactor: extract attestation functionality into dedicated crate (#936) * refactor: extract attestation functionality into dedicated crate * commit lock * fix integration test * clippy * fix docs * fix import * fix wasm types * fix doctest * verifier config default rootstore * fix integration test * fix notary integration tests --- Cargo.lock | 33 ++- Cargo.toml | 3 + crates/attestation/Cargo.toml | 40 ++++ .../src}/builder.rs | 37 ++-- .../attestation => attestation/src}/config.rs | 8 +- .../src/connection.rs} | 76 +++++-- .../src}/extension.rs | 2 +- crates/attestation/src/fixtures.rs | 129 +++++++++++ crates/attestation/src/hash.rs | 19 ++ .../attestation.rs => attestation/src/lib.rs} | 207 ++++++++++++++++-- .../{core => attestation}/src/presentation.rs | 15 +- .../attestation => attestation/src}/proof.rs | 8 +- crates/{core => attestation}/src/provider.rs | 8 +- crates/{core => attestation}/src/request.rs | 26 +-- .../src/request/builder.rs | 13 +- .../src/request/config.rs | 7 +- crates/{core => attestation}/src/secrets.rs | 8 +- crates/attestation/src/serialize.rs | 53 +++++ crates/{core => attestation}/src/signing.rs | 8 +- crates/{core => attestation}/tests/api.rs | 15 +- crates/core/Cargo.toml | 13 +- crates/core/src/connection.rs | 145 +++++------- crates/core/src/connection/commit.rs | 40 ---- crates/core/src/fixtures.rs | 119 +--------- crates/core/src/hash.rs | 52 +---- crates/core/src/lib.rs | 184 +--------------- crates/core/src/merkle.rs | 31 +-- crates/core/src/serialize.rs | 18 -- crates/core/src/transcript.rs | 14 -- crates/core/src/transcript/commit.rs | 6 +- crates/core/src/transcript/encoding.rs | 7 +- crates/core/src/transcript/encoding/proof.rs | 17 +- .../core/src/transcript/encoding/provider.rs | 1 + crates/core/src/transcript/encoding/tree.rs | 7 +- crates/core/src/transcript/hash.rs | 4 +- crates/core/src/transcript/proof.rs | 39 ++-- crates/examples/attestation/present.rs | 2 +- crates/examples/attestation/prove.rs | 11 +- crates/examples/attestation/verify.rs | 2 +- crates/examples/interactive/interactive.rs | 53 ++--- crates/harness/executor/src/bench/prover.rs | 13 +- crates/harness/executor/src/bench/verifier.rs | 11 +- crates/harness/executor/test_plugins/basic.rs | 26 +-- crates/notary/server/src/server.rs | 2 +- crates/notary/server/src/service.rs | 9 +- crates/notary/server/src/signing.rs | 2 +- crates/notary/server/src/types.rs | 2 +- .../notary/tests-integration/tests/notary.rs | 22 +- crates/tls/core/src/rand.rs | 2 +- crates/tls/core/src/verify.rs | 60 ++--- crates/tlsn/Cargo.toml | 2 + crates/tlsn/src/lib.rs | 3 +- crates/tlsn/src/prover.rs | 54 +++-- crates/tlsn/src/prover/config.rs | 92 +++++--- crates/tlsn/src/verifier.rs | 59 ++++- crates/tlsn/src/verifier/config.rs | 31 ++- crates/tlsn/tests/test.rs | 67 +++--- crates/wasm/src/lib.rs | 2 +- crates/wasm/src/prover/mod.rs | 7 +- crates/wasm/src/types.rs | 52 ++--- 60 files changed, 1059 insertions(+), 939 deletions(-) create mode 100644 crates/attestation/Cargo.toml rename crates/{core/src/attestation => attestation/src}/builder.rs (96%) rename crates/{core/src/attestation => attestation/src}/config.rs (95%) rename crates/{core/src/connection/proof.rs => attestation/src/connection.rs} (51%) rename crates/{core/src/attestation => attestation/src}/extension.rs (94%) create mode 100644 crates/attestation/src/fixtures.rs create mode 100644 crates/attestation/src/hash.rs rename crates/{core/src/attestation.rs => attestation/src/lib.rs} (52%) rename crates/{core => attestation}/src/presentation.rs (96%) rename crates/{core/src/attestation => attestation/src}/proof.rs (98%) rename crates/{core => attestation}/src/provider.rs (91%) rename crates/{core => attestation}/src/request.rs (94%) rename crates/{core => attestation}/src/request/builder.rs (96%) rename crates/{core => attestation}/src/request/config.rs (95%) rename crates/{core => attestation}/src/secrets.rs (87%) create mode 100644 crates/attestation/src/serialize.rs rename crates/{core => attestation}/src/signing.rs (99%) rename crates/{core => attestation}/tests/api.rs (97%) delete mode 100644 crates/core/src/connection/commit.rs delete mode 100644 crates/core/src/serialize.rs diff --git a/Cargo.lock b/Cargo.lock index e024d595f..b99ed5085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6470,6 +6470,7 @@ dependencies = [ "serde", "serio", "thiserror 1.0.69", + "tlsn-attestation", "tlsn-cipher", "tlsn-core", "tlsn-deap", @@ -6485,6 +6486,31 @@ dependencies = [ "tracing-subscriber", "uid-mux", "web-spawn", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tlsn-attestation" +version = "0.1.0-alpha.13-pre" +dependencies = [ + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "bcs", + "blake3", + "k256", + "opaque-debug", + "p256", + "rand 0.9.1", + "rand06-compat", + "rstest", + "serde", + "thiserror 1.0.69", + "tiny-keccak", + "tlsn-core", + "tlsn-data-fixtures", + "tlsn-tls-core", + "webpki-roots 0.26.11", ] [[package]] @@ -6510,20 +6536,13 @@ dependencies = [ name = "tlsn-core" version = "0.1.0-alpha.13-pre" dependencies = [ - "alloy-primitives", - "alloy-signer", - "alloy-signer-local", - "bcs", "bimap", "bincode", "blake3", "hex", "itybity 0.2.1", - "k256", "opaque-debug", - "p256", "rand 0.9.1", - "rand06-compat", "rand_chacha 0.9.0", "rand_core 0.9.3", "rangeset", diff --git a/Cargo.toml b/Cargo.toml index b0e1964fb..221befbe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/attestation", "crates/components/deap", "crates/components/cipher", "crates/components/hmac-sha256", @@ -47,6 +48,7 @@ notary-client = { path = "crates/notary/client" } notary-common = { path = "crates/notary/common" } notary-server = { path = "crates/notary/server" } tls-server-fixture = { path = "crates/tls/server-fixture" } +tlsn-attestation = { path = "crates/attestation" } tlsn-cipher = { path = "crates/components/cipher" } tlsn-core = { path = "crates/core" } tlsn-data-fixtures = { path = "crates/data-fixtures" } @@ -152,6 +154,7 @@ serde_json = { version = "1.0" } sha2 = { version = "0.10" } signature = { version = "2.2" } thiserror = { version = "1.0" } +tiny-keccak = { version = "2.0" } tokio = { version = "1.38" } tokio-rustls = { version = "0.24" } tokio-util = { version = "0.7" } diff --git a/crates/attestation/Cargo.toml b/crates/attestation/Cargo.toml new file mode 100644 index 000000000..5aa936afa --- /dev/null +++ b/crates/attestation/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "tlsn-attestation" +version = "0.1.0-alpha.13-pre" +edition = "2024" + +[features] +default = [] +fixtures = ["tlsn-core/fixtures", "dep:tlsn-data-fixtures"] + +[dependencies] +tlsn-tls-core = { workspace = true } +tlsn-core = { workspace = true } +tlsn-data-fixtures = { workspace = true, optional = true } + +bcs = { workspace = true } +blake3 = { workspace = true } +p256 = { workspace = true, features = ["serde"] } +k256 = { workspace = true } +opaque-debug = { workspace = true } +rand = { workspace = true } +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } +tiny-keccak = { workspace = true, features = ["keccak"] } +webpki-roots = { workspace = true } + +[dev-dependencies] +alloy-primitives = { version = "0.8.22", default-features = false } +alloy-signer = { version = "0.12", default-features = false } +alloy-signer-local = { version = "0.12", default-features = false } +rand06-compat = { workspace = true } +rstest = { workspace = true } +tlsn-core = { workspace = true, features = ["fixtures"] } +tlsn-data-fixtures = { workspace = true } + +[lints] +workspace = true + +[[test]] +name = "api" +required-features = ["fixtures"] diff --git a/crates/core/src/attestation/builder.rs b/crates/attestation/src/builder.rs similarity index 96% rename from crates/core/src/attestation/builder.rs rename to crates/attestation/src/builder.rs index 8ccb42060..1f827c701 100644 --- a/crates/core/src/attestation/builder.rs +++ b/crates/attestation/src/builder.rs @@ -1,19 +1,17 @@ use std::error::Error; -use rand::{rng, Rng}; +use rand::{Rng, rng}; -use crate::{ - attestation::{ - Attestation, AttestationConfig, Body, Extension, FieldId, Header, ServerCertCommitment, - VERSION, - }, +use tlsn_core::{ connection::{ConnectionInfo, ServerEphemKey}, hash::HashAlgId, - request::Request, - serialize::CanonicalSerialize, - signing::SignatureAlgId, transcript::TranscriptCommitment, - CryptoProvider, +}; + +use crate::{ + Attestation, AttestationConfig, Body, CryptoProvider, Extension, FieldId, Header, + ServerCertCommitment, VERSION, request::Request, serialize::CanonicalSerialize, + signing::SignatureAlgId, }; /// Attestation builder state for accepting a request. @@ -243,14 +241,15 @@ impl std::fmt::Display for AttestationBuilderError { #[cfg(test)] mod test { use rstest::{fixture, rstest}; - use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; - - use crate::{ + use tlsn_core::{ connection::{HandshakeData, HandshakeDataV1_2}, - fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture}, + fixtures::{ConnectionFixture, encoding_provider}, hash::Blake3, transcript::Transcript, }; + use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; + + use crate::fixtures::{RequestFixture, request_fixture}; use super::*; @@ -403,7 +402,10 @@ mod test { let HandshakeData::V1_2(HandshakeDataV1_2 { server_ephemeral_key, .. - }) = server_cert_data.handshake; + }) = server_cert_data.handshake + else { + panic!("expected v1.2 handshake data"); + }; attestation_builder.server_ephemeral_key(server_ephemeral_key); @@ -471,7 +473,10 @@ mod test { let HandshakeData::V1_2(HandshakeDataV1_2 { server_ephemeral_key, .. - }) = server_cert_data.handshake; + }) = server_cert_data.handshake + else { + panic!("expected v1.2 handshake data"); + }; attestation_builder .connection_info(connection_info) diff --git a/crates/core/src/attestation/config.rs b/crates/attestation/src/config.rs similarity index 95% rename from crates/core/src/attestation/config.rs rename to crates/attestation/src/config.rs index 8813e7947..29e766cfb 100644 --- a/crates/core/src/attestation/config.rs +++ b/crates/attestation/src/config.rs @@ -1,9 +1,9 @@ use std::{fmt::Debug, sync::Arc}; +use tlsn_core::hash::HashAlgId; + use crate::{ - attestation::{Extension, InvalidExtension}, - hash::{HashAlgId, DEFAULT_SUPPORTED_HASH_ALGS}, - signing::SignatureAlgId, + Extension, InvalidExtension, hash::DEFAULT_SUPPORTED_HASH_ALGS, signing::SignatureAlgId, }; type ExtensionValidator = Arc Result<(), InvalidExtension> + Send + Sync>; @@ -124,7 +124,7 @@ impl AttestationConfigBuilder { /// /// # Example /// ``` - /// # use tlsn_core::attestation::{AttestationConfig, InvalidExtension}; + /// # use tlsn_attestation::{AttestationConfig, InvalidExtension}; /// # let mut builder = AttestationConfig::builder(); /// builder.extension_validator(|extensions| { /// for extension in extensions { diff --git a/crates/core/src/connection/proof.rs b/crates/attestation/src/connection.rs similarity index 51% rename from crates/core/src/connection/proof.rs rename to crates/attestation/src/connection.rs index 9de13ffcd..9327acec7 100644 --- a/crates/core/src/connection/proof.rs +++ b/crates/attestation/src/connection.rs @@ -1,16 +1,65 @@ -//! Types for proving details of a connection. +//! Types for committing details of a connection. +//! +//! ## Commitment +//! +//! During the TLS handshake the Notary receives the Server's ephemeral public +//! key, and this key serves as a binding commitment to the identity of the +//! Server. The ephemeral key itself does not reveal the Server's identity, but +//! it is bound to it via a signature created using the Server's +//! X.509 certificate. +//! +//! A Prover can withhold the Server's signature and certificate chain from the +//! Notary to improve privacy and censorship resistance. +//! +//! ## Proving the Server's identity +//! +//! A Prover can prove the Server's identity to a Verifier by sending a +//! [`ServerIdentityProof`]. This proof contains all the information required to +//! establish the link between the TLS connection and the Server's X.509 +//! certificate. A Verifier checks the Server's certificate against their own +//! trust anchors, the same way a typical TLS client would. use serde::{Deserialize, Serialize}; -use crate::{ - connection::{ - commit::{ServerCertCommitment, ServerCertOpening}, - CertificateVerificationError, ServerEphemKey, ServerName, - }, - hash::{HashAlgorithmExt, HashProviderError}, - CryptoProvider, +use tlsn_core::{ + connection::{CertificateVerificationError, ServerCertData, ServerEphemKey, ServerName}, + hash::{Blinded, HashAlgorithm, HashProviderError, TypedHash}, }; +use crate::{CryptoProvider, hash::HashAlgorithmExt, serialize::impl_domain_separator}; + +/// Opens a [`ServerCertCommitment`]. +#[derive(Clone, Serialize, Deserialize)] +pub struct ServerCertOpening(Blinded); + +impl_domain_separator!(ServerCertOpening); + +opaque_debug::implement!(ServerCertOpening); + +impl ServerCertOpening { + pub(crate) fn new(data: ServerCertData) -> Self { + Self(Blinded::new(data)) + } + + pub(crate) fn commit(&self, hasher: &dyn HashAlgorithm) -> ServerCertCommitment { + ServerCertCommitment(TypedHash { + alg: hasher.id(), + value: hasher.hash_separated(self), + }) + } + + /// Returns the server identity data. + pub fn data(&self) -> &ServerCertData { + self.0.data() + } +} + +/// Commitment to a server certificate. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ServerCertCommitment(pub(crate) TypedHash); + +impl_domain_separator!(ServerCertCommitment); + /// TLS server identity proof. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerIdentityProof { @@ -27,7 +76,7 @@ impl ServerIdentityProof { /// /// # Arguments /// - /// * `provider` - The crypto provider to use for verification. + /// * `provider` - Crypto provider. /// * `time` - The time of the connection. /// * `server_ephemeral_key` - The server's ephemeral key. /// * `commitment` - Commitment to the server certificate. @@ -48,12 +97,9 @@ impl ServerIdentityProof { } // Verify certificate and identity. - self.opening.data().verify_with_provider( - provider, - time, - server_ephemeral_key, - &self.name, - )?; + self.opening + .data() + .verify(&provider.cert, time, server_ephemeral_key, &self.name)?; Ok(self.name) } diff --git a/crates/core/src/attestation/extension.rs b/crates/attestation/src/extension.rs similarity index 94% rename from crates/core/src/attestation/extension.rs rename to crates/attestation/src/extension.rs index e6ee6dc45..976cf28b7 100644 --- a/crates/core/src/attestation/extension.rs +++ b/crates/attestation/src/extension.rs @@ -2,7 +2,7 @@ use std::error::Error; use serde::{Deserialize, Serialize}; -use crate::hash::impl_domain_separator; +use crate::serialize::impl_domain_separator; /// An attestation extension. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/crates/attestation/src/fixtures.rs b/crates/attestation/src/fixtures.rs new file mode 100644 index 000000000..28dd516ae --- /dev/null +++ b/crates/attestation/src/fixtures.rs @@ -0,0 +1,129 @@ +//! Attestation fixtures. + +use tlsn_core::{ + connection::{HandshakeData, HandshakeDataV1_2}, + fixtures::ConnectionFixture, + hash::HashAlgorithm, + transcript::{ + Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment, + encoding::{EncodingProvider, EncodingTree}, + }, +}; + +use crate::{ + Attestation, AttestationConfig, CryptoProvider, Extension, + request::{Request, RequestConfig}, + signing::SignatureAlgId, +}; + +/// Returns a notary signing key fixture. +pub fn notary_signing_key() -> p256::ecdsa::SigningKey { + p256::ecdsa::SigningKey::from_slice(&[1; 32]).unwrap() +} + +/// A Request fixture used for testing. +#[allow(missing_docs)] +pub struct RequestFixture { + pub encoding_tree: EncodingTree, + pub request: Request, +} + +/// Returns a request fixture for testing. +pub fn request_fixture( + transcript: Transcript, + encodings_provider: impl EncodingProvider, + connection: ConnectionFixture, + encoding_hasher: impl HashAlgorithm, + extensions: Vec, +) -> RequestFixture { + let provider = CryptoProvider::default(); + let (sent_len, recv_len) = transcript.len(); + + let ConnectionFixture { + server_name, + server_cert_data, + .. + } = connection; + + let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); + transcript_commitment_builder + .commit_sent(&(0..sent_len)) + .unwrap() + .commit_recv(&(0..recv_len)) + .unwrap(); + let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); + + // Prover constructs encoding tree. + let encoding_tree = EncodingTree::new( + &encoding_hasher, + transcripts_commitment_config.iter_encoding(), + &encodings_provider, + ) + .unwrap(); + + let mut builder = RequestConfig::builder(); + + for extension in extensions { + builder.extension(extension); + } + + let request_config = builder.build().unwrap(); + + let mut request_builder = Request::builder(&request_config); + request_builder + .server_name(server_name) + .server_cert_data(server_cert_data) + .transcript(transcript); + + let (request, _) = request_builder.build(&provider).unwrap(); + + RequestFixture { + encoding_tree, + request, + } +} + +/// Returns an attestation fixture for testing. +pub fn attestation_fixture( + request: Request, + connection: ConnectionFixture, + signature_alg: SignatureAlgId, + transcript_commitments: &[TranscriptCommitment], +) -> Attestation { + let ConnectionFixture { + connection_info, + server_cert_data, + .. + } = connection; + + let HandshakeData::V1_2(HandshakeDataV1_2 { + server_ephemeral_key, + .. + }) = server_cert_data.handshake + else { + panic!("expected v1.2 handshake data"); + }; + + let mut provider = CryptoProvider::default(); + match signature_alg { + SignatureAlgId::SECP256K1 => provider.signer.set_secp256k1(&[42u8; 32]).unwrap(), + SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[42u8; 32]).unwrap(), + _ => unimplemented!(), + }; + + let attestation_config = AttestationConfig::builder() + .supported_signature_algs([signature_alg]) + .build() + .unwrap(); + + let mut attestation_builder = Attestation::builder(&attestation_config) + .accept_request(request) + .unwrap(); + + attestation_builder + .connection_info(connection_info) + .server_ephemeral_key(server_ephemeral_key) + .transcript_commitments(transcript_commitments.to_vec()); + + attestation_builder.build(&provider).unwrap() +} diff --git a/crates/attestation/src/hash.rs b/crates/attestation/src/hash.rs new file mode 100644 index 000000000..aabda90c4 --- /dev/null +++ b/crates/attestation/src/hash.rs @@ -0,0 +1,19 @@ +use tlsn_core::hash::{Hash, HashAlgId, HashAlgorithm}; + +use crate::serialize::{CanonicalSerialize, DomainSeparator}; + +pub(crate) const DEFAULT_SUPPORTED_HASH_ALGS: &[HashAlgId] = + &[HashAlgId::SHA256, HashAlgId::BLAKE3, HashAlgId::KECCAK256]; + +pub(crate) trait HashAlgorithmExt: HashAlgorithm { + #[allow(dead_code)] + fn hash_canonical(&self, data: &T) -> Hash { + self.hash(&data.serialize()) + } + + fn hash_separated(&self, data: &T) -> Hash { + self.hash_prefixed(data.domain(), &data.serialize()) + } +} + +impl HashAlgorithmExt for T {} diff --git a/crates/core/src/attestation.rs b/crates/attestation/src/lib.rs similarity index 52% rename from crates/core/src/attestation.rs rename to crates/attestation/src/lib.rs index bc85b1ff5..01f3dccbc 100644 --- a/crates/core/src/attestation.rs +++ b/crates/attestation/src/lib.rs @@ -1,4 +1,28 @@ -//! Attestation types. +//! TLSNotary attestation types. +//! +//! # Introduction +//! +//! This library provides core functionality for TLSNotary **attestations**. +//! +//! Once the TLS commitment protocol has been completed the Prover holds a +//! collection of commitments pertaining to the TLS connection. Most +//! importantly, the Prover is committed to the +//! [`ServerName`](tlsn_core::connection::ServerName), +//! and the [`Transcript`](tlsn_core::transcript::Transcript) of application +//! data. Subsequently, the Prover can request an [`Attestation`] from the +//! Notary who will include the commitments as well as any additional +//! information which may be useful to an attestation Verifier. +//! +//! Holding an attestation, the Prover can construct a +//! [`Presentation`](crate::presentation::Presentation) which facilitates +//! selectively disclosing various aspects of the TLS connection to a Verifier. +//! If the Verifier trusts the Notary, or more specifically the verifying key of +//! the attestation, then the Verifier can trust the authenticity of the +//! information disclosed in the presentation. +//! +//! **Be sure to check out the various submodules for more information.** +//! +//! # Structure //! //! An attestation is a cryptographically signed document issued by a Notary who //! witnessed a TLS connection. It contains various fields which can be used to @@ -24,37 +48,194 @@ //! extensions](crate::request::RequestConfigBuilder::extension) //! to their attestation request, provided that the Notary supports them //! (disallowed by default). A Notary may also be configured to -//! [validate](crate::attestation::AttestationConfigBuilder::extension_validator) +//! [validate](crate::AttestationConfigBuilder::extension_validator) //! any extensions requested by a Prover using custom application logic. //! Additionally, a Notary may -//! [include](crate::attestation::AttestationBuilder::extension) +//! [include](crate::AttestationBuilder::extension) //! their own extensions. +//! +//! # Committing to the transcript +//! +//! The TLS commitment protocol produces commitments to the entire transcript of +//! application data. However, we may want to disclose only a subset of the data +//! in a presentation. Prior to attestation, the Prover has the opportunity to +//! slice and dice the commitments into smaller sections which can be +//! selectively disclosed. Additionally, the Prover may want to use different +//! commitment schemes depending on the context they expect to disclose. +//! +//! The primary API for this process is the +//! [`TranscriptCommitConfigBuilder`](tlsn_core::transcript::TranscriptCommitConfigBuilder) +//! which is used to build up a configuration. +//! +//! ```no_run +//! # use tlsn_core::transcript::{TranscriptCommitConfigBuilder, Transcript, Direction}; +//! # use tlsn_core::hash::HashAlgId; +//! # fn main() -> Result<(), Box> { +//! # let transcript: Transcript = unimplemented!(); +//! let (sent_len, recv_len) = transcript.len(); +//! +//! // Create a new configuration builder. +//! let mut builder = TranscriptCommitConfigBuilder::new(&transcript); +//! +//! // Specify all the transcript commitments we want to make. +//! builder +//! // Use BLAKE3 for encoding commitments. +//! .encoding_hash_alg(HashAlgId::BLAKE3) +//! // Commit to all sent data. +//! .commit_sent(&(0..sent_len))? +//! // Commit to the first 10 bytes of sent data. +//! .commit_sent(&(0..10))? +//! // Skip some bytes so it can be omitted in the presentation. +//! .commit_sent(&(20..sent_len))? +//! // Commit to all received data. +//! .commit_recv(&(0..recv_len))?; +//! +//! let config = builder.build()?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Requesting an attestation +//! +//! The first step in the attestation protocol is for the Prover to make a +//! [`Request`](crate::request::Request), which can be configured using the +//! associated [builder](crate::request::RequestConfigBuilder). With it the +//! Prover can configure some of the details of the attestation, such as which +//! cryptographic algorithms are used (if the Notary supports them). +//! +//! The Prover may also request for extensions to be added to the attestation, +//! see [here](#extensions) for more information. +//! +//! Upon being issued an attestation, the Prover will also hold a corresponding +//! [`Secrets`] which contains all private information. This pair can be stored +//! and used later to construct a +//! [`Presentation`](crate::presentation::Presentation), [see +//! below](#constructing-a-presentation). +//! +//! # Issuing an attestation +//! +//! Upon receiving a request, the Notary can issue an [`Attestation`] which can +//! be configured using the associated +//! [builder](crate::AttestationConfigBuilder). +//! +//! The Notary's [`CryptoProvider`] must be configured with an appropriate +//! signing key for attestations. See +//! [`SignerProvider`](crate::signing::SignerProvider) for more information. +//! +//! # Constructing a presentation +//! +//! A Prover can use an [`Attestation`] and the corresponding [`Secrets`] to +//! construct a verifiable [`Presentation`](crate::presentation::Presentation). +//! +//! ```no_run +//! # use tlsn_attestation::{Attestation, CryptoProvider, Secrets, presentation::Presentation}; +//! # use tlsn_core::transcript::{TranscriptCommitmentKind, Direction}; +//! # fn main() -> Result<(), Box> { +//! # let attestation: Attestation = unimplemented!(); +//! # let secrets: Secrets = unimplemented!(); +//! # let crypto_provider: CryptoProvider = unimplemented!(); +//! let (_sent_len, recv_len) = secrets.transcript().len(); +//! +//! // First, we decide which application data we would like to disclose. +//! let mut builder = secrets.transcript_proof_builder(); +//! +//! builder +//! // Use transcript encoding commitments. +//! .commitment_kinds(&[TranscriptCommitmentKind::Encoding]) +//! // Disclose the first 10 bytes of the sent data. +//! .reveal(&(0..10), Direction::Sent)? +//! // Disclose all of the received data. +//! .reveal(&(0..recv_len), Direction::Received)?; +//! +//! let transcript_proof = builder.build()?; +//! +//! // Most cases we will also disclose the server identity. +//! let identity_proof = secrets.identity_proof(); +//! +//! // Now we can construct the presentation. +//! let mut builder = attestation.presentation_builder(&crypto_provider); +//! +//! builder +//! .identity_proof(identity_proof) +//! .transcript_proof(transcript_proof); +//! +//! // Finally, we build the presentation. Send it to a verifier! +//! let presentation: Presentation = builder.build()?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Verifying a presentation +//! +//! Verifying a presentation is as simple as checking the verifier trusts the +//! verifying key then calling +//! [`Presentation::verify`](crate::presentation::Presentation::verify). +//! +//! ```no_run +//! # use tlsn_attestation::{CryptoProvider, presentation::{Presentation, PresentationOutput}, signing::VerifyingKey}; +//! # fn main() -> Result<(), Box> { +//! # let presentation: Presentation = unimplemented!(); +//! # let trusted_key: VerifyingKey = unimplemented!(); +//! # let crypto_provider: CryptoProvider = unimplemented!(); +//! // Assert that we trust the verifying key. +//! assert_eq!(presentation.verifying_key(), &trusted_key); +//! +//! let PresentationOutput { +//! attestation, +//! server_name, +//! connection_info, +//! transcript, +//! .. +//! } = presentation.verify(&crypto_provider)?; +//! # Ok(()) +//! # } +//! ``` + +#![deny(missing_docs, unreachable_pub, unused_must_use)] +#![deny(clippy::all)] +#![forbid(unsafe_code)] mod builder; mod config; +pub mod connection; mod extension; +#[cfg(any(test, feature = "fixtures"))] +pub mod fixtures; +pub(crate) mod hash; +pub mod presentation; mod proof; +mod provider; +pub mod request; +mod secrets; +pub(crate) mod serialize; +pub mod signing; use std::fmt; use rand::distr::{Distribution, StandardUniform}; use serde::{Deserialize, Serialize}; -use crate::{ - connection::{ConnectionInfo, ServerCertCommitment, ServerEphemKey}, - hash::{impl_domain_separator, Hash, HashAlgorithm, HashAlgorithmExt, TypedHash}, +use tlsn_core::{ + connection::{ConnectionInfo, ServerEphemKey}, + hash::{Hash, HashAlgorithm, TypedHash}, merkle::MerkleTree, - presentation::PresentationBuilder, - signing::{Signature, VerifyingKey}, transcript::TranscriptCommitment, - CryptoProvider, +}; + +use crate::{ + connection::ServerCertCommitment, + hash::HashAlgorithmExt, + presentation::PresentationBuilder, + serialize::impl_domain_separator, + signing::{Signature, VerifyingKey}, }; pub use builder::{AttestationBuilder, AttestationBuilderError}; pub use config::{AttestationConfig, AttestationConfigBuilder, AttestationConfigError}; pub use extension::{Extension, InvalidExtension}; pub use proof::{AttestationError, AttestationProof}; - +pub use provider::CryptoProvider; +pub use secrets::Secrets; /// Current version of attestations. pub const VERSION: Version = Version(0); @@ -127,8 +308,6 @@ pub enum FieldKind { } /// Attestation header. -/// -/// See [module level documentation](crate::attestation) for more information. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Header { /// An identifier for the attestation. @@ -142,8 +321,6 @@ pub struct Header { impl_domain_separator!(Header); /// Attestation body. -/// -/// See [module level documentation](crate::attestation) for more information. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Body { verifying_key: Field, @@ -246,8 +423,6 @@ impl Body { } /// An attestation document. -/// -/// See [module level documentation](crate::attestation) for more information. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Attestation { /// The signature of the attestation. diff --git a/crates/core/src/presentation.rs b/crates/attestation/src/presentation.rs similarity index 96% rename from crates/core/src/presentation.rs rename to crates/attestation/src/presentation.rs index b0c02917c..754b043fd 100644 --- a/crates/core/src/presentation.rs +++ b/crates/attestation/src/presentation.rs @@ -26,12 +26,15 @@ use std::fmt; use serde::{Deserialize, Serialize}; -use crate::{ - attestation::{Attestation, AttestationError, AttestationProof, Extension}, - connection::{ConnectionInfo, ServerIdentityProof, ServerIdentityProofError, ServerName}, - signing::VerifyingKey, +use tlsn_core::{ + connection::{ConnectionInfo, ServerName}, transcript::{PartialTranscript, TranscriptProof, TranscriptProofError}, - CryptoProvider, +}; + +use crate::{ + Attestation, AttestationError, AttestationProof, CryptoProvider, Extension, + connection::{ServerIdentityProof, ServerIdentityProofError}, + signing::VerifyingKey, }; /// A verifiable presentation. @@ -86,7 +89,7 @@ impl Presentation { let transcript = transcript .map(|transcript| { transcript.verify_with_provider( - provider, + &provider.hash, &attestation.body.connection_info().transcript_length, attestation.body.transcript_commitments(), ) diff --git a/crates/core/src/attestation/proof.rs b/crates/attestation/src/proof.rs similarity index 98% rename from crates/core/src/attestation/proof.rs rename to crates/attestation/src/proof.rs index 51e6e80bd..21b4fc32f 100644 --- a/crates/core/src/attestation/proof.rs +++ b/crates/attestation/src/proof.rs @@ -2,13 +2,15 @@ use std::fmt; use serde::{Deserialize, Serialize}; -use crate::{ - attestation::{Attestation, Body, Header}, +use tlsn_core::{ hash::HashAlgorithm, merkle::{MerkleProof, MerkleTree}, +}; + +use crate::{ + Attestation, Body, CryptoProvider, Header, serialize::CanonicalSerialize, signing::{Signature, VerifyingKey}, - CryptoProvider, }; /// Proof of an attestation. diff --git a/crates/core/src/provider.rs b/crates/attestation/src/provider.rs similarity index 91% rename from crates/core/src/provider.rs rename to crates/attestation/src/provider.rs index 3dabf4a03..c02e6263a 100644 --- a/crates/core/src/provider.rs +++ b/crates/attestation/src/provider.rs @@ -2,11 +2,9 @@ use tls_core::{ anchors::{OwnedTrustAnchor, RootCertStore}, verify::WebPkiVerifier, }; +use tlsn_core::hash::HashProvider; -use crate::{ - hash::HashProvider, - signing::{SignatureVerifierProvider, SignerProvider}, -}; +use crate::signing::{SignatureVerifierProvider, SignerProvider}; /// Cryptography provider. /// @@ -17,7 +15,7 @@ use crate::{ /// implementations. /// /// Algorithms are uniquely identified using an 8-bit ID, eg. -/// [`HashAlgId`](crate::hash::HashAlgId), half of which is reserved for the +/// [`HashAlgId`](tlsn_core::hash::HashAlgId), half of which is reserved for the /// officially supported algorithms. If you think that a new algorithm should be /// added to the official set, please open an issue. Beware that other parties /// may assign different algorithms to the same ID as you, and we make no effort diff --git a/crates/core/src/request.rs b/crates/attestation/src/request.rs similarity index 94% rename from crates/core/src/request.rs rename to crates/attestation/src/request.rs index 8d6669af5..53aac8f02 100644 --- a/crates/core/src/request.rs +++ b/crates/attestation/src/request.rs @@ -18,12 +18,9 @@ mod config; use serde::{Deserialize, Serialize}; -use crate::{ - attestation::{Attestation, Extension}, - connection::ServerCertCommitment, - hash::HashAlgId, - signing::SignatureAlgId, -}; +use tlsn_core::hash::HashAlgId; + +use crate::{Attestation, Extension, connection::ServerCertCommitment, signing::SignatureAlgId}; pub use builder::{RequestBuilder, RequestBuilderError}; pub use config::{RequestConfig, RequestConfigBuilder, RequestConfigBuilderError}; @@ -85,18 +82,19 @@ pub struct InconsistentAttestation(String); #[cfg(test)] mod test { + use tlsn_core::{ + connection::TranscriptLength, + fixtures::{ConnectionFixture, encoding_provider}, + hash::{Blake3, HashAlgId}, + transcript::Transcript, + }; use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; use crate::{ - connection::{ServerCertOpening, TranscriptLength}, - fixtures::{ - attestation_fixture, encoding_provider, request_fixture, ConnectionFixture, - RequestFixture, - }, - hash::{Blake3, HashAlgId}, - signing::SignatureAlgId, - transcript::Transcript, CryptoProvider, + connection::ServerCertOpening, + fixtures::{RequestFixture, attestation_fixture, request_fixture}, + signing::SignatureAlgId, }; #[test] diff --git a/crates/core/src/request/builder.rs b/crates/attestation/src/request/builder.rs similarity index 96% rename from crates/core/src/request/builder.rs rename to crates/attestation/src/request/builder.rs index fca681a24..ac6e191a0 100644 --- a/crates/core/src/request/builder.rs +++ b/crates/attestation/src/request/builder.rs @@ -1,9 +1,12 @@ -use crate::{ - connection::{ServerCertData, ServerCertOpening, ServerName}, - request::{Request, RequestConfig}, - secrets::Secrets, +use tlsn_core::{ + connection::{ServerCertData, ServerName}, transcript::{Transcript, TranscriptCommitment, TranscriptSecret}, - CryptoProvider, +}; + +use crate::{ + CryptoProvider, Secrets, + connection::ServerCertOpening, + request::{Request, RequestConfig}, }; /// Builder for [`Request`]. diff --git a/crates/core/src/request/config.rs b/crates/attestation/src/request/config.rs similarity index 95% rename from crates/core/src/request/config.rs rename to crates/attestation/src/request/config.rs index 2859e940f..56f3b55c1 100644 --- a/crates/core/src/request/config.rs +++ b/crates/attestation/src/request/config.rs @@ -1,7 +1,6 @@ -use crate::{ - attestation::Extension, hash::HashAlgId, signing::SignatureAlgId, - transcript::TranscriptCommitConfig, -}; +use tlsn_core::{hash::HashAlgId, transcript::TranscriptCommitConfig}; + +use crate::{Extension, signing::SignatureAlgId}; /// Request configuration. #[derive(Debug, Clone)] diff --git a/crates/core/src/secrets.rs b/crates/attestation/src/secrets.rs similarity index 87% rename from crates/core/src/secrets.rs rename to crates/attestation/src/secrets.rs index 7e49ffcbc..0a93c0600 100644 --- a/crates/core/src/secrets.rs +++ b/crates/attestation/src/secrets.rs @@ -1,11 +1,13 @@ use serde::{Deserialize, Serialize}; -use crate::{ - connection::{ServerCertOpening, ServerIdentityProof, ServerName}, +use tlsn_core::{ + connection::ServerName, transcript::{Transcript, TranscriptCommitment, TranscriptProofBuilder, TranscriptSecret}, }; -/// Secret data of an [`Attestation`](crate::attestation::Attestation). +use crate::connection::{ServerCertOpening, ServerIdentityProof}; + +/// Secret data of an [`Attestation`](crate::Attestation). #[derive(Clone, Serialize, Deserialize)] pub struct Secrets { pub(crate) server_name: ServerName, diff --git a/crates/attestation/src/serialize.rs b/crates/attestation/src/serialize.rs new file mode 100644 index 000000000..828e3d7a1 --- /dev/null +++ b/crates/attestation/src/serialize.rs @@ -0,0 +1,53 @@ +/// Canonical serialization of TLSNotary types. +/// +/// This trait is used to serialize types into a canonical byte representation. +pub(crate) trait CanonicalSerialize { + /// Serializes the type. + fn serialize(&self) -> Vec; +} + +impl CanonicalSerialize for T +where + T: serde::Serialize, +{ + fn serialize(&self) -> Vec { + // For now we use BCS for serialization. In future releases we will want to + // consider this further, particularly with respect to EVM compatibility. + bcs::to_bytes(self).unwrap() + } +} + +/// A type with a domain separator which is used during hashing to mitigate type +/// confusion attacks. +pub(crate) trait DomainSeparator { + /// Returns the domain separator for the type. + fn domain(&self) -> &[u8]; +} + +macro_rules! impl_domain_separator { + ($type:ty) => { + impl $crate::serialize::DomainSeparator for $type { + fn domain(&self) -> &[u8] { + use std::sync::LazyLock; + + // Computes a 16 byte hash of the type's name to use as a domain separator. + static DOMAIN: LazyLock<[u8; 16]> = LazyLock::new(|| { + let domain: [u8; 32] = blake3::hash(stringify!($type).as_bytes()).into(); + domain[..16].try_into().unwrap() + }); + + &*DOMAIN + } + } + }; +} + +pub(crate) use impl_domain_separator; + +impl_domain_separator!(tlsn_core::connection::ServerEphemKey); +impl_domain_separator!(tlsn_core::connection::ConnectionInfo); +impl_domain_separator!(tlsn_core::connection::HandshakeData); +impl_domain_separator!(tlsn_core::transcript::TranscriptCommitment); +impl_domain_separator!(tlsn_core::transcript::TranscriptSecret); +impl_domain_separator!(tlsn_core::transcript::encoding::EncodingCommitment); +impl_domain_separator!(tlsn_core::transcript::hash::PlaintextHash); diff --git a/crates/core/src/signing.rs b/crates/attestation/src/signing.rs similarity index 99% rename from crates/core/src/signing.rs rename to crates/attestation/src/signing.rs index 60a18a543..eae69e504 100644 --- a/crates/core/src/signing.rs +++ b/crates/attestation/src/signing.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use crate::hash::impl_domain_separator; +use crate::serialize::impl_domain_separator; /// Key algorithm identifier. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -242,8 +242,8 @@ mod secp256k1 { use std::sync::{Arc, Mutex}; use k256::ecdsa::{ - signature::{SignerMut, Verifier}, Signature as Secp256K1Signature, SigningKey, + signature::{SignerMut, Verifier}, }; use super::*; @@ -318,8 +318,8 @@ mod secp256r1 { use std::sync::{Arc, Mutex}; use p256::ecdsa::{ - signature::{SignerMut, Verifier}, Signature as Secp256R1Signature, SigningKey, + signature::{SignerMut, Verifier}, }; use super::*; @@ -394,7 +394,7 @@ mod secp256k1eth { use std::sync::{Arc, Mutex}; use k256::ecdsa::{ - signature::hazmat::PrehashVerifier, Signature as Secp256K1Signature, SigningKey, + Signature as Secp256K1Signature, SigningKey, signature::hazmat::PrehashVerifier, }; use tiny_keccak::{Hasher, Keccak}; diff --git a/crates/core/tests/api.rs b/crates/attestation/tests/api.rs similarity index 97% rename from crates/core/tests/api.rs rename to crates/attestation/tests/api.rs index f414bd8ca..167a9b101 100644 --- a/crates/core/tests/api.rs +++ b/crates/attestation/tests/api.rs @@ -1,17 +1,18 @@ -use tlsn_core::{ - attestation::{Attestation, AttestationConfig}, - connection::{HandshakeData, HandshakeDataV1_2}, - fixtures::{self, encoder_secret, ConnectionFixture}, - hash::Blake3, +use tlsn_attestation::{ + Attestation, AttestationConfig, CryptoProvider, presentation::PresentationOutput, request::{Request, RequestConfig}, signing::SignatureAlgId, +}; +use tlsn_core::{ + connection::{HandshakeData, HandshakeDataV1_2}, + fixtures::{self, ConnectionFixture, encoder_secret}, + hash::Blake3, transcript::{ - encoding::{EncodingCommitment, EncodingTree}, Direction, Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment, TranscriptSecret, + encoding::{EncodingCommitment, EncodingTree}, }, - CryptoProvider, }; use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 54305ff4e..36436825e 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -21,13 +21,10 @@ tlsn-tls-core = { workspace = true, features = ["serde"] } tlsn-utils = { workspace = true } rangeset = { workspace = true, features = ["serde"] } -bcs = { workspace = true } bimap = { version = "0.6", features = ["serde"] } blake3 = { workspace = true } hex = { workspace = true, optional = true } -k256 = { workspace = true } opaque-debug = { workspace = true } -p256 = { workspace = true, features = ["serde"] } rand = { workspace = true } rand_core = { workspace = true } rand_chacha = { workspace = true } @@ -36,21 +33,13 @@ rstest = { workspace = true, optional = true } serde = { workspace = true } sha2 = { workspace = true } thiserror = { workspace = true } -tiny-keccak = { version = "2.0", features = ["keccak"] } +tiny-keccak = { workspace = true, features = ["keccak"] } web-time = { workspace = true } webpki-roots = { workspace = true } itybity = { workspace = true } [dev-dependencies] -alloy-primitives = { version = "0.8.22", default-features = false } -alloy-signer = { version = "0.12", default-features = false } -alloy-signer-local = { version = "0.12", default-features = false } bincode = { workspace = true } hex = { workspace = true } rstest = { workspace = true } tlsn-data-fixtures = { workspace = true } -rand06-compat = { workspace = true } - -[[test]] -name = "api" -required-features = ["fixtures"] diff --git a/crates/core/src/connection.rs b/crates/core/src/connection.rs index 11ee9d778..85f6a1ef0 100644 --- a/crates/core/src/connection.rs +++ b/crates/core/src/connection.rs @@ -1,26 +1,4 @@ //! TLS connection types. -//! -//! ## Commitment -//! -//! During the TLS handshake the Notary receives the Server's ephemeral public -//! key, and this key serves as a binding commitment to the identity of the -//! Server. The ephemeral key itself does not reveal the Server's identity, but -//! it is bound to it via a signature created using the Server's -//! X.509 certificate. -//! -//! A Prover can withhold the Server's signature and certificate chain from the -//! Notary to improve privacy and censorship resistance. -//! -//! ## Proving the Server's identity -//! -//! A Prover can prove the Server's identity to a Verifier by sending a -//! [`ServerIdentityProof`]. This proof contains all the information required to -//! establish the link between the TLS connection and the Server's X.509 -//! certificate. A Verifier checks the Server's certificate against their own -//! trust anchors, the same way a typical TLS client would. - -mod commit; -mod proof; use std::fmt; @@ -31,15 +9,10 @@ use tls_core::{ enums::NamedGroup, handshake::{DigitallySignedStruct, ServerECDHParams}, }, - verify::ServerCertVerifier as _, + verify::{ServerCertVerifier as _, WebPkiVerifier}, }; use web_time::{Duration, UNIX_EPOCH}; -use crate::{hash::impl_domain_separator, CryptoProvider}; - -pub use commit::{ServerCertCommitment, ServerCertOpening}; -pub use proof::{ServerIdentityProof, ServerIdentityProofError}; - /// TLS version. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -198,8 +171,6 @@ pub struct ServerEphemKey { pub key: Vec, } -impl_domain_separator!(ServerEphemKey); - impl ServerEphemKey { /// Encodes the key exchange parameters as in TLS. pub(crate) fn kx_params(&self) -> Vec { @@ -240,8 +211,6 @@ pub struct ConnectionInfo { pub transcript_length: TranscriptLength, } -impl_domain_separator!(ConnectionInfo); - /// Transcript length information. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TranscriptLength { @@ -271,8 +240,6 @@ pub enum HandshakeData { V1_2(HandshakeDataV1_2), } -impl_domain_separator!(HandshakeData); - /// Verify data from the TLS handshake finished messages. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VerifyData { @@ -298,13 +265,13 @@ impl ServerCertData { /// /// # Arguments /// - /// * `provider` - The crypto provider to use for verification. + /// * `verifier` - Cerificate verifier. /// * `time` - The time of the connection. /// * `server_ephemeral_key` - The server's ephemeral key. /// * `server_name` - The server name. - pub fn verify_with_provider( + pub fn verify( &self, - provider: &CryptoProvider, + verifier: &WebPkiVerifier, time: u64, server_ephemeral_key: &ServerEphemKey, server_name: &ServerName, @@ -341,8 +308,7 @@ impl ServerCertData { // Verify the end entity cert is valid for the provided server name // and that it chains to at least one of the roots we trust. - provider - .cert + verifier .verify_server_cert( end_entity, intermediates, @@ -361,8 +327,7 @@ impl ServerCertData { let dss = DigitallySignedStruct::new(self.sig.scheme.into(), self.sig.sig.clone()); - provider - .cert + verifier .verify_tls12_signature(&message, end_entity, &dss) .map_err(|_| CertificateVerificationError::InvalidServerSignature)?; @@ -389,19 +354,27 @@ pub enum CertificateVerificationError { #[cfg(test)] mod tests { use super::*; - use crate::{ - fixtures::ConnectionFixture, provider::default_cert_verifier, transcript::Transcript, - }; + use crate::{fixtures::ConnectionFixture, transcript::Transcript}; use hex::FromHex; use rstest::*; - use tls_core::verify::WebPkiVerifier; + use tls_core::{ + anchors::{OwnedTrustAnchor, RootCertStore}, + verify::WebPkiVerifier, + }; use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; #[fixture] #[once] - fn crypto_provider() -> CryptoProvider { - let mut store = default_cert_verifier().root_store().clone(); + fn verifier() -> WebPkiVerifier { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject.as_ref(), + ta.subject_public_key_info.as_ref(), + ta.name_constraints.as_ref().map(|nc| nc.as_ref()), + ) + })); // Add a cert which is no longer included in the Mozilla root store. let cert = tls_core::key::Certificate( @@ -414,14 +387,9 @@ mod tests { .clone(), ); - store.add(&cert).unwrap(); + root_store.add(&cert).unwrap(); - CryptoProvider { - hash: Default::default(), - cert: WebPkiVerifier::new(store, None), - signer: Default::default(), - signature: Default::default(), - } + WebPkiVerifier::new(root_store, None) } fn tlsnotary() -> ConnectionFixture { @@ -437,7 +405,7 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_sucess_ca_implicit( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { // Remove the CA cert @@ -445,8 +413,8 @@ mod tests { assert!(data .server_cert_data - .verify_with_provider( - crypto_provider, + .verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -460,13 +428,13 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_success_ca_explicit( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] data: ConnectionFixture, ) { assert!(data .server_cert_data - .verify_with_provider( - crypto_provider, + .verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -479,14 +447,14 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_fail_bad_time( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] data: ConnectionFixture, ) { // unix time when the cert chain was NOT valid let bad_time: u64 = 1571465711; - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, bad_time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -503,7 +471,7 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_fail_no_interm_cert( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { // Remove the CA cert @@ -511,8 +479,8 @@ mod tests { // Remove the intermediate cert data.server_cert_data.certs.pop(); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -530,14 +498,14 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_fail_no_interm_cert_with_ca_cert( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { // Remove the intermediate cert data.server_cert_data.certs.remove(1); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -554,7 +522,7 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_fail_bad_ee_cert( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { let ee: &[u8] = include_bytes!("./fixtures/data/unknown/ee.der"); @@ -562,8 +530,8 @@ mod tests { // Change the end entity cert data.server_cert_data.certs[0] = Certificate(ee.to_vec()); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -580,15 +548,15 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_sig_ke_params_fail_bad_client_random( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { let HandshakeData::V1_2(HandshakeDataV1_2 { client_random, .. }) = &mut data.server_cert_data.handshake; client_random[31] = client_random[31].wrapping_add(1); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -605,13 +573,13 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_sig_ke_params_fail_bad_sig( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { data.server_cert_data.sig.sig[31] = data.server_cert_data.sig.sig[31].wrapping_add(1); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), @@ -628,13 +596,13 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_check_dns_name_present_in_cert_fail_bad_host( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] data: ConnectionFixture, ) { let bad_name = ServerName::from("badhost.com"); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &bad_name, @@ -650,17 +618,14 @@ mod tests { #[rstest] #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] - fn test_invalid_ephemeral_key( - crypto_provider: &CryptoProvider, - #[case] data: ConnectionFixture, - ) { + fn test_invalid_ephemeral_key(verifier: &WebPkiVerifier, #[case] data: ConnectionFixture) { let wrong_ephemeral_key = ServerEphemKey { typ: KeyType::SECP256R1, key: Vec::::from_hex(include_bytes!("./fixtures/data/unknown/pubkey")).unwrap(), }; - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, &wrong_ephemeral_key, &ServerName::from(data.server_name.as_ref()), @@ -677,14 +642,14 @@ mod tests { #[case::tlsnotary(tlsnotary())] #[case::appliedzkp(appliedzkp())] fn test_verify_cert_chain_fail_no_cert( - crypto_provider: &CryptoProvider, + verifier: &WebPkiVerifier, #[case] mut data: ConnectionFixture, ) { // Empty certs data.server_cert_data.certs = Vec::new(); - let err = data.server_cert_data.verify_with_provider( - crypto_provider, + let err = data.server_cert_data.verify( + verifier, data.connection_info.time, data.server_ephemeral_key(), &ServerName::from(data.server_name.as_ref()), diff --git a/crates/core/src/connection/commit.rs b/crates/core/src/connection/commit.rs deleted file mode 100644 index 71d15c534..000000000 --- a/crates/core/src/connection/commit.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Types for committing details of a connection. - -use serde::{Deserialize, Serialize}; - -use crate::{ - connection::ServerCertData, - hash::{impl_domain_separator, Blinded, HashAlgorithm, HashAlgorithmExt, TypedHash}, -}; - -/// Opens a [`ServerCertCommitment`]. -#[derive(Clone, Serialize, Deserialize)] -pub struct ServerCertOpening(Blinded); - -impl_domain_separator!(ServerCertOpening); - -opaque_debug::implement!(ServerCertOpening); - -impl ServerCertOpening { - pub(crate) fn new(data: ServerCertData) -> Self { - Self(Blinded::new(data)) - } - - pub(crate) fn commit(&self, hasher: &dyn HashAlgorithm) -> ServerCertCommitment { - ServerCertCommitment(TypedHash { - alg: hasher.id(), - value: hasher.hash_separated(self), - }) - } - - /// Returns the server identity data. - pub fn data(&self) -> &ServerCertData { - self.0.data() - } -} - -/// Commitment to a server certificate. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ServerCertCommitment(pub(crate) TypedHash); - -impl_domain_separator!(ServerCertCommitment); diff --git a/crates/core/src/fixtures.rs b/crates/core/src/fixtures.rs index 1fcea676f..f1b332cb3 100644 --- a/crates/core/src/fixtures.rs +++ b/crates/core/src/fixtures.rs @@ -5,22 +5,16 @@ mod provider; pub use provider::FixtureEncodingProvider; use hex::FromHex; -use p256::ecdsa::SigningKey; use crate::{ - attestation::{Attestation, AttestationConfig, Extension}, connection::{ Certificate, ConnectionInfo, HandshakeData, HandshakeDataV1_2, KeyType, ServerCertData, ServerEphemKey, ServerName, ServerSignature, SignatureScheme, TlsVersion, TranscriptLength, }, - hash::HashAlgorithm, - request::{Request, RequestConfig}, - signing::SignatureAlgId, transcript::{ - encoding::{EncoderSecret, EncodingProvider, EncodingTree}, - Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment, + encoding::{EncoderSecret, EncodingProvider}, + Transcript, }, - CryptoProvider, }; /// A fixture containing various TLS connection data. @@ -152,112 +146,3 @@ pub fn encoder_secret_tampered_seed() -> EncoderSecret { seed[0] += 1; EncoderSecret::new(seed, DELTA) } - -/// Returns a notary signing key fixture. -pub fn notary_signing_key() -> SigningKey { - SigningKey::from_slice(&[1; 32]).unwrap() -} - -/// A Request fixture used for testing. -#[allow(missing_docs)] -pub struct RequestFixture { - pub encoding_tree: EncodingTree, - pub request: Request, -} - -/// Returns a request fixture for testing. -pub fn request_fixture( - transcript: Transcript, - encodings_provider: impl EncodingProvider, - connection: ConnectionFixture, - encoding_hasher: impl HashAlgorithm, - extensions: Vec, -) -> RequestFixture { - let provider = CryptoProvider::default(); - let (sent_len, recv_len) = transcript.len(); - - let ConnectionFixture { - server_name, - server_cert_data, - .. - } = connection; - - let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); - transcript_commitment_builder - .commit_sent(&(0..sent_len)) - .unwrap() - .commit_recv(&(0..recv_len)) - .unwrap(); - let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); - - // Prover constructs encoding tree. - let encoding_tree = EncodingTree::new( - &encoding_hasher, - transcripts_commitment_config.iter_encoding(), - &encodings_provider, - ) - .unwrap(); - - let mut builder = RequestConfig::builder(); - - for extension in extensions { - builder.extension(extension); - } - - let request_config = builder.build().unwrap(); - - let mut request_builder = Request::builder(&request_config); - request_builder - .server_name(server_name) - .server_cert_data(server_cert_data) - .transcript(transcript); - - let (request, _) = request_builder.build(&provider).unwrap(); - - RequestFixture { - encoding_tree, - request, - } -} - -/// Returns an attestation fixture for testing. -pub fn attestation_fixture( - request: Request, - connection: ConnectionFixture, - signature_alg: SignatureAlgId, - transcript_commitments: &[TranscriptCommitment], -) -> Attestation { - let ConnectionFixture { - connection_info, - server_cert_data, - .. - } = connection; - - let HandshakeData::V1_2(HandshakeDataV1_2 { - server_ephemeral_key, - .. - }) = server_cert_data.handshake; - - let mut provider = CryptoProvider::default(); - match signature_alg { - SignatureAlgId::SECP256K1 => provider.signer.set_secp256k1(&[42u8; 32]).unwrap(), - SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[42u8; 32]).unwrap(), - _ => unimplemented!(), - }; - - let attestation_config = AttestationConfig::builder() - .supported_signature_algs([signature_alg]) - .build() - .unwrap(); - - let mut attestation_builder = Attestation::builder(&attestation_config) - .accept_request(request) - .unwrap(); - - attestation_builder - .connection_info(connection_info) - .server_ephemeral_key(server_ephemeral_key) - .transcript_commitments(transcript_commitments.to_vec()); - - attestation_builder.build(&provider).unwrap() -} diff --git a/crates/core/src/hash.rs b/crates/core/src/hash.rs index 0d64b2141..f8eb38833 100644 --- a/crates/core/src/hash.rs +++ b/crates/core/src/hash.rs @@ -5,11 +5,6 @@ use std::{collections::HashMap, fmt::Display}; use rand::{distr::StandardUniform, prelude::Distribution}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::serialize::CanonicalSerialize; - -pub(crate) const DEFAULT_SUPPORTED_HASH_ALGS: &[HashAlgId] = - &[HashAlgId::SHA256, HashAlgId::BLAKE3, HashAlgId::KECCAK256]; - /// Maximum length of a hash value. const MAX_LEN: usize = 64; @@ -238,19 +233,6 @@ pub trait HashAlgorithm { fn hash_prefixed(&self, prefix: &[u8], data: &[u8]) -> Hash; } -pub(crate) trait HashAlgorithmExt: HashAlgorithm { - #[allow(dead_code)] - fn hash_canonical(&self, data: &T) -> Hash { - self.hash(&data.serialize()) - } - - fn hash_separated(&self, data: &T) -> Hash { - self.hash_prefixed(data.domain(), &data.serialize()) - } -} - -impl HashAlgorithmExt for T {} - /// A hash blinder. #[derive(Clone, Serialize, Deserialize)] pub struct Blinder([u8; 16]); @@ -274,52 +256,26 @@ impl Distribution for StandardUniform { /// A blinded pre-image of a hash. #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct Blinded { +pub struct Blinded { data: T, blinder: Blinder, } impl Blinded { /// Creates a new blinded pre-image. - pub(crate) fn new(data: T) -> Self { + pub fn new(data: T) -> Self { Self { data, blinder: rand::random(), } } - pub(crate) fn data(&self) -> &T { + /// Returns the data. + pub fn data(&self) -> &T { &self.data } } -/// A type with a domain separator which is used during hashing to mitigate type -/// confusion attacks. -pub(crate) trait DomainSeparator { - /// Returns the domain separator for the type. - fn domain(&self) -> &[u8]; -} - -macro_rules! impl_domain_separator { - ($type:ty) => { - impl $crate::hash::DomainSeparator for $type { - fn domain(&self) -> &[u8] { - use std::sync::LazyLock; - - // Computes a 16 byte hash of the type's name to use as a domain separator. - static DOMAIN: LazyLock<[u8; 16]> = LazyLock::new(|| { - let domain: [u8; 32] = blake3::hash(stringify!($type).as_bytes()).into(); - domain[..16].try_into().unwrap() - }); - - &*DOMAIN - } - } - }; -} - -pub(crate) use impl_domain_separator; - mod sha2 { use ::sha2::Digest; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index f9c61a37e..f3671cb9e 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,198 +1,16 @@ //! TLSNotary core library. -//! -//! # Introduction -//! -//! This library provides core functionality for the TLSNotary **attestation** -//! protocol, including some more general types which are useful outside -//! of attestations. -//! -//! Once the MPC-TLS protocol has been completed the Prover holds a collection -//! of commitments pertaining to the TLS connection. Most importantly, the -//! Prover is committed to the [`ServerName`](crate::connection::ServerName), -//! and the [`Transcript`](crate::transcript::Transcript) of application data. -//! Subsequently, the Prover can request an -//! [`Attestation`](crate::attestation::Attestation) from the Notary who will -//! include the commitments as well as any additional information which may be -//! useful to an attestation Verifier. -//! -//! Holding an attestation, the Prover can construct a -//! [`Presentation`](crate::presentation::Presentation) which facilitates -//! selectively disclosing various aspects of the TLS connection to a Verifier. -//! If the Verifier trusts the Notary, or more specifically the verifying key of -//! the attestation, then the Verifier can trust the authenticity of the -//! information disclosed in the presentation. -//! -//! **Be sure to check out the various submodules for more information.** -//! -//! # Committing to the transcript -//! -//! The MPC-TLS protocol produces commitments to the entire transcript of -//! application data. However, we may want to disclose only a subset of the data -//! in a presentation. Prior to attestation, the Prover has the opportunity to -//! slice and dice the commitments into smaller sections which can be -//! selectively disclosed. Additionally, the Prover may want to use different -//! commitment schemes depending on the context they expect to disclose. -//! -//! The primary API for this process is the -//! [`TranscriptCommitConfigBuilder`](crate::transcript::TranscriptCommitConfigBuilder) -//! which is used to build up a configuration. -//! -//! Currently, only the -//! [`Encoding`](crate::transcript::TranscriptCommitmentKind::Encoding) -//! commitment kind is supported. In the future you will be able to acquire hash -//! commitments directly to the transcript data. -//! -//! ```no_run -//! # use tlsn_core::transcript::{TranscriptCommitConfigBuilder, Transcript, Direction}; -//! # use tlsn_core::hash::HashAlgId; -//! # fn main() -> Result<(), Box> { -//! # let transcript: Transcript = unimplemented!(); -//! let (sent_len, recv_len) = transcript.len(); -//! -//! // Create a new configuration builder. -//! let mut builder = TranscriptCommitConfigBuilder::new(&transcript); -//! -//! // Specify all the transcript commitments we want to make. -//! builder -//! // Use BLAKE3 for encoding commitments. -//! .encoding_hash_alg(HashAlgId::BLAKE3) -//! // Commit to all sent data. -//! .commit_sent(&(0..sent_len))? -//! // Commit to the first 10 bytes of sent data. -//! .commit_sent(&(0..10))? -//! // Skip some bytes so it can be omitted in the presentation. -//! .commit_sent(&(20..sent_len))? -//! // Commit to all received data. -//! .commit_recv(&(0..recv_len))?; -//! -//! let config = builder.build()?; -//! # Ok(()) -//! # } -//! ``` -//! -//! # Requesting an attestation -//! -//! The first step in the attestation protocol is for the Prover to make a -//! [`Request`](crate::request::Request), which can be configured using the -//! associated [builder](crate::request::RequestConfigBuilder). With it the -//! Prover can configure some of the details of the attestation, such as which -//! cryptographic algorithms are used (if the Notary supports them). -//! -//! The Prover may also request for extensions to be added to the attestation, -//! see [here](crate::attestation#extensions) for more information. -//! -//! Upon being issued an attestation, the Prover will also hold a corresponding -//! [`Secrets`] which contains all private information. This pair can be stored -//! and used later to construct a -//! [`Presentation`](crate::presentation::Presentation), [see -//! below](#constructing-a-presentation). -//! -//! # Issuing an attestation -//! -//! Upon receiving a request, the Notary can issue an -//! [`Attestation`](crate::attestation::Attestation) which can be configured -//! using the associated -//! [builder](crate::attestation::AttestationConfigBuilder). -//! -//! The Notary's [`CryptoProvider`] must be configured with an appropriate -//! signing key for attestations. See -//! [`SignerProvider`](crate::signing::SignerProvider) for more information. -//! -//! # Constructing a presentation -//! -//! A Prover can use an [`Attestation`](crate::attestation::Attestation) and the -//! corresponding [`Secrets`] to construct a verifiable -//! [`Presentation`](crate::presentation::Presentation). -//! -//! ```no_run -//! # use tlsn_core::presentation::Presentation; -//! # use tlsn_core::attestation::Attestation; -//! # use tlsn_core::transcript::{TranscriptCommitmentKind, Direction}; -//! # use tlsn_core::{Secrets, CryptoProvider}; -//! # fn main() -> Result<(), Box> { -//! # let attestation: Attestation = unimplemented!(); -//! # let secrets: Secrets = unimplemented!(); -//! # let crypto_provider: CryptoProvider = unimplemented!(); -//! let (_sent_len, recv_len) = secrets.transcript().len(); -//! -//! // First, we decide which application data we would like to disclose. -//! let mut builder = secrets.transcript_proof_builder(); -//! -//! builder -//! // Use transcript encoding commitments. -//! .commitment_kinds(&[TranscriptCommitmentKind::Encoding]) -//! // Disclose the first 10 bytes of the sent data. -//! .reveal(&(0..10), Direction::Sent)? -//! // Disclose all of the received data. -//! .reveal(&(0..recv_len), Direction::Received)?; -//! -//! let transcript_proof = builder.build()?; -//! -//! // Most cases we will also disclose the server identity. -//! let identity_proof = secrets.identity_proof(); -//! -//! // Now we can construct the presentation. -//! let mut builder = attestation.presentation_builder(&crypto_provider); -//! -//! builder -//! .identity_proof(identity_proof) -//! .transcript_proof(transcript_proof); -//! -//! // Finally, we build the presentation. Send it to a verifier! -//! let presentation: Presentation = builder.build()?; -//! # Ok(()) -//! # } -//! ``` -//! -//! # Verifying a presentation -//! -//! Verifying a presentation is as simple as checking the verifier trusts the -//! verifying key then calling -//! [`Presentation::verify`](crate::presentation::Presentation::verify). -//! -//! ```no_run -//! # use tlsn_core::presentation::{Presentation, PresentationOutput}; -//! # use tlsn_core::signing::VerifyingKey; -//! # use tlsn_core::CryptoProvider; -//! # fn main() -> Result<(), Box> { -//! # let presentation: Presentation = unimplemented!(); -//! # let trusted_key: VerifyingKey = unimplemented!(); -//! # let crypto_provider: CryptoProvider = unimplemented!(); -//! // Assert that we trust the verifying key. -//! assert_eq!(presentation.verifying_key(), &trusted_key); -//! -//! let PresentationOutput { -//! attestation, -//! server_name, -//! connection_info, -//! transcript, -//! .. -//! } = presentation.verify(&crypto_provider)?; -//! # Ok(()) -//! # } -//! ``` #![deny(missing_docs, unreachable_pub, unused_must_use)] #![deny(clippy::all)] #![forbid(unsafe_code)] -pub mod attestation; pub mod connection; #[cfg(any(test, feature = "fixtures"))] pub mod fixtures; pub mod hash; -pub(crate) mod merkle; -pub mod presentation; -mod provider; -pub mod request; -mod secrets; -pub(crate) mod serialize; -pub mod signing; +pub mod merkle; pub mod transcript; -pub use provider::CryptoProvider; -pub use secrets::Secrets; - use rangeset::ToRangeSet; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/merkle.rs b/crates/core/src/merkle.rs index efbc94517..e12ead0cd 100644 --- a/crates/core/src/merkle.rs +++ b/crates/core/src/merkle.rs @@ -5,10 +5,10 @@ use utils::iter::DuplicateCheck; use crate::hash::{Hash, HashAlgId, HashAlgorithm, TypedHash}; -/// Errors that can occur during operations with Merkle tree and Merkle proof. +/// Merkle tree error. #[derive(Debug, thiserror::Error)] #[error("merkle error: {0}")] -pub(crate) struct MerkleError(String); +pub struct MerkleError(String); impl MerkleError { fn new(msg: impl Into) -> Self { @@ -16,8 +16,9 @@ impl MerkleError { } } +/// Merkle proof. #[derive(Clone, Serialize, Deserialize)] -pub(crate) struct MerkleProof { +pub struct MerkleProof { alg: HashAlgId, leaf_count: usize, proof: rs_merkle::MerkleProof, @@ -33,7 +34,7 @@ impl MerkleProof { /// /// - If the length of `leaf_indices` and `leaf_hashes` does not match. /// - If `leaf_indices` contains duplicates. - pub(crate) fn verify( + pub fn verify( &self, hasher: &dyn HashAlgorithm, root: &TypedHash, @@ -80,25 +81,29 @@ impl rs_merkle::Hasher for RsMerkleHasher<'_> { } } +/// Merkle tree. #[derive(Clone, Serialize, Deserialize)] -pub(crate) struct MerkleTree { +pub struct MerkleTree { alg: HashAlgId, tree: rs_merkle::MerkleTree, } impl MerkleTree { - pub(crate) fn new(alg: HashAlgId) -> Self { + /// Creates a new Merkle tree. + pub fn new(alg: HashAlgId) -> Self { Self { alg, tree: Default::default(), } } - pub(crate) fn algorithm(&self) -> HashAlgId { + /// Returns the hash algorithm used to create the tree. + pub fn algorithm(&self) -> HashAlgId { self.alg } - pub(crate) fn root(&self) -> TypedHash { + /// Returns the root of the tree. + pub fn root(&self) -> TypedHash { TypedHash { alg: self.alg, value: self.tree.root().expect("tree should not be empty"), @@ -111,7 +116,7 @@ impl MerkleTree { /// /// - If the provided hasher is not the same as the one used to create the /// tree. - pub(crate) fn insert(&mut self, hasher: &dyn HashAlgorithm, mut leaves: Vec) { + pub fn insert(&mut self, hasher: &dyn HashAlgorithm, mut leaves: Vec) { assert_eq!(self.alg, hasher.id(), "hash algorithm mismatch"); self.tree.append(&mut leaves); @@ -124,7 +129,7 @@ impl MerkleTree { /// /// - If the provided indices are not unique and sorted. /// - If the provided indices are out of bounds. - pub(crate) fn proof(&self, indices: &[usize]) -> MerkleProof { + pub fn proof(&self, indices: &[usize]) -> MerkleProof { assert!( indices.windows(2).all(|w| w[0] < w[1]), "indices must be unique and sorted" @@ -145,7 +150,7 @@ impl MerkleTree { #[cfg(test)] mod test { - use crate::hash::{impl_domain_separator, Blake3, HashAlgorithmExt, Keccak256, Sha256}; + use crate::hash::{Blake3, Keccak256, Sha256}; use super::*; use rstest::*; @@ -153,12 +158,10 @@ mod test { #[derive(Serialize)] struct T(u64); - impl_domain_separator!(T); - fn leaves(hasher: &H, leaves: impl IntoIterator) -> Vec { leaves .into_iter() - .map(|x| hasher.hash_canonical(&x)) + .map(|x| hasher.hash(&x.0.to_be_bytes())) .collect() } diff --git a/crates/core/src/serialize.rs b/crates/core/src/serialize.rs deleted file mode 100644 index 777149fbb..000000000 --- a/crates/core/src/serialize.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// Canonical serialization of TLSNotary types. -/// -/// This trait is used to serialize types into a canonical byte representation. -pub(crate) trait CanonicalSerialize { - /// Serializes the type. - fn serialize(&self) -> Vec; -} - -impl CanonicalSerialize for T -where - T: serde::Serialize, -{ - fn serialize(&self) -> Vec { - // For now we use BCS for serialization. In future releases we will want to - // consider this further, particularly with respect to EVM compatibility. - bcs::to_bytes(self).unwrap() - } -} diff --git a/crates/core/src/transcript.rs b/crates/core/src/transcript.rs index 677bf7574..2b5b69e5f 100644 --- a/crates/core/src/transcript.rs +++ b/crates/core/src/transcript.rs @@ -11,28 +11,14 @@ //! recovered by parsing the application data and relating it to the bytes //! in the transcript. //! -//! ## Commitments -//! -//! During the attestation process a Prover can generate multiple commitments to -//! various parts of the transcript. These commitments are inserted into the -//! attestation body and can be used by the Verifier to verify transcript proofs -//! later. -//! -//! To configure the transcript commitments, use the -//! [`TranscriptCommitConfigBuilder`]. -//! //! ## Selective Disclosure //! //! Using a [`TranscriptProof`] a Prover can selectively disclose parts of a //! transcript to a Verifier in the form of a [`PartialTranscript`]. A Verifier //! always learns the length of the transcript, but sensitive data can be //! withheld. -//! -//! To create a proof, use the [`TranscriptProofBuilder`] which is returned by -//! [`Secrets::transcript_proof_builder`](crate::Secrets::transcript_proof_builder). mod commit; -#[doc(hidden)] pub mod encoding; pub mod hash; mod proof; diff --git a/crates/core/src/transcript/commit.rs b/crates/core/src/transcript/commit.rs index 6ec065a57..901c851c0 100644 --- a/crates/core/src/transcript/commit.rs +++ b/crates/core/src/transcript/commit.rs @@ -6,7 +6,7 @@ use rangeset::ToRangeSet; use serde::{Deserialize, Serialize}; use crate::{ - hash::{impl_domain_separator, HashAlgId}, + hash::HashAlgId, transcript::{ encoding::{EncodingCommitment, EncodingTree}, hash::{PlaintextHash, PlaintextHashSecret}, @@ -55,8 +55,6 @@ pub enum TranscriptCommitment { Hash(PlaintextHash), } -impl_domain_separator!(TranscriptCommitment); - /// Secret for a transcript commitment. #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] @@ -67,8 +65,6 @@ pub enum TranscriptSecret { Hash(PlaintextHashSecret), } -impl_domain_separator!(TranscriptSecret); - /// Configuration for transcript commitments. #[derive(Debug, Clone)] pub struct TranscriptCommitConfig { diff --git a/crates/core/src/transcript/encoding.rs b/crates/core/src/transcript/encoding.rs index d5e2c4052..4da243f7b 100644 --- a/crates/core/src/transcript/encoding.rs +++ b/crates/core/src/transcript/encoding.rs @@ -1,7 +1,4 @@ //! Transcript encoding commitments and proofs. -//! -//! This is an internal module that is not intended to be used directly by -//! users. mod encoder; mod proof; @@ -15,7 +12,7 @@ pub use tree::{EncodingTree, EncodingTreeError}; use serde::{Deserialize, Serialize}; -use crate::hash::{impl_domain_separator, TypedHash}; +use crate::hash::TypedHash; /// Transcript encoding commitment. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -25,5 +22,3 @@ pub struct EncodingCommitment { /// Seed used to generate the encodings. pub secret: EncoderSecret, } - -impl_domain_separator!(EncodingCommitment); diff --git a/crates/core/src/transcript/encoding/proof.rs b/crates/core/src/transcript/encoding/proof.rs index 9c4abd378..a883b5468 100644 --- a/crates/core/src/transcript/encoding/proof.rs +++ b/crates/core/src/transcript/encoding/proof.rs @@ -4,14 +4,13 @@ use rangeset::{RangeSet, UnionMut}; use serde::{Deserialize, Serialize}; use crate::{ - hash::{Blinder, HashProviderError}, + hash::{Blinder, HashProvider, HashProviderError}, merkle::{MerkleError, MerkleProof}, transcript::{ commit::MAX_TOTAL_COMMITTED_DATA, encoding::{new_encoder, Encoder, EncodingCommitment}, Direction, Idx, }, - CryptoProvider, }; /// An opening of a leaf in the encoding tree. @@ -42,18 +41,18 @@ impl EncodingProof { /// /// # Arguments /// - /// * `provider` - Crypto provider. + /// * `provider` - Hash provider. /// * `commitment` - Encoding commitment to verify against. /// * `sent` - Sent data to authenticate. /// * `recv` - Received data to authenticate. pub fn verify_with_provider( &self, - provider: &CryptoProvider, + provider: &HashProvider, commitment: &EncodingCommitment, sent: &[u8], recv: &[u8], ) -> Result<(Idx, Idx), EncodingProofError> { - let hasher = provider.hash.get(&commitment.root.alg)?; + let hasher = provider.get(&commitment.root.alg)?; let encoder = new_encoder(&commitment.secret); let Self { @@ -280,7 +279,7 @@ mod test { let err = proof .verify_with_provider( - &CryptoProvider::default(), + &HashProvider::default(), &commitment, transcript.sent(), transcript.received(), @@ -302,7 +301,7 @@ mod test { let recv = &transcript.received()[transcript.received().len() - 2..]; let err = proof - .verify_with_provider(&CryptoProvider::default(), &commitment, sent, recv) + .verify_with_provider(&HashProvider::default(), &commitment, sent, recv) .unwrap_err(); assert!(matches!(err.kind, ErrorKind::Proof)); @@ -322,7 +321,7 @@ mod test { let err = proof .verify_with_provider( - &CryptoProvider::default(), + &HashProvider::default(), &commitment, transcript.sent(), transcript.received(), @@ -346,7 +345,7 @@ mod test { let err = proof .verify_with_provider( - &CryptoProvider::default(), + &HashProvider::default(), &commitment, transcript.sent(), transcript.received(), diff --git a/crates/core/src/transcript/encoding/provider.rs b/crates/core/src/transcript/encoding/provider.rs index 428cdd448..c70e666a4 100644 --- a/crates/core/src/transcript/encoding/provider.rs +++ b/crates/core/src/transcript/encoding/provider.rs @@ -13,6 +13,7 @@ pub trait EncodingProvider { ) -> Result<(), EncodingProviderError>; } +/// Error for [`EncodingProvider`]. #[derive(Debug, thiserror::Error)] #[error("failed to provide encoding")] pub struct EncodingProviderError; diff --git a/crates/core/src/transcript/encoding/tree.rs b/crates/core/src/transcript/encoding/tree.rs index cc98f1c9c..c429495d5 100644 --- a/crates/core/src/transcript/encoding/tree.rs +++ b/crates/core/src/transcript/encoding/tree.rs @@ -193,9 +193,8 @@ mod tests { use super::*; use crate::{ fixtures::{encoder_secret, encoding_provider}, - hash::Blake3, + hash::{Blake3, HashProvider}, transcript::{encoding::EncodingCommitment, Transcript}, - CryptoProvider, }; use tlsn_data_fixtures::http::{request::POST_JSON, response::OK_JSON}; @@ -229,7 +228,7 @@ mod tests { let (auth_sent, auth_recv) = proof .verify_with_provider( - &CryptoProvider::default(), + &HashProvider::default(), &commitment, transcript.sent(), transcript.received(), @@ -267,7 +266,7 @@ mod tests { let (auth_sent, auth_recv) = proof .verify_with_provider( - &CryptoProvider::default(), + &HashProvider::default(), &commitment, transcript.sent(), transcript.received(), diff --git a/crates/core/src/transcript/hash.rs b/crates/core/src/transcript/hash.rs index 5a97d85e9..724f9570c 100644 --- a/crates/core/src/transcript/hash.rs +++ b/crates/core/src/transcript/hash.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - hash::{impl_domain_separator, Blinder, HashAlgId, HashAlgorithm, TypedHash}, + hash::{Blinder, HashAlgId, HashAlgorithm, TypedHash}, transcript::{Direction, Idx}, }; @@ -28,8 +28,6 @@ pub struct PlaintextHash { pub hash: TypedHash, } -impl_domain_separator!(PlaintextHash); - /// Secret component of [`PlaintextHash`]. #[derive(Clone, Serialize, Deserialize)] pub struct PlaintextHashSecret { diff --git a/crates/core/src/transcript/proof.rs b/crates/core/src/transcript/proof.rs index 8db4050bd..d3629642f 100644 --- a/crates/core/src/transcript/proof.rs +++ b/crates/core/src/transcript/proof.rs @@ -6,14 +6,13 @@ use std::{collections::HashSet, fmt}; use crate::{ connection::TranscriptLength, - hash::HashAlgId, + hash::{HashAlgId, HashProvider}, transcript::{ commit::{TranscriptCommitment, TranscriptCommitmentKind}, encoding::{EncodingProof, EncodingProofError, EncodingTree}, hash::{hash_plaintext, PlaintextHash, PlaintextHashSecret}, Direction, Idx, PartialTranscript, Transcript, TranscriptSecret, }, - CryptoProvider, }; /// Default commitment kinds in order of preference for building transcript @@ -42,11 +41,11 @@ impl TranscriptProof { /// /// # Arguments /// - /// * `provider` - The crypto provider to use for verification. + /// * `provider` - The hash provider to use for verification. /// * `attestation_body` - The attestation body to verify against. pub fn verify_with_provider<'a>( self, - provider: &CryptoProvider, + provider: &HashProvider, length: &TranscriptLength, commitments: impl IntoIterator, ) -> Result { @@ -109,7 +108,7 @@ impl TranscriptProof { blinder, } in self.hash_secrets { - let hasher = provider.hash.get(&alg).map_err(|_| { + let hasher = provider.get(&alg).map_err(|_| { TranscriptProofError::new( ErrorKind::Hash, format!("hash opening has unknown algorithm: {alg}"), @@ -261,7 +260,7 @@ pub struct TranscriptProofBuilder<'a> { impl<'a> TranscriptProofBuilder<'a> { /// Creates a new proof builder. - pub(crate) fn new( + pub fn new( transcript: &'a Transcript, secrets: impl IntoIterator, ) -> Self { @@ -568,7 +567,7 @@ mod tests { use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; use crate::{ - fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture}, + fixtures::encoding_provider, hash::{Blake3, Blinder, HashAlgId}, transcript::TranscriptCommitConfigBuilder, }; @@ -578,15 +577,13 @@ mod tests { #[rstest] fn test_verify_missing_encoding_commitment_root() { let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); - let connection = ConnectionFixture::tlsnotary(transcript.length()); - - let RequestFixture { encoding_tree, .. } = request_fixture( - transcript.clone(), - encoding_provider(GET_WITH_HEADER, OK_JSON), - connection.clone(), - Blake3::default(), - Vec::new(), - ); + let idxs = vec![(Direction::Received, Idx::new(0..transcript.len().1))]; + let encoding_tree = EncodingTree::new( + &Blake3::default(), + &idxs, + &encoding_provider(transcript.sent(), transcript.received()), + ) + .unwrap(); let secrets = vec![TranscriptSecret::Encoding(encoding_tree)]; let mut builder = TranscriptProofBuilder::new(&transcript, &secrets); @@ -595,7 +592,7 @@ mod tests { let transcript_proof = builder.build().unwrap(); - let provider = CryptoProvider::default(); + let provider = HashProvider::default(); let err = transcript_proof .verify_with_provider(&provider, &transcript.length(), &[]) .err() @@ -637,14 +634,14 @@ mod tests { #[rstest] fn test_reveal_with_hash_commitment() { let mut rng = rand::rngs::StdRng::seed_from_u64(0); - let provider = CryptoProvider::default(); + let provider = HashProvider::default(); let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); let direction = Direction::Sent; let idx = Idx::new(0..10); let blinder: Blinder = rng.random(); let alg = HashAlgId::SHA256; - let hasher = provider.hash.get(&alg).unwrap(); + let hasher = provider.get(&alg).unwrap(); let commitment = PlaintextHash { direction, @@ -683,14 +680,14 @@ mod tests { #[rstest] fn test_reveal_with_inconsistent_hash_commitment() { let mut rng = rand::rngs::StdRng::seed_from_u64(0); - let provider = CryptoProvider::default(); + let provider = HashProvider::default(); let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); let direction = Direction::Sent; let idx = Idx::new(0..10); let blinder: Blinder = rng.random(); let alg = HashAlgId::SHA256; - let hasher = provider.hash.get(&alg).unwrap(); + let hasher = provider.get(&alg).unwrap(); let commitment = PlaintextHash { direction, diff --git a/crates/examples/attestation/present.rs b/crates/examples/attestation/present.rs index 7c6ca560e..60be20e2f 100644 --- a/crates/examples/attestation/present.rs +++ b/crates/examples/attestation/present.rs @@ -5,7 +5,7 @@ use clap::Parser; use hyper::header; -use tlsn_core::{attestation::Attestation, presentation::Presentation, CryptoProvider, Secrets}; +use tlsn::attestation::{presentation::Presentation, Attestation, CryptoProvider, Secrets}; use tlsn_examples::ExampleType; use tlsn_formats::http::HttpTranscript; diff --git a/crates/examples/attestation/prove.rs b/crates/examples/attestation/prove.rs index 520334412..cc6018ff7 100644 --- a/crates/examples/attestation/prove.rs +++ b/crates/examples/attestation/prove.rs @@ -13,13 +13,13 @@ use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tracing::debug; use notary_client::{Accepted, NotarizationRequest, NotaryClient}; -use tls_core::verify::WebPkiVerifier; use tls_server_fixture::{CA_CERT_DER, SERVER_DOMAIN}; use tlsn::{ + attestation::request::RequestConfig, config::ProtocolConfig, prover::{Prover, ProverConfig, TlsConfig}, + transcript::TranscriptCommitConfig, }; -use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig, CryptoProvider}; use tlsn_examples::ExampleType; use tlsn_formats::http::{DefaultHttpCommitter, HttpCommit, HttpTranscript}; use tlsn_server_fixture::DEFAULT_FIXTURE_PORT; @@ -102,10 +102,6 @@ async fn notarize( root_store .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let crypto_provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; // Set up protocol configuration for prover. let mut prover_config_builder = ProverConfig::builder(); @@ -119,8 +115,7 @@ async fn notarize( .max_sent_data(tlsn_examples::MAX_SENT_DATA) .max_recv_data(tlsn_examples::MAX_RECV_DATA) .build()?, - ) - .crypto_provider(crypto_provider); + ); // (Optional) Set up TLS client authentication if required by the server. prover_config_builder.tls_config( diff --git a/crates/examples/attestation/verify.rs b/crates/examples/attestation/verify.rs index 8c52ba17a..6cede614b 100644 --- a/crates/examples/attestation/verify.rs +++ b/crates/examples/attestation/verify.rs @@ -8,7 +8,7 @@ use clap::Parser; use tls_core::verify::WebPkiVerifier; use tls_server_fixture::CA_CERT_DER; -use tlsn_core::{ +use tlsn::attestation::{ presentation::{Presentation, PresentationOutput}, signing::VerifyingKey, CryptoProvider, diff --git a/crates/examples/interactive/interactive.rs b/crates/examples/interactive/interactive.rs index 535a7a794..931ae6b18 100644 --- a/crates/examples/interactive/interactive.rs +++ b/crates/examples/interactive/interactive.rs @@ -10,18 +10,15 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tracing::instrument; -use tls_core::verify::WebPkiVerifier; use tls_server_fixture::CA_CERT_DER; use tlsn::{ config::{ProtocolConfig, ProtocolConfigValidator}, - prover::{Prover, ProverConfig, TlsConfig}, - verifier::{Verifier, VerifierConfig}, -}; -use tlsn_core::{ - transcript::PartialTranscript, CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig, + prover::{ProveConfig, Prover, ProverConfig, TlsConfig}, + transcript::PartialTranscript, + verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig}, }; use tlsn_server_fixture::DEFAULT_FIXTURE_PORT; -use tlsn_server_fixture_certs::{CLIENT_CERT, CLIENT_KEY, SERVER_DOMAIN}; +use tlsn_server_fixture_certs::SERVER_DOMAIN; const SECRET: &str = "TLSNotary's private key 🤡"; @@ -72,41 +69,29 @@ async fn prover( assert_eq!(uri.scheme().unwrap().as_str(), "https"); let server_domain = uri.authority().unwrap().host(); - // Create a crypto provider accepting the server-fixture's self-signed - // root certificate. - // - // This is only required for offline testing with the server-fixture. In - // production, use `CryptoProvider::default()` instead. + // Create a root certificate store with the server-fixture's self-signed + // certificate. This is only required for offline testing with the + // server-fixture. let mut root_store = tls_core::anchors::RootCertStore::empty(); root_store .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let crypto_provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; + let mut tls_config_builder = TlsConfig::builder(); + tls_config_builder.root_store(root_store); + let tls_config = tls_config_builder.build().unwrap(); // Set up protocol configuration for prover. let mut prover_config_builder = ProverConfig::builder(); prover_config_builder .server_name(server_domain) + .tls_config(tls_config) .protocol_config( ProtocolConfig::builder() .max_sent_data(MAX_SENT_DATA) .max_recv_data(MAX_RECV_DATA) .build() .unwrap(), - ) - .crypto_provider(crypto_provider); - - // (Optional) Set up TLS client authentication if required by the server. - prover_config_builder.tls_config( - TlsConfig::builder() - .client_auth_pem((vec![CLIENT_CERT.to_vec()], CLIENT_KEY.to_vec())) - .unwrap() - .build() - .unwrap(), - ); + ); let prover_config = prover_config_builder.build().unwrap(); @@ -206,23 +191,17 @@ async fn verifier( .build() .unwrap(); - // Create a crypto provider accepting the server-fixture's self-signed - // root certificate. - // - // This is only required for offline testing with the server-fixture. In - // production, use `CryptoProvider::default()` instead. + // Create a root certificate store with the server-fixture's self-signed + // certificate. This is only required for offline testing with the + // server-fixture. let mut root_store = tls_core::anchors::RootCertStore::empty(); root_store .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let crypto_provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; let verifier_config = VerifierConfig::builder() + .root_store(root_store) .protocol_config_validator(config_validator) - .crypto_provider(crypto_provider) .build() .unwrap(); let verifier = Verifier::new(verifier_config); diff --git a/crates/harness/executor/src/bench/prover.rs b/crates/harness/executor/src/bench/prover.rs index 2556701d8..f359ba2ec 100644 --- a/crates/harness/executor/src/bench/prover.rs +++ b/crates/harness/executor/src/bench/prover.rs @@ -4,12 +4,10 @@ use anyhow::Result; use futures::{AsyncReadExt, AsyncWriteExt, TryFutureExt}; use harness_core::bench::{Bench, ProverMetrics}; -use tls_core::verify::WebPkiVerifier; use tlsn::{ config::ProtocolConfig, - prover::{Prover, ProverConfig}, + prover::{ProveConfig, Prover, ProverConfig, TlsConfig}, }; -use tlsn_core::{CryptoProvider, ProveConfig}; use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN}; use crate::{ @@ -39,16 +37,15 @@ pub async fn bench_prover(provider: &IoProvider, config: &Bench) -> Result Result<()> .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let crypto_provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; - let verifier = Verifier::new( VerifierConfig::builder() + .root_store(root_store) .protocol_config_validator(protocol_config) - .crypto_provider(crypto_provider) .build()?, ); diff --git a/crates/harness/executor/test_plugins/basic.rs b/crates/harness/executor/test_plugins/basic.rs index b1bb01d0a..35334c56e 100644 --- a/crates/harness/executor/test_plugins/basic.rs +++ b/crates/harness/executor/test_plugins/basic.rs @@ -1,13 +1,10 @@ -use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier}; +use tls_core::anchors::RootCertStore; use tlsn::{ config::{ProtocolConfig, ProtocolConfigValidator}, - prover::{Prover, ProverConfig}, - verifier::{Verifier, VerifierConfig}, -}; -use tlsn_core::{ - CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig, hash::HashAlgId, + prover::{ProveConfig, Prover, ProverConfig, TlsConfig}, transcript::{TranscriptCommitConfig, TranscriptCommitment, TranscriptCommitmentKind}, + verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig}, }; use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN}; @@ -29,14 +26,15 @@ async fn prover(provider: &IoProvider) { .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let crypto_provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; + let mut tls_config_builder = TlsConfig::builder(); + tls_config_builder.root_store(root_store); + + let tls_config = tls_config_builder.build().unwrap(); let prover = Prover::new( ProverConfig::builder() .server_name(SERVER_DOMAIN) + .tls_config(tls_config) .protocol_config( ProtocolConfig::builder() .max_sent_data(MAX_SENT_DATA) @@ -45,7 +43,6 @@ async fn prover(provider: &IoProvider) { .build() .unwrap(), ) - .crypto_provider(crypto_provider) .build() .unwrap(), ) @@ -122,11 +119,6 @@ async fn verifier(provider: &IoProvider) { .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let crypto_provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; - let config = VerifierConfig::builder() .protocol_config_validator( ProtocolConfigValidator::builder() @@ -135,7 +127,7 @@ async fn verifier(provider: &IoProvider) { .build() .unwrap(), ) - .crypto_provider(crypto_provider) + .root_store(root_store) .build() .unwrap(); diff --git a/crates/notary/server/src/server.rs b/crates/notary/server/src/server.rs index 456272fa1..67c6886ea 100644 --- a/crates/notary/server/src/server.rs +++ b/crates/notary/server/src/server.rs @@ -19,7 +19,7 @@ use std::{ pin::Pin, sync::Arc, }; -use tlsn_core::CryptoProvider; +use tlsn::attestation::CryptoProvider; use tokio::{fs::File, io::AsyncReadExt, net::TcpListener}; use tokio_rustls::{rustls, TlsAcceptor}; use tower_http::cors::CorsLayer; diff --git a/crates/notary/server/src/service.rs b/crates/notary/server/src/service.rs index 9209565d2..bf2cab467 100644 --- a/crates/notary/server/src/service.rs +++ b/crates/notary/server/src/service.rs @@ -13,10 +13,10 @@ use eyre::eyre; use notary_common::{NotarizationSessionRequest, NotarizationSessionResponse}; use std::time::Duration; use tlsn::{ + attestation::AttestationConfig, config::ProtocolConfigValidator, verifier::{Verifier, VerifierConfig}, }; -use tlsn_core::attestation::AttestationConfig; use tokio::{ io::{AsyncRead, AsyncWrite}, time::timeout, @@ -222,13 +222,16 @@ pub async fn notary_service( .max_recv_data(notary_globals.notarization_config.max_recv_data) .build()?, ) - .crypto_provider(crypto_provider) .build()?; #[allow(deprecated)] timeout( Duration::from_secs(notary_globals.notarization_config.timeout), - Verifier::new(config).notarize(socket.compat(), &att_config), + Verifier::new(config).notarize_with_provider( + socket.compat(), + &att_config, + &crypto_provider, + ), ) .await .map_err(|_| eyre!("Timeout reached before notarization completes"))??; diff --git a/crates/notary/server/src/signing.rs b/crates/notary/server/src/signing.rs index b28bcc9ad..c45b75e3e 100644 --- a/crates/notary/server/src/signing.rs +++ b/crates/notary/server/src/signing.rs @@ -7,7 +7,7 @@ use pkcs8::{ AssociatedOid, DecodePrivateKey, LineEnding, PrivateKeyInfo, }; use rand06_compat::Rand0_6CompatExt; -use tlsn_core::signing::{Secp256k1Signer, Secp256r1Signer, SignatureAlgId, Signer}; +use tlsn::attestation::signing::{Secp256k1Signer, Secp256r1Signer, SignatureAlgId, Signer}; use tracing::error; /// A cryptographic key used for signing attestations. diff --git a/crates/notary/server/src/types.rs b/crates/notary/server/src/types.rs index ddea516dc..9e9bc9d4b 100644 --- a/crates/notary/server/src/types.rs +++ b/crates/notary/server/src/types.rs @@ -3,7 +3,7 @@ use std::{ collections::HashMap, sync::{Arc, Mutex}, }; -use tlsn_core::CryptoProvider; +use tlsn::attestation::CryptoProvider; use tokio::sync::Semaphore; #[cfg(feature = "tee_quote")] diff --git a/crates/notary/tests-integration/tests/notary.rs b/crates/notary/tests-integration/tests/notary.rs index 960f9befb..95b6b0431 100644 --- a/crates/notary/tests-integration/tests/notary.rs +++ b/crates/notary/tests-integration/tests/notary.rs @@ -15,13 +15,13 @@ use notary_common::{ClientType, NotarizationSessionRequest, NotarizationSessionR use rstest::rstest; use rustls::{Certificate, RootCertStore}; 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::{ + attestation::request::RequestConfig, config::ProtocolConfig, - prover::{Prover, ProverConfig}, + prover::{Prover, ProverConfig, TlsConfig}, + transcript::TranscriptCommitConfig, }; -use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig, CryptoProvider}; use tokio::{ io::{AsyncRead, AsyncWrite, AsyncWriteExt}, time::sleep, @@ -230,11 +230,6 @@ async fn test_tcp_prover( .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; - let protocol_config = ProtocolConfig::builder() .max_sent_data(MAX_SENT_DATA) .max_recv_data(MAX_RECV_DATA) @@ -244,8 +239,8 @@ async fn test_tcp_prover( // Set up prover config. let prover_config = ProverConfig::builder() .server_name(SERVER_DOMAIN) + .tls_config(TlsConfig::builder().root_store(root_store).build().unwrap()) .protocol_config(protocol_config) - .crypto_provider(provider) .build() .unwrap(); @@ -428,10 +423,9 @@ async fn test_websocket_prover() { .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; + let mut tls_config_builder = TlsConfig::builder(); + tls_config_builder.root_store(root_store); + let tls_config = tls_config_builder.build().unwrap(); let protocol_config = ProtocolConfig::builder() .max_sent_data(MAX_SENT_DATA) @@ -443,7 +437,7 @@ async fn test_websocket_prover() { let prover_config = ProverConfig::builder() .server_name(SERVER_DOMAIN) .protocol_config(protocol_config) - .crypto_provider(provider) + .tls_config(tls_config) .build() .unwrap(); diff --git a/crates/tls/core/src/rand.rs b/crates/tls/core/src/rand.rs index 8e5edbbd7..effef0113 100644 --- a/crates/tls/core/src/rand.rs +++ b/crates/tls/core/src/rand.rs @@ -7,7 +7,7 @@ pub fn fill_random(bytes: &mut [u8]) -> Result<(), Error> { Ok(()) } -/// Make a Vec of the given size +/// Make a `Vec` of the given size /// containing random material. pub fn random_vec(len: usize) -> Result, Error> { let mut v = vec![0; len]; diff --git a/crates/tls/core/src/verify.rs b/crates/tls/core/src/verify.rs index 1373e59dc..10ac68821 100644 --- a/crates/tls/core/src/verify.rs +++ b/crates/tls/core/src/verify.rs @@ -111,11 +111,11 @@ pub trait ServerCertVerifier: Send + Sync { /// connection. /// /// This method is only called for TLS1.2 handshakes. Note that, in TLS1.2, - /// SignatureSchemes such as `SignatureScheme::ECDSA_NISTP256_SHA256` are not - /// in fact bound to the specific curve implied in their name. + /// SignatureSchemes such as `SignatureScheme::ECDSA_NISTP256_SHA256` are + /// not in fact bound to the specific curve implied in their name. /// - /// This trait method has a default implementation that uses webpki to verify - /// the signature. + /// This trait method has a default implementation that uses webpki to + /// verify the signature. fn verify_tls12_signature( &self, message: &[u8], @@ -130,12 +130,13 @@ pub trait ServerCertVerifier: Send + Sync { /// This method is only called for TLS1.3 handshakes. /// /// This method is very similar to `verify_tls12_signature`: but note the - /// tighter ECDSA SignatureScheme semantics -- e.g. `SignatureScheme::ECDSA_NISTP256_SHA256` - /// must only validate signatures using public keys on the right curve -- - /// rustls does not enforce this requirement for you. + /// tighter ECDSA SignatureScheme semantics -- e.g. + /// `SignatureScheme::ECDSA_NISTP256_SHA256` must only validate + /// signatures using public keys on the right curve -- rustls does not + /// enforce this requirement for you. /// - /// This trait method has a default implementation that uses webpki to verify - /// the signature. + /// This trait method has a default implementation that uses webpki to + /// verify the signature. fn verify_tls13_signature( &self, message: &[u8], @@ -186,9 +187,9 @@ pub trait ClientCertVerifier: Send + Sync { true } - /// Return `Some(true)` to require a client certificate and `Some(false)` to make - /// client authentication optional. Return `None` to abort the connection. - /// Defaults to `Some(self.offer_client_auth())`. + /// Return `Some(true)` to require a client certificate and `Some(false)` to + /// make client authentication optional. Return `None` to abort the + /// connection. Defaults to `Some(self.offer_client_auth())`. fn client_auth_mandatory(&self) -> Option { Some(self.offer_client_auth()) } @@ -227,11 +228,11 @@ pub trait ClientCertVerifier: Send + Sync { /// connection. /// /// This method is only called for TLS1.2 handshakes. Note that, in TLS1.2, - /// SignatureSchemes such as `SignatureScheme::ECDSA_NISTP256_SHA256` are not - /// in fact bound to the specific curve implied in their name. + /// SignatureSchemes such as `SignatureScheme::ECDSA_NISTP256_SHA256` are + /// not in fact bound to the specific curve implied in their name. /// - /// This trait method has a default implementation that uses webpki to verify - /// the signature. + /// This trait method has a default implementation that uses webpki to + /// verify the signature. fn verify_tls12_signature( &self, message: &[u8], @@ -246,12 +247,13 @@ pub trait ClientCertVerifier: Send + Sync { /// This method is only called for TLS1.3 handshakes. /// /// This method is very similar to `verify_tls12_signature`: but note the - /// tighter ECDSA SignatureScheme semantics -- e.g. `SignatureScheme::ECDSA_NISTP256_SHA256` - /// must only validate signatures using public keys on the right curve -- - /// rustls does not enforce this requirement for you. + /// tighter ECDSA SignatureScheme semantics -- e.g. + /// `SignatureScheme::ECDSA_NISTP256_SHA256` must only validate + /// signatures using public keys on the right curve -- rustls does not + /// enforce this requirement for you. /// - /// This trait method has a default implementation that uses webpki to verify - /// the signature. + /// This trait method has a default implementation that uses webpki to + /// verify the signature. fn verify_tls13_signature( &self, message: &[u8], @@ -321,6 +323,7 @@ impl ServerCertVerifier for WebPkiVerifier { /// Default `ServerCertVerifier`, see the trait impl for more information. #[allow(unreachable_pub)] +#[derive(Debug, Clone)] pub struct WebPkiVerifier { roots: RootCertStore, ct_policy: Option, @@ -363,12 +366,14 @@ impl WebPkiVerifier { /// Policy for enforcing Certificate Transparency. /// -/// Because Certificate Transparency logs are sharded on a per-year basis and can be trusted or -/// distrusted relatively quickly, rustls stores a validation deadline. Server certificates will -/// be validated against the configured CT logs until the deadline expires. After the deadline, -/// certificates will no longer be validated, and a warning message will be logged. The deadline +/// Because Certificate Transparency logs are sharded on a per-year basis and +/// can be trusted or distrusted relatively quickly, rustls stores a validation +/// deadline. Server certificates will be validated against the configured CT +/// logs until the deadline expires. After the deadline, certificates will no +/// longer be validated, and a warning message will be logged. The deadline /// may vary depending on how often you deploy builds with updated dependencies. #[allow(unreachable_pub)] +#[derive(Debug, Clone)] pub struct CertificateTransparencyPolicy { logs: &'static [&'static sct::Log<'static>], validation_deadline: SystemTime, @@ -505,8 +510,9 @@ fn verify_sig_using_any_alg( message: &[u8], sig: &[u8], ) -> Result<(), webpki::Error> { - // TLS doesn't itself give us enough info to map to a single webpki::SignatureAlgorithm. - // Therefore, convert_algs maps to several and we try them all. + // TLS doesn't itself give us enough info to map to a single + // webpki::SignatureAlgorithm. Therefore, convert_algs maps to several and + // we try them all. for alg in algs { match cert.verify_signature(alg, message, sig) { Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) => continue, diff --git a/crates/tlsn/Cargo.toml b/crates/tlsn/Cargo.toml index 30e2e640a..5efe1d029 100644 --- a/crates/tlsn/Cargo.toml +++ b/crates/tlsn/Cargo.toml @@ -16,6 +16,7 @@ rayon = ["mpz-common/rayon"] force-st = ["mpz-common/force-st"] [dependencies] +tlsn-attestation = { workspace = true } tlsn-core = { workspace = true } tlsn-deap = { workspace = true } tlsn-tls-client = { workspace = true } @@ -52,6 +53,7 @@ ghash = { workspace = true } semver = { workspace = true, features = ["serde"] } once_cell = { workspace = true } rangeset = { workspace = true } +webpki-roots = { workspace = true } [dev-dependencies] rstest = { workspace = true } diff --git a/crates/tlsn/src/lib.rs b/crates/tlsn/src/lib.rs index 3524f083b..ea36f5976 100644 --- a/crates/tlsn/src/lib.rs +++ b/crates/tlsn/src/lib.rs @@ -16,7 +16,8 @@ pub(crate) mod tag; pub mod verifier; pub(crate) mod zk_aes_ctr; -pub use tlsn_core::{attestation, connection, hash, presentation, transcript}; +pub use tlsn_attestation as attestation; +pub use tlsn_core::{connection, hash, transcript}; /// The party's role in the TLSN protocol. /// diff --git a/crates/tlsn/src/prover.rs b/crates/tlsn/src/prover.rs index b07f5128a..b6764d1d8 100644 --- a/crates/tlsn/src/prover.rs +++ b/crates/tlsn/src/prover.rs @@ -37,11 +37,14 @@ use std::sync::Arc; use tls_client::{ClientConnection, ServerName as TlsServerName}; use tls_client_async::{TlsConnection, bind_client}; use tls_core::msgs::enums::ContentType; -use tlsn_core::{ - ProvePayload, Secrets, - attestation::Attestation, - connection::ServerCertData, +use tlsn_attestation::{ + Attestation, CryptoProvider, Secrets, request::{Request, RequestConfig}, +}; +use tlsn_core::{ + ProvePayload, + connection::ServerCertData, + hash::{Blake3, HashAlgId, HashAlgorithm, Keccak256, Sha256}, transcript::{Direction, TlsTranscript, Transcript, TranscriptCommitment, TranscriptSecret}, }; use tlsn_deap::Deap; @@ -190,7 +193,7 @@ impl Prover { let config = tls_client::ClientConfig::builder() .with_safe_defaults() - .with_root_certificates(self.config.crypto_provider().cert.root_store().clone()); + .with_root_certificates(self.config.tls_config().root_store().clone()); let config = if let Some((cert, key)) = self.config.tls_config().client_auth() { config @@ -389,12 +392,17 @@ impl Prover { let mut hash_commitments = None; if let Some(commit_config) = config.transcript_commit() { if commit_config.has_encoding() { - let hasher = self - .config - .crypto_provider() - .hash - .get(commit_config.encoding_hash_alg()) - .map_err(ProverError::config)?; + let hasher: &(dyn HashAlgorithm + Send + Sync) = + match *commit_config.encoding_hash_alg() { + HashAlgId::SHA256 => &Sha256::default(), + HashAlgId::KECCAK256 => &Keccak256::default(), + HashAlgId::BLAKE3 => &Blake3::default(), + alg => { + return Err(ProverError::config(format!( + "unsupported hash algorithm for encoding commitment: {alg}" + ))); + } + }; let (commitment, tree) = mux_fut .poll_with( @@ -462,6 +470,26 @@ impl Prover { pub async fn notarize( &mut self, config: &RequestConfig, + ) -> Result<(Attestation, Secrets), ProverError> { + #[allow(deprecated)] + self.notarize_with_provider(config, &CryptoProvider::default()) + .await + } + + /// Requests an attestation from the verifier. + /// + /// # Arguments + /// + /// * `config` - The attestation request configuration. + /// * `provider` - Cryptography provider. + #[instrument(parent = &self.span, level = "info", skip_all, err)] + #[deprecated( + note = "attestation functionality will be removed from this API in future releases." + )] + pub async fn notarize_with_provider( + &mut self, + config: &RequestConfig, + provider: &CryptoProvider, ) -> Result<(Attestation, Secrets), ProverError> { let mut builder = ProveConfig::builder(self.transcript()); @@ -520,9 +548,7 @@ impl Prover { .transcript(transcript.clone()) .transcript_commitments(transcript_secrets, transcript_commitments); - let (request, secrets) = builder - .build(self.config.crypto_provider()) - .map_err(ProverError::attestation)?; + let (request, secrets) = builder.build(provider).map_err(ProverError::attestation)?; let attestation = mux_fut .poll_with(async { diff --git a/crates/tlsn/src/prover/config.rs b/crates/tlsn/src/prover/config.rs index 56400eb20..b946a5592 100644 --- a/crates/tlsn/src/prover/config.rs +++ b/crates/tlsn/src/prover/config.rs @@ -1,11 +1,11 @@ -use std::sync::Arc; - use crate::config::{NetworkSetting, ProtocolConfig}; -use derive_builder::UninitializedFieldError; use mpc_tls::Config; use rustls_pki_types::{CertificateDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, pem::PemObject}; -use tls_core::key; -use tlsn_core::{CryptoProvider, connection::ServerName}; +use tls_core::{ + anchors::{OwnedTrustAnchor, RootCertStore}, + key, +}; +use tlsn_core::connection::ServerName; /// Configuration for the prover. #[derive(Debug, Clone, derive_builder::Builder)] @@ -15,9 +15,6 @@ pub struct ProverConfig { server_name: ServerName, /// Protocol configuration to be checked with the verifier. protocol_config: ProtocolConfig, - /// Cryptography provider. - #[builder(default, setter(into))] - crypto_provider: Arc, /// TLS configuration. #[builder(default)] tls_config: TlsConfig, @@ -34,11 +31,6 @@ impl ProverConfig { &self.server_name } - /// Returns the crypto provider. - pub fn crypto_provider(&self) -> &CryptoProvider { - &self.crypto_provider - } - /// Returns the protocol configuration. pub fn protocol_config(&self) -> &ProtocolConfig { &self.protocol_config @@ -75,21 +67,43 @@ impl ProverConfig { } /// Configuration for the prover's TLS connection. -#[derive(Debug, Clone, Default, derive_builder::Builder)] -#[builder(build_fn(error = "TlsConfigError"))] +#[derive(Debug, Clone)] pub struct TlsConfig { + /// Root certificates. + root_store: RootCertStore, /// Certificate chain and a matching private key for client /// authentication. - #[builder(default, setter(custom, strip_option))] client_auth: Option<(Vec, key::PrivateKey)>, } +impl Default for TlsConfig { + fn default() -> Self { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject.as_ref(), + ta.subject_public_key_info.as_ref(), + ta.name_constraints.as_ref().map(|nc| nc.as_ref()), + ) + })); + + Self { + root_store, + client_auth: None, + } + } +} + impl TlsConfig { /// Creates a new builder for `TlsConfig`. pub fn builder() -> TlsConfigBuilder { TlsConfigBuilder::default() } + pub(crate) fn root_store(&self) -> &RootCertStore { + &self.root_store + } + /// Returns a certificate chain and a matching private key for client /// authentication. pub fn client_auth(&self) -> &Option<(Vec, key::PrivateKey)> { @@ -97,7 +111,21 @@ impl TlsConfig { } } +/// Builder for [`TlsConfig`]. +#[derive(Debug, Default)] +pub struct TlsConfigBuilder { + root_store: Option, + client_auth: Option<(Vec, key::PrivateKey)>, +} + impl TlsConfigBuilder { + /// Sets the root certificates to use for verifying the server's + /// certificate. + pub fn root_store(&mut self, store: RootCertStore) -> &mut Self { + self.root_store = Some(store); + self + } + /// Sets a DER-encoded certificate chain and a matching private key for /// client authentication. /// @@ -117,7 +145,7 @@ impl TlsConfigBuilder { .map(key::Certificate) .collect::>(); - self.client_auth = Some(Some((certs, key::PrivateKey(cert_key.1)))); + self.client_auth = Some((certs, key::PrivateKey(cert_key.1))); self } @@ -157,9 +185,29 @@ impl TlsConfigBuilder { }) .collect::, _>>()?; - self.client_auth = Some(Some((certs, key::PrivateKey(key)))); + self.client_auth = Some((certs, key::PrivateKey(key))); Ok(self) } + + /// Builds the TLS configuration. + pub fn build(&self) -> Result { + Ok(TlsConfig { + root_store: self.root_store.clone().unwrap_or_else(|| { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map( + |ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject.as_ref(), + ta.subject_public_key_info.as_ref(), + ta.name_constraints.as_ref().map(|nc| nc.as_ref()), + ) + }, + )); + root_store + }), + client_auth: self.client_auth.clone(), + }) + } } /// TLS configuration error. @@ -170,16 +218,8 @@ pub struct TlsConfigError(#[from] ErrorRepr); #[derive(Debug, thiserror::Error)] #[error("tls config error: {0}")] enum ErrorRepr { - #[error("missing field: {0:?}")] - MissingField(String), #[error("the certificate for client authentication is invalid")] InvalidCertificate, #[error("the private key for client authentication is invalid")] InvalidKey, } - -impl From for TlsConfigError { - fn from(e: UninitializedFieldError) -> Self { - ErrorRepr::MissingField(e.field_name().to_string()).into() - } -} diff --git a/crates/tlsn/src/verifier.rs b/crates/tlsn/src/verifier.rs index 9cc479188..62f764371 100644 --- a/crates/tlsn/src/verifier.rs +++ b/crates/tlsn/src/verifier.rs @@ -31,12 +31,11 @@ use mpz_core::Block; use mpz_garble_core::Delta; use mpz_vm_core::prelude::*; use serio::{SinkExt, stream::IoStreamExt}; -use tls_core::msgs::enums::ContentType; +use tls_core::{msgs::enums::ContentType, verify::WebPkiVerifier}; +use tlsn_attestation::{Attestation, AttestationConfig, CryptoProvider, request::Request}; use tlsn_core::{ ProvePayload, - attestation::{Attestation, AttestationConfig}, connection::{ConnectionInfo, ServerName, TranscriptLength}, - request::Request, transcript::{TlsTranscript, TranscriptCommitment}, }; use tlsn_deap::Deap; @@ -170,11 +169,36 @@ impl Verifier { self, socket: S, config: &AttestationConfig, + ) -> Result { + #[allow(deprecated)] + self.notarize_with_provider(socket, config, &CryptoProvider::default()) + .await + } + + /// Runs the verifier to completion and attests to the TLS session. + /// + /// This is a convenience method which runs all the steps needed for + /// notarization. + /// + /// # Arguments + /// + /// * `socket` - The socket to the prover. + /// * `config` - The attestation configuration. + /// * `provider` - Cryptography provider. + #[instrument(parent = &self.span, level = "info", skip_all, err)] + #[deprecated( + note = "attestation functionality will be removed from this API in future releases." + )] + pub async fn notarize_with_provider( + self, + socket: S, + config: &AttestationConfig, + provider: &CryptoProvider, ) -> Result { let mut verifier = self.setup(socket).await?.run().await?; #[allow(deprecated)] - let attestation = verifier.notarize(config).await?; + let attestation = verifier.notarize_with_provider(config, provider).await?; verifier.close().await?; @@ -342,10 +366,11 @@ impl Verifier { .poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from)) .await?; + let verifier = WebPkiVerifier::new(self.config.root_store().clone(), None); let server_name = if let Some((name, cert_data)) = server_identity { cert_data - .verify_with_provider( - self.config.crypto_provider(), + .verify( + &verifier, tls_transcript.time(), tls_transcript.server_ephemeral_key(), &name, @@ -459,6 +484,26 @@ impl Verifier { pub async fn notarize( &mut self, config: &AttestationConfig, + ) -> Result { + #[allow(deprecated)] + self.notarize_with_provider(config, &CryptoProvider::default()) + .await + } + + /// Attests to the TLS session. + /// + /// # Arguments + /// + /// * `config` - Attestation configuration. + /// * `provider` - Cryptography provider. + #[instrument(parent = &self.span, level = "info", skip_all, err)] + #[deprecated( + note = "attestation functionality will be removed from this API in future releases." + )] + pub async fn notarize_with_provider( + &mut self, + config: &AttestationConfig, + provider: &CryptoProvider, ) -> Result { let VerifierOutput { server_name, @@ -528,7 +573,7 @@ impl Verifier { .transcript_commitments(transcript_commitments); let attestation = builder - .build(self.config.crypto_provider()) + .build(provider) .map_err(VerifierError::attestation)?; mux_fut diff --git a/crates/tlsn/src/verifier/config.rs b/crates/tlsn/src/verifier/config.rs index 7ff24116f..97361fb88 100644 --- a/crates/tlsn/src/verifier/config.rs +++ b/crates/tlsn/src/verifier/config.rs @@ -1,11 +1,8 @@ -use std::{ - fmt::{Debug, Formatter, Result}, - sync::Arc, -}; +use std::fmt::{Debug, Formatter, Result}; use crate::config::{NetworkSetting, ProtocolConfig, ProtocolConfigValidator}; use mpc_tls::Config; -use tlsn_core::CryptoProvider; +use tls_core::anchors::{OwnedTrustAnchor, RootCertStore}; /// Configuration for the [`Verifier`](crate::tls::Verifier). #[allow(missing_docs)] @@ -13,9 +10,8 @@ use tlsn_core::CryptoProvider; #[builder(pattern = "owned")] pub struct VerifierConfig { protocol_config_validator: ProtocolConfigValidator, - /// Cryptography provider. - #[builder(default, setter(into))] - crypto_provider: Arc, + #[builder(default = "default_root_store()")] + root_store: RootCertStore, } impl Debug for VerifierConfig { @@ -37,9 +33,9 @@ impl VerifierConfig { &self.protocol_config_validator } - /// Returns the cryptography provider. - pub fn crypto_provider(&self) -> &CryptoProvider { - &self.crypto_provider + /// Returns the root certificate store. + pub fn root_store(&self) -> &RootCertStore { + &self.root_store } pub(crate) fn build_mpc_tls_config(&self, protocol_config: &ProtocolConfig) -> Config { @@ -65,3 +61,16 @@ impl VerifierConfig { builder.build().unwrap() } } + +fn default_root_store() -> RootCertStore { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject.as_ref(), + ta.subject_public_key_info.as_ref(), + ta.name_constraints.as_ref().map(|nc| nc.as_ref()), + ) + })); + + root_store +} diff --git a/crates/tlsn/tests/test.rs b/crates/tlsn/tests/test.rs index 2c2c51f2b..1987b6e0c 100644 --- a/crates/tlsn/tests/test.rs +++ b/crates/tlsn/tests/test.rs @@ -1,13 +1,9 @@ use futures::{AsyncReadExt, AsyncWriteExt}; -use tls_core::verify::WebPkiVerifier; use tlsn::{ config::{ProtocolConfig, ProtocolConfigValidator}, - prover::{Prover, ProverConfig}, - verifier::{Verifier, VerifierConfig}, -}; -use tlsn_core::{ - CryptoProvider, attestation::AttestationConfig, request::RequestConfig, - signing::SignatureAlgId, transcript::TranscriptCommitConfig, + prover::{ProveConfig, Prover, ProverConfig, TlsConfig}, + transcript::{TranscriptCommitConfig, TranscriptCommitment}, + verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig}, }; use tlsn_server_fixture::bind; use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN}; @@ -36,7 +32,6 @@ async fn test() { } #[instrument(skip(verifier_socket))] -#[allow(deprecated)] async fn prover(verifier_socket: T) { let (client_socket, server_socket) = tokio::io::duplex(2 << 16); @@ -47,14 +42,15 @@ async fn prover(verifier_soc .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; + let mut tls_config_builder = TlsConfig::builder(); + tls_config_builder.root_store(root_store); + + let tls_config = tls_config_builder.build().unwrap(); let prover = Prover::new( ProverConfig::builder() .server_name(SERVER_DOMAIN) + .tls_config(tls_config) .protocol_config( ProtocolConfig::builder() .max_sent_data(MAX_SENT_DATA) @@ -64,7 +60,6 @@ async fn prover(verifier_soc .build() .unwrap(), ) - .crypto_provider(provider) .build() .unwrap(), ) @@ -98,31 +93,28 @@ async fn prover(verifier_soc let transcript_commit = builder.build().unwrap(); - let mut builder = RequestConfig::builder(); + let mut builder = ProveConfig::builder(prover.transcript()); + + builder.server_identity(); + + builder.reveal_sent(&(0..10)).unwrap(); + builder.reveal_recv(&(0..10)).unwrap(); builder.transcript_commit(transcript_commit); let config = builder.build().unwrap(); - prover.notarize(&config).await.unwrap(); + prover.prove(&config).await.unwrap(); prover.close().await.unwrap(); } #[instrument(skip(socket))] -#[allow(deprecated)] async fn verifier(socket: T) { let mut root_store = tls_core::anchors::RootCertStore::empty(); root_store .add(&tls_core::key::Certificate(CA_CERT_DER.to_vec())) .unwrap(); - let mut provider = CryptoProvider { - cert: WebPkiVerifier::new(root_store, None), - ..Default::default() - }; - - provider.signer.set_secp256k1(&[1u8; 32]).unwrap(); - let config_validator = ProtocolConfigValidator::builder() .max_sent_data(MAX_SENT_DATA) .max_recv_data(MAX_RECV_DATA) @@ -131,16 +123,35 @@ async fn verifier(soc let verifier = Verifier::new( VerifierConfig::builder() + .root_store(root_store) .protocol_config_validator(config_validator) - .crypto_provider(provider) .build() .unwrap(), ); - let config = AttestationConfig::builder() - .supported_signature_algs(vec![SignatureAlgId::SECP256K1]) - .build() + let VerifierOutput { + server_name, + transcript, + transcript_commitments, + } = verifier + .verify(socket.compat(), &VerifyConfig::default()) + .await .unwrap(); - _ = verifier.notarize(socket.compat(), &config).await.unwrap(); + let transcript = transcript.unwrap(); + + assert_eq!(server_name.unwrap().as_str(), SERVER_DOMAIN); + assert!(!transcript.is_complete()); + assert_eq!( + transcript.sent_authed().iter_ranges().next().unwrap(), + 0..10 + ); + assert_eq!( + transcript.received_authed().iter_ranges().next().unwrap(), + 0..10 + ); + assert!(matches!( + transcript_commitments[0], + TranscriptCommitment::Encoding(_) + )); } diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index 35ad91668..3b4fd4171 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -14,7 +14,7 @@ pub mod verifier; pub use log::{LoggingConfig, LoggingLevel}; -use tlsn_core::{transcript::Direction, CryptoProvider}; +use tlsn::{attestation::CryptoProvider, transcript::Direction}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; diff --git a/crates/wasm/src/prover/mod.rs b/crates/wasm/src/prover/mod.rs index e403a052c..5aa333c85 100644 --- a/crates/wasm/src/prover/mod.rs +++ b/crates/wasm/src/prover/mod.rs @@ -7,8 +7,11 @@ use futures::TryFutureExt; use http_body_util::{BodyExt, Full}; use hyper::body::Bytes; use tls_client_async::TlsConnection; -use tlsn::prover::{state, ProveConfig, Prover}; -use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfigBuilder}; +use tlsn::{ + attestation::request::RequestConfig, + prover::{state, ProveConfig, Prover}, + transcript::TranscriptCommitConfigBuilder, +}; use tracing::info; use wasm_bindgen::{prelude::*, JsError}; use wasm_bindgen_futures::spawn_local; diff --git a/crates/wasm/src/types.rs b/crates/wasm/src/types.rs index 1210a0442..85c657930 100644 --- a/crates/wasm/src/types.rs +++ b/crates/wasm/src/types.rs @@ -4,7 +4,7 @@ use http_body_util::Full; use hyper::body::Bytes; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; -use tlsn_core::CryptoProvider; +use tlsn::attestation::CryptoProvider; use tsify_next::Tsify; use wasm_bindgen::prelude::*; @@ -80,11 +80,11 @@ pub enum TlsVersion { V1_3, } -impl From for TlsVersion { - fn from(value: tlsn_core::connection::TlsVersion) -> Self { +impl From for TlsVersion { + fn from(value: tlsn::connection::TlsVersion) -> Self { match value { - tlsn_core::connection::TlsVersion::V1_2 => Self::V1_2, - tlsn_core::connection::TlsVersion::V1_3 => Self::V1_3, + tlsn::connection::TlsVersion::V1_2 => Self::V1_2, + tlsn::connection::TlsVersion::V1_3 => Self::V1_3, } } } @@ -96,8 +96,8 @@ pub struct TranscriptLength { pub recv: usize, } -impl From for TranscriptLength { - fn from(value: tlsn_core::connection::TranscriptLength) -> Self { +impl From for TranscriptLength { + fn from(value: tlsn::connection::TranscriptLength) -> Self { Self { sent: value.sent as usize, recv: value.received as usize, @@ -113,8 +113,8 @@ pub struct ConnectionInfo { transcript_length: TranscriptLength, } -impl From for ConnectionInfo { - fn from(value: tlsn_core::connection::ConnectionInfo) -> Self { +impl From for ConnectionInfo { + fn from(value: tlsn::connection::ConnectionInfo) -> Self { Self { time: value.time, version: value.version.into(), @@ -130,8 +130,8 @@ pub struct Transcript { pub recv: Vec, } -impl From<&tlsn_core::transcript::Transcript> for Transcript { - fn from(value: &tlsn_core::transcript::Transcript) -> Self { +impl From<&tlsn::transcript::Transcript> for Transcript { + fn from(value: &tlsn::transcript::Transcript) -> Self { Self { sent: value.sent().to_vec(), recv: value.received().to_vec(), @@ -148,8 +148,8 @@ pub struct PartialTranscript { pub recv_authed: Vec>, } -impl From for PartialTranscript { - fn from(value: tlsn_core::transcript::PartialTranscript) -> Self { +impl From for PartialTranscript { + fn from(value: tlsn::transcript::PartialTranscript) -> Self { Self { sent: value.sent_unsafe().to_vec(), sent_authed: value.sent_authed().iter_ranges().collect(), @@ -183,7 +183,7 @@ pub enum KeyType { #[derive(Debug, Clone, Serialize, Deserialize)] #[wasm_bindgen] #[serde(transparent)] -pub struct Attestation(pub(crate) tlsn_core::attestation::Attestation); +pub struct Attestation(pub(crate) tlsn::attestation::Attestation); #[wasm_bindgen] impl Attestation { @@ -202,8 +202,8 @@ impl Attestation { } } -impl From for Attestation { - fn from(value: tlsn_core::attestation::Attestation) -> Self { +impl From for Attestation { + fn from(value: tlsn::attestation::Attestation) -> Self { Self(value) } } @@ -211,7 +211,7 @@ impl From for Attestation { #[derive(Debug, Clone, Serialize, Deserialize)] #[wasm_bindgen] #[serde(transparent)] -pub struct Secrets(pub(crate) tlsn_core::Secrets); +pub struct Secrets(pub(crate) tlsn::attestation::Secrets); #[wasm_bindgen] impl Secrets { @@ -231,8 +231,8 @@ impl Secrets { } } -impl From for Secrets { - fn from(value: tlsn_core::Secrets) -> Self { +impl From for Secrets { + fn from(value: tlsn::attestation::Secrets) -> Self { Self(value) } } @@ -240,7 +240,7 @@ impl From for Secrets { #[derive(Debug, Serialize, Deserialize)] #[wasm_bindgen] #[serde(transparent)] -pub struct Presentation(tlsn_core::presentation::Presentation); +pub struct Presentation(tlsn::attestation::presentation::Presentation); #[wasm_bindgen] impl Presentation { @@ -269,8 +269,8 @@ impl Presentation { } } -impl From for Presentation { - fn from(value: tlsn_core::presentation::Presentation) -> Self { +impl From for Presentation { + fn from(value: tlsn::attestation::presentation::Presentation) -> Self { Self(value) } } @@ -284,8 +284,8 @@ pub struct PresentationOutput { pub transcript: Option, } -impl From for PresentationOutput { - fn from(value: tlsn_core::presentation::PresentationOutput) -> Self { +impl From for PresentationOutput { + fn from(value: tlsn::attestation::presentation::PresentationOutput) -> Self { Self { attestation: value.attestation.into(), server_name: value.server_name.map(|name| name.as_str().to_string()), @@ -317,8 +317,8 @@ pub struct VerifyingKey { pub data: Vec, } -impl From<&tlsn_core::signing::VerifyingKey> for VerifyingKey { - fn from(value: &tlsn_core::signing::VerifyingKey) -> Self { +impl From<&tlsn::attestation::signing::VerifyingKey> for VerifyingKey { + fn from(value: &tlsn::attestation::signing::VerifyingKey) -> Self { Self { alg: value.alg.as_u8(), data: value.data.clone(),