mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 21:38:00 -05:00
refactor(core): decouple attestation from core api (#875)
* refactor(core): decouple attestation from core api * remove dead test * fix encoding tree test * clippy * fix comment
This commit is contained in:
@@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
use tls_core::verify::WebPkiVerifier;
|
||||
use tlsn_common::config::ProtocolConfigValidator;
|
||||
use tlsn_core::CryptoProvider;
|
||||
use tlsn_core::{CryptoProvider, VerifyConfig};
|
||||
use tlsn_server_fixture_certs::CA_CERT_DER;
|
||||
use tlsn_verifier::{Verifier, VerifierConfig};
|
||||
|
||||
@@ -100,7 +100,9 @@ async fn run_instance<S: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>
|
||||
.build()?,
|
||||
);
|
||||
|
||||
verifier.verify(io.compat()).await?;
|
||||
verifier
|
||||
.verify(io.compat(), &VerifyConfig::default())
|
||||
.await?;
|
||||
|
||||
println!("verifier done");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
|
||||
use tlsn_common::config::ProtocolConfig;
|
||||
use tlsn_core::{transcript::Idx, CryptoProvider};
|
||||
use tlsn_core::{CryptoProvider, ProveConfig};
|
||||
use tlsn_prover::{Prover, ProverConfig};
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
|
||||
@@ -111,15 +111,19 @@ pub async fn run_prover(
|
||||
Ok::<(), anyhow::Error>(())
|
||||
};
|
||||
|
||||
let (prover_task, _) = try_join(prover_fut.map_err(anyhow::Error::from), tls_fut).await?;
|
||||
|
||||
let mut prover = prover_task.start_prove();
|
||||
let (mut prover, _) = try_join(prover_fut.map_err(anyhow::Error::from), tls_fut).await?;
|
||||
|
||||
let (sent_len, recv_len) = prover.transcript().len();
|
||||
prover
|
||||
.prove_transcript(Idx::new(0..sent_len), Idx::new(0..recv_len))
|
||||
.await?;
|
||||
prover.finalize().await?;
|
||||
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
builder.reveal_sent(&(0..sent_len)).unwrap();
|
||||
builder.reveal_recv(&(0..recv_len)).unwrap();
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.prove(&config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ derive_builder = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
opaque-debug = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rangeset = { workspace = true }
|
||||
serio = { workspace = true, features = ["codec", "bincode"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -3,14 +3,27 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use mpz_common::Context;
|
||||
use mpz_core::Block;
|
||||
use mpz_memory_core::{
|
||||
binary::U8,
|
||||
correlated::{Delta, Key, Mac},
|
||||
Vector,
|
||||
};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use tlsn_core::transcript::{
|
||||
encoding::{new_encoder, Encoder, EncoderSecret, EncodingProvider, EncodingProviderError},
|
||||
Direction,
|
||||
use tlsn_core::{
|
||||
hash::HashAlgorithm,
|
||||
transcript::{
|
||||
encoding::{
|
||||
new_encoder, Encoder, EncoderSecret, EncodingCommitment, EncodingProvider,
|
||||
EncodingProviderError, EncodingTree, EncodingTreeError,
|
||||
},
|
||||
Direction, Idx,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::transcript::TranscriptRefs;
|
||||
|
||||
/// Bytes of encoding, per byte.
|
||||
const ENCODING_SIZE: usize = 128;
|
||||
|
||||
@@ -23,22 +36,29 @@ struct Encodings {
|
||||
/// Transfers the encodings using the provided seed and keys.
|
||||
///
|
||||
/// The keys must be consistent with the global delta used in the encodings.
|
||||
pub async fn transfer(
|
||||
pub async fn transfer<'a>(
|
||||
ctx: &mut Context,
|
||||
secret: &EncoderSecret,
|
||||
sent_keys: impl IntoIterator<Item = &'_ Block>,
|
||||
recv_keys: impl IntoIterator<Item = &'_ Block>,
|
||||
) -> Result<(), EncodingError> {
|
||||
let encoder = new_encoder(secret);
|
||||
refs: &TranscriptRefs,
|
||||
delta: &Delta,
|
||||
f: impl Fn(Vector<U8>) -> &'a [Key],
|
||||
) -> Result<EncodingCommitment, EncodingError> {
|
||||
let secret = EncoderSecret::new(rand::rng().random(), delta.as_block().to_bytes());
|
||||
let encoder = new_encoder(&secret);
|
||||
|
||||
let sent_keys: Vec<u8> = sent_keys
|
||||
.into_iter()
|
||||
.flat_map(|key| key.as_bytes())
|
||||
let sent_keys: Vec<u8> = refs
|
||||
.sent()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|key| key.as_block().as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
let recv_keys: Vec<u8> = recv_keys
|
||||
.into_iter()
|
||||
.flat_map(|key| key.as_bytes())
|
||||
let recv_keys: Vec<u8> = refs
|
||||
.recv()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|key| key.as_block().as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
@@ -75,26 +95,40 @@ pub async fn transfer(
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
let root = ctx.io_mut().expect_next().await?;
|
||||
ctx.io_mut().send(secret.clone()).await?;
|
||||
|
||||
Ok(EncodingCommitment {
|
||||
root,
|
||||
secret: secret.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Receives the encodings using the provided MACs.
|
||||
///
|
||||
/// The MACs must be consistent with the global delta used in the encodings.
|
||||
pub async fn receive(
|
||||
pub async fn receive<'a>(
|
||||
ctx: &mut Context,
|
||||
sent_macs: impl IntoIterator<Item = &'_ Block>,
|
||||
recv_macs: impl IntoIterator<Item = &'_ Block>,
|
||||
) -> Result<impl EncodingProvider, EncodingError> {
|
||||
hasher: &(dyn HashAlgorithm + Send + Sync),
|
||||
refs: &TranscriptRefs,
|
||||
f: impl Fn(Vector<U8>) -> &'a [Mac],
|
||||
idxs: impl IntoIterator<Item = &(Direction, Idx)>,
|
||||
) -> Result<(EncodingCommitment, EncodingTree), EncodingError> {
|
||||
let Encodings { mut sent, mut recv } = ctx.io_mut().expect_next().await?;
|
||||
|
||||
let sent_macs: Vec<u8> = sent_macs
|
||||
.into_iter()
|
||||
let sent_macs: Vec<u8> = refs
|
||||
.sent()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|mac| mac.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
let recv_macs: Vec<u8> = recv_macs
|
||||
.into_iter()
|
||||
let recv_macs: Vec<u8> = refs
|
||||
.recv()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|mac| mac.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
@@ -127,7 +161,17 @@ pub async fn receive(
|
||||
.zip(recv_macs)
|
||||
.for_each(|(enc, mac)| *enc ^= mac);
|
||||
|
||||
Ok(Provider { sent, recv })
|
||||
let provider = Provider { sent, recv };
|
||||
|
||||
let tree = EncodingTree::new(hasher, idxs, &provider)?;
|
||||
let root = tree.root();
|
||||
|
||||
ctx.io_mut().send(root.clone()).await?;
|
||||
let secret = ctx.io_mut().expect_next().await?;
|
||||
|
||||
let commitment = EncodingCommitment { root, secret };
|
||||
|
||||
Ok((commitment, tree))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -177,6 +221,8 @@ enum ErrorRepr {
|
||||
expected: usize,
|
||||
got: usize,
|
||||
},
|
||||
#[error("encoding tree error: {0}")]
|
||||
EncodingTree(EncodingTreeError),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for EncodingError {
|
||||
@@ -184,3 +230,9 @@ impl From<std::io::Error> for EncodingError {
|
||||
Self(ErrorRepr::Io(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncodingTreeError> for EncodingError {
|
||||
fn from(value: EncodingTreeError) -> Self {
|
||||
Self(ErrorRepr::EncodingTree(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
//! TLS transcript.
|
||||
|
||||
use mpz_memory_core::{binary::U8, Vector};
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
MemoryExt, Vector,
|
||||
};
|
||||
use mpz_vm_core::{Vm, VmError};
|
||||
use rangeset::Intersection;
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_core::transcript::{Direction, Idx, Transcript};
|
||||
use tlsn_core::transcript::{Direction, Idx, PartialTranscript, Transcript};
|
||||
|
||||
/// A transcript of sent and received TLS records.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
@@ -168,6 +172,69 @@ impl TranscriptRefs {
|
||||
#[error("not all application plaintext was committed to in the TLS transcript")]
|
||||
pub struct IncompleteTranscript {}
|
||||
|
||||
/// Decodes the transcript.
|
||||
pub fn decode_transcript(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
sent: &Idx,
|
||||
recv: &Idx,
|
||||
refs: &TranscriptRefs,
|
||||
) -> Result<(), VmError> {
|
||||
let sent_refs = refs.get(Direction::Sent, sent).expect("index is in bounds");
|
||||
let recv_refs = refs
|
||||
.get(Direction::Received, recv)
|
||||
.expect("index is in bounds");
|
||||
|
||||
for slice in sent_refs.into_iter().chain(recv_refs) {
|
||||
// Drop the future, we don't need it.
|
||||
drop(vm.decode(slice)?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies a partial transcript.
|
||||
pub fn verify_transcript(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
transcript: &PartialTranscript,
|
||||
refs: &TranscriptRefs,
|
||||
) -> Result<(), InconsistentTranscript> {
|
||||
let sent_refs = refs
|
||||
.get(Direction::Sent, transcript.sent_authed())
|
||||
.expect("index is in bounds");
|
||||
let recv_refs = refs
|
||||
.get(Direction::Received, transcript.received_authed())
|
||||
.expect("index is in bounds");
|
||||
|
||||
let mut authenticated_data = Vec::new();
|
||||
for data in sent_refs.into_iter().chain(recv_refs) {
|
||||
let plaintext = vm
|
||||
.get(data)
|
||||
.expect("reference is valid")
|
||||
.expect("plaintext is decoded");
|
||||
authenticated_data.extend_from_slice(&plaintext);
|
||||
}
|
||||
|
||||
let mut purported_data = Vec::with_capacity(authenticated_data.len());
|
||||
for range in transcript.sent_authed().iter_ranges() {
|
||||
purported_data.extend_from_slice(&transcript.sent_unsafe()[range]);
|
||||
}
|
||||
|
||||
for range in transcript.received_authed().iter_ranges() {
|
||||
purported_data.extend_from_slice(&transcript.received_unsafe()[range]);
|
||||
}
|
||||
|
||||
if purported_data != authenticated_data {
|
||||
return Err(InconsistentTranscript {});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Error for [`verify_transcript`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("inconsistent transcript")]
|
||||
pub struct InconsistentTranscript {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TranscriptRefs;
|
||||
|
||||
@@ -46,7 +46,7 @@ use crate::{
|
||||
merkle::MerkleTree,
|
||||
presentation::PresentationBuilder,
|
||||
signing::{Signature, VerifyingKey},
|
||||
transcript::encoding::EncodingCommitment,
|
||||
transcript::TranscriptCommitment,
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
@@ -150,8 +150,8 @@ pub struct Body {
|
||||
connection_info: Field<ConnectionInfo>,
|
||||
server_ephemeral_key: Field<ServerEphemKey>,
|
||||
cert_commitment: Field<ServerCertCommitment>,
|
||||
encoding_commitment: Option<Field<EncodingCommitment>>,
|
||||
extensions: Vec<Field<Extension>>,
|
||||
transcript_commitments: Vec<Field<TranscriptCommitment>>,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
@@ -195,8 +195,8 @@ impl Body {
|
||||
connection_info: conn_info,
|
||||
server_ephemeral_key,
|
||||
cert_commitment,
|
||||
encoding_commitment,
|
||||
extensions,
|
||||
transcript_commitments,
|
||||
} = self;
|
||||
|
||||
let mut fields: Vec<(FieldId, Hash)> = vec![
|
||||
@@ -212,14 +212,11 @@ impl Body {
|
||||
),
|
||||
];
|
||||
|
||||
if let Some(encoding_commitment) = encoding_commitment {
|
||||
fields.push((
|
||||
encoding_commitment.id,
|
||||
hasher.hash_separated(&encoding_commitment.data),
|
||||
));
|
||||
for field in extensions.iter() {
|
||||
fields.push((field.id, hasher.hash_separated(&field.data)));
|
||||
}
|
||||
|
||||
for field in extensions.iter() {
|
||||
for field in transcript_commitments.iter() {
|
||||
fields.push((field.id, hasher.hash_separated(&field.data)));
|
||||
}
|
||||
|
||||
@@ -242,9 +239,9 @@ impl Body {
|
||||
&self.cert_commitment.data
|
||||
}
|
||||
|
||||
/// Returns the encoding commitment.
|
||||
pub(crate) fn encoding_commitment(&self) -> Option<&EncodingCommitment> {
|
||||
self.encoding_commitment.as_ref().map(|field| &field.data)
|
||||
/// Returns the transcript commitments.
|
||||
pub(crate) fn transcript_commitments(&self) -> impl Iterator<Item = &TranscriptCommitment> {
|
||||
self.transcript_commitments.iter().map(|field| &field.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@ use rand::{rng, Rng};
|
||||
|
||||
use crate::{
|
||||
attestation::{
|
||||
Attestation, AttestationConfig, Body, EncodingCommitment, Extension, FieldId, FieldKind,
|
||||
Header, ServerCertCommitment, VERSION,
|
||||
Attestation, AttestationConfig, Body, Extension, FieldId, Header, ServerCertCommitment,
|
||||
VERSION,
|
||||
},
|
||||
connection::{ConnectionInfo, ServerEphemKey},
|
||||
hash::{HashAlgId, TypedHash},
|
||||
hash::HashAlgId,
|
||||
request::Request,
|
||||
serialize::CanonicalSerialize,
|
||||
signing::SignatureAlgId,
|
||||
transcript::encoding::EncoderSecret,
|
||||
transcript::TranscriptCommitment,
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
@@ -27,9 +27,8 @@ pub struct Sign {
|
||||
connection_info: Option<ConnectionInfo>,
|
||||
server_ephemeral_key: Option<ServerEphemKey>,
|
||||
cert_commitment: ServerCertCommitment,
|
||||
encoding_commitment_root: Option<TypedHash>,
|
||||
encoder_secret: Option<EncoderSecret>,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
}
|
||||
|
||||
/// An attestation builder.
|
||||
@@ -59,7 +58,6 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
signature_alg,
|
||||
hash_alg,
|
||||
server_cert_commitment: cert_commitment,
|
||||
encoding_commitment_root,
|
||||
extensions,
|
||||
} = request;
|
||||
|
||||
@@ -77,17 +75,6 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
));
|
||||
}
|
||||
|
||||
if encoding_commitment_root.is_some()
|
||||
&& !config
|
||||
.supported_fields()
|
||||
.contains(&FieldKind::EncodingCommitment)
|
||||
{
|
||||
return Err(AttestationBuilderError::new(
|
||||
ErrorKind::Request,
|
||||
"encoding commitment is not supported",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(validator) = config.extension_validator() {
|
||||
validator(&extensions)
|
||||
.map_err(|err| AttestationBuilderError::new(ErrorKind::Extension, err))?;
|
||||
@@ -101,8 +88,7 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
connection_info: None,
|
||||
server_ephemeral_key: None,
|
||||
cert_commitment,
|
||||
encoding_commitment_root,
|
||||
encoder_secret: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
extensions,
|
||||
},
|
||||
})
|
||||
@@ -122,18 +108,21 @@ impl AttestationBuilder<'_, Sign> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the encoder secret.
|
||||
pub fn encoder_secret(&mut self, secret: EncoderSecret) -> &mut Self {
|
||||
self.state.encoder_secret = Some(secret);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an extension to the attestation.
|
||||
pub fn extension(&mut self, extension: Extension) -> &mut Self {
|
||||
self.state.extensions.push(extension);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript commitments.
|
||||
pub fn transcript_commitments(
|
||||
&mut self,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
) -> &mut Self {
|
||||
self.state.transcript_commitments = transcript_commitments;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the attestation.
|
||||
pub fn build(self, provider: &CryptoProvider) -> Result<Attestation, AttestationBuilderError> {
|
||||
let Sign {
|
||||
@@ -142,9 +131,8 @@ impl AttestationBuilder<'_, Sign> {
|
||||
connection_info,
|
||||
server_ephemeral_key,
|
||||
cert_commitment,
|
||||
encoding_commitment_root,
|
||||
encoder_secret,
|
||||
extensions,
|
||||
transcript_commitments,
|
||||
} = self.state;
|
||||
|
||||
let hasher = provider.hash.get(&hash_alg).map_err(|_| {
|
||||
@@ -162,19 +150,6 @@ impl AttestationBuilder<'_, Sign> {
|
||||
)
|
||||
})?;
|
||||
|
||||
let encoding_commitment = if let Some(root) = encoding_commitment_root {
|
||||
let Some(secret) = encoder_secret else {
|
||||
return Err(AttestationBuilderError::new(
|
||||
ErrorKind::Field,
|
||||
"encoding commitment requested but encoder_secret was not set",
|
||||
));
|
||||
};
|
||||
|
||||
Some(EncodingCommitment { root, secret })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut field_id = FieldId::default();
|
||||
|
||||
let body = Body {
|
||||
@@ -186,11 +161,14 @@ impl AttestationBuilder<'_, Sign> {
|
||||
AttestationBuilderError::new(ErrorKind::Field, "handshake data was not set")
|
||||
})?),
|
||||
cert_commitment: field_id.next(cert_commitment),
|
||||
encoding_commitment: encoding_commitment.map(|commitment| field_id.next(commitment)),
|
||||
extensions: extensions
|
||||
.into_iter()
|
||||
.map(|extension| field_id.next(extension))
|
||||
.collect(),
|
||||
transcript_commitments: transcript_commitments
|
||||
.into_iter()
|
||||
.map(|commitment| field_id.next(commitment))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
@@ -269,9 +247,7 @@ mod test {
|
||||
|
||||
use crate::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
fixtures::{
|
||||
encoder_secret, encoding_provider, request_fixture, ConnectionFixture, RequestFixture,
|
||||
},
|
||||
fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
|
||||
hash::Blake3,
|
||||
transcript::Transcript,
|
||||
};
|
||||
@@ -346,36 +322,6 @@ mod test {
|
||||
assert!(err.is_request());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_accept_unsupported_encoding_commitment() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture { request, .. } = request_fixture(
|
||||
transcript,
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection,
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation_config = AttestationConfig::builder()
|
||||
.supported_signature_algs([SignatureAlgId::SECP256K1])
|
||||
.supported_fields([
|
||||
FieldKind::ConnectionInfo,
|
||||
FieldKind::ServerEphemKey,
|
||||
FieldKind::ServerIdentityCommitment,
|
||||
])
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let err = Attestation::builder(&attestation_config)
|
||||
.accept_request(request)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(err.is_request());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_sign_missing_signer(attestation_config: &AttestationConfig) {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
@@ -396,49 +342,10 @@ mod test {
|
||||
let mut provider = CryptoProvider::default();
|
||||
provider.signer.set_secp256r1(&[42u8; 32]).unwrap();
|
||||
|
||||
let err = attestation_builder.build(&provider).err().unwrap();
|
||||
let err = attestation_builder.build(&provider).unwrap_err();
|
||||
assert!(matches!(err.kind, ErrorKind::Config));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_sign_missing_encoding_seed(
|
||||
attestation_config: &AttestationConfig,
|
||||
crypto_provider: &CryptoProvider,
|
||||
) {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture { request, .. } = request_fixture(
|
||||
transcript,
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut attestation_builder = Attestation::builder(attestation_config)
|
||||
.accept_request(request)
|
||||
.unwrap();
|
||||
|
||||
let ConnectionFixture {
|
||||
connection_info,
|
||||
server_cert_data,
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake;
|
||||
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.server_ephemeral_key(server_ephemeral_key);
|
||||
|
||||
let err = attestation_builder.build(crypto_provider).err().unwrap();
|
||||
assert!(matches!(err.kind, ErrorKind::Field));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_sign_missing_server_ephemeral_key(
|
||||
attestation_config: &AttestationConfig,
|
||||
@@ -463,11 +370,9 @@ mod test {
|
||||
connection_info, ..
|
||||
} = connection;
|
||||
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.encoder_secret(encoder_secret());
|
||||
attestation_builder.connection_info(connection_info);
|
||||
|
||||
let err = attestation_builder.build(crypto_provider).err().unwrap();
|
||||
let err = attestation_builder.build(crypto_provider).unwrap_err();
|
||||
assert!(matches!(err.kind, ErrorKind::Field));
|
||||
}
|
||||
|
||||
@@ -500,11 +405,9 @@ mod test {
|
||||
..
|
||||
}) = server_cert_data.handshake;
|
||||
|
||||
attestation_builder
|
||||
.server_ephemeral_key(server_ephemeral_key)
|
||||
.encoder_secret(encoder_secret());
|
||||
attestation_builder.server_ephemeral_key(server_ephemeral_key);
|
||||
|
||||
let err = attestation_builder.build(crypto_provider).err().unwrap();
|
||||
let err = attestation_builder.build(crypto_provider).unwrap_err();
|
||||
assert!(matches!(err.kind, ErrorKind::Field));
|
||||
}
|
||||
|
||||
@@ -572,8 +475,7 @@ mod test {
|
||||
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.server_ephemeral_key(server_ephemeral_key)
|
||||
.encoder_secret(encoder_secret());
|
||||
.server_ephemeral_key(server_ephemeral_key);
|
||||
|
||||
let attestation = attestation_builder.build(crypto_provider).unwrap();
|
||||
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
attestation::{Extension, FieldKind, InvalidExtension},
|
||||
attestation::{Extension, InvalidExtension},
|
||||
hash::{HashAlgId, DEFAULT_SUPPORTED_HASH_ALGS},
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
|
||||
type ExtensionValidator = Arc<dyn Fn(&[Extension]) -> Result<(), InvalidExtension> + Send + Sync>;
|
||||
|
||||
const DEFAULT_SUPPORTED_FIELDS: &[FieldKind] = &[
|
||||
FieldKind::ConnectionInfo,
|
||||
FieldKind::ServerEphemKey,
|
||||
FieldKind::ServerIdentityCommitment,
|
||||
FieldKind::EncodingCommitment,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum ErrorKind {
|
||||
@@ -52,7 +45,6 @@ impl AttestationConfigError {
|
||||
pub struct AttestationConfig {
|
||||
supported_signature_algs: Vec<SignatureAlgId>,
|
||||
supported_hash_algs: Vec<HashAlgId>,
|
||||
supported_fields: Vec<FieldKind>,
|
||||
extension_validator: Option<ExtensionValidator>,
|
||||
}
|
||||
|
||||
@@ -70,10 +62,6 @@ impl AttestationConfig {
|
||||
&self.supported_hash_algs
|
||||
}
|
||||
|
||||
pub(crate) fn supported_fields(&self) -> &[FieldKind] {
|
||||
&self.supported_fields
|
||||
}
|
||||
|
||||
pub(crate) fn extension_validator(&self) -> Option<&ExtensionValidator> {
|
||||
self.extension_validator.as_ref()
|
||||
}
|
||||
@@ -84,7 +72,6 @@ impl Debug for AttestationConfig {
|
||||
f.debug_struct("AttestationConfig")
|
||||
.field("supported_signature_algs", &self.supported_signature_algs)
|
||||
.field("supported_hash_algs", &self.supported_hash_algs)
|
||||
.field("supported_fields", &self.supported_fields)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -93,7 +80,6 @@ impl Debug for AttestationConfig {
|
||||
pub struct AttestationConfigBuilder {
|
||||
supported_signature_algs: Vec<SignatureAlgId>,
|
||||
supported_hash_algs: Vec<HashAlgId>,
|
||||
supported_fields: Vec<FieldKind>,
|
||||
extension_validator: Option<ExtensionValidator>,
|
||||
}
|
||||
|
||||
@@ -102,7 +88,6 @@ impl Default for AttestationConfigBuilder {
|
||||
Self {
|
||||
supported_signature_algs: Vec::default(),
|
||||
supported_hash_algs: DEFAULT_SUPPORTED_HASH_ALGS.to_vec(),
|
||||
supported_fields: DEFAULT_SUPPORTED_FIELDS.to_vec(),
|
||||
extension_validator: Some(Arc::new(|e| {
|
||||
if !e.is_empty() {
|
||||
Err(InvalidExtension::new(
|
||||
@@ -135,12 +120,6 @@ impl AttestationConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the supported attestation fields.
|
||||
pub fn supported_fields(&mut self, supported_fields: impl Into<Vec<FieldKind>>) -> &mut Self {
|
||||
self.supported_fields = supported_fields.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the extension validator.
|
||||
///
|
||||
/// # Example
|
||||
@@ -169,7 +148,6 @@ impl AttestationConfigBuilder {
|
||||
Ok(AttestationConfig {
|
||||
supported_signature_algs: self.supported_signature_algs.clone(),
|
||||
supported_hash_algs: self.supported_hash_algs.clone(),
|
||||
supported_fields: self.supported_fields.clone(),
|
||||
extension_validator: self.extension_validator.clone(),
|
||||
})
|
||||
}
|
||||
@@ -180,7 +158,6 @@ impl Debug for AttestationConfigBuilder {
|
||||
f.debug_struct("AttestationConfigBuilder")
|
||||
.field("supported_signature_algs", &self.supported_signature_algs)
|
||||
.field("supported_hash_algs", &self.supported_hash_algs)
|
||||
.field("supported_fields", &self.supported_fields)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
signing::SignatureAlgId,
|
||||
transcript::{
|
||||
encoding::{EncoderSecret, EncodingProvider, EncodingTree},
|
||||
Transcript, TranscriptCommitConfigBuilder,
|
||||
Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
|
||||
},
|
||||
CryptoProvider,
|
||||
};
|
||||
@@ -195,7 +195,6 @@ pub fn request_fixture(
|
||||
&encoding_hasher,
|
||||
transcripts_commitment_config.iter_encoding(),
|
||||
&encodings_provider,
|
||||
&transcript.length(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -211,8 +210,7 @@ pub fn request_fixture(
|
||||
request_builder
|
||||
.server_name(server_name)
|
||||
.server_cert_data(server_cert_data)
|
||||
.transcript(transcript)
|
||||
.encoding_tree(encoding_tree.clone());
|
||||
.transcript(transcript);
|
||||
|
||||
let (request, _) = request_builder.build(&provider).unwrap();
|
||||
|
||||
@@ -227,7 +225,7 @@ pub fn attestation_fixture(
|
||||
request: Request,
|
||||
connection: ConnectionFixture,
|
||||
signature_alg: SignatureAlgId,
|
||||
secret: EncoderSecret,
|
||||
transcript_commitments: &[TranscriptCommitment],
|
||||
) -> Attestation {
|
||||
let ConnectionFixture {
|
||||
connection_info,
|
||||
@@ -259,7 +257,7 @@ pub fn attestation_fixture(
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.server_ephemeral_key(server_ephemeral_key)
|
||||
.encoder_secret(secret);
|
||||
.transcript_commitments(transcript_commitments.to_vec());
|
||||
|
||||
attestation_builder.build(&provider).unwrap()
|
||||
}
|
||||
|
||||
@@ -192,3 +192,222 @@ pub mod transcript;
|
||||
|
||||
pub use provider::CryptoProvider;
|
||||
pub use secrets::Secrets;
|
||||
|
||||
use rangeset::ToRangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertData, ServerName},
|
||||
transcript::{
|
||||
Direction, Idx, PartialTranscript, Transcript, TranscriptCommitConfig,
|
||||
TranscriptCommitRequest, TranscriptCommitment, TranscriptSecret,
|
||||
},
|
||||
};
|
||||
|
||||
/// Configuration to prove information to the verifier.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProveConfig {
|
||||
server_identity: bool,
|
||||
transcript: Option<PartialTranscript>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl ProveConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder(transcript: &Transcript) -> ProveConfigBuilder {
|
||||
ProveConfigBuilder::new(transcript)
|
||||
}
|
||||
|
||||
/// Returns `true` if the server identity is to be proven.
|
||||
pub fn server_identity(&self) -> bool {
|
||||
self.server_identity
|
||||
}
|
||||
|
||||
/// Returns the transcript to be proven.
|
||||
pub fn transcript(&self) -> Option<&PartialTranscript> {
|
||||
self.transcript.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the transcript commitment configuration.
|
||||
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
|
||||
self.transcript_commit.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`ProveConfig`].
|
||||
#[derive(Debug)]
|
||||
pub struct ProveConfigBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
server_identity: bool,
|
||||
reveal_sent: Idx,
|
||||
reveal_recv: Idx,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl<'a> ProveConfigBuilder<'a> {
|
||||
/// Creates a new builder.
|
||||
pub fn new(transcript: &'a Transcript) -> Self {
|
||||
Self {
|
||||
transcript,
|
||||
server_identity: false,
|
||||
reveal_sent: Idx::default(),
|
||||
reveal_recv: Idx::default(),
|
||||
transcript_commit: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Proves the server identity.
|
||||
pub fn server_identity(&mut self) -> &mut Self {
|
||||
self.server_identity = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures transcript commitments.
|
||||
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
|
||||
self.transcript_commit = Some(transcript_commit);
|
||||
self
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the transcript.
|
||||
pub fn reveal(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigBuilderError> {
|
||||
let idx = Idx::new(ranges.to_range_set());
|
||||
|
||||
if idx.end() > self.transcript.len_of_direction(direction) {
|
||||
return Err(ProveConfigBuilderError(
|
||||
ProveConfigBuilderErrorRepr::IndexOutOfBounds {
|
||||
direction,
|
||||
actual: idx.end(),
|
||||
len: self.transcript.len_of_direction(direction),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
match direction {
|
||||
Direction::Sent => self.reveal_sent.union_mut(&idx),
|
||||
Direction::Received => self.reveal_recv.union_mut(&idx),
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the sent data transcript.
|
||||
pub fn reveal_sent(
|
||||
&mut self,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigBuilderError> {
|
||||
self.reveal(Direction::Sent, ranges)
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the received data transcript.
|
||||
pub fn reveal_recv(
|
||||
&mut self,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigBuilderError> {
|
||||
self.reveal(Direction::Received, ranges)
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<ProveConfig, ProveConfigBuilderError> {
|
||||
let transcript = if !self.reveal_sent.is_empty() || !self.reveal_recv.is_empty() {
|
||||
Some(
|
||||
self.transcript
|
||||
.to_partial(self.reveal_sent, self.reveal_recv),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ProveConfig {
|
||||
server_identity: self.server_identity,
|
||||
transcript,
|
||||
transcript_commit: self.transcript_commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ProveConfigBuilder`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ProveConfigBuilderError(#[from] ProveConfigBuilderErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ProveConfigBuilderErrorRepr {
|
||||
#[error("range is out of bounds of the transcript ({direction}): {actual} > {len}")]
|
||||
IndexOutOfBounds {
|
||||
direction: Direction,
|
||||
actual: usize,
|
||||
len: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Configuration to verify information from the prover.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct VerifyConfig {}
|
||||
|
||||
impl VerifyConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> VerifyConfigBuilder {
|
||||
VerifyConfigBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`VerifyConfig`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VerifyConfigBuilder {}
|
||||
|
||||
impl VerifyConfigBuilder {
|
||||
/// Creates a new builder.
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<VerifyConfig, VerifyConfigBuilderError> {
|
||||
Ok(VerifyConfig {})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`VerifyConfigBuilder`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct VerifyConfigBuilderError(#[from] VerifyConfigBuilderErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum VerifyConfigBuilderErrorRepr {}
|
||||
|
||||
/// Payload sent to the verifier.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ProvePayload {
|
||||
/// Server identity data.
|
||||
pub server_identity: Option<(ServerName, ServerCertData)>,
|
||||
/// Transcript data.
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
/// Transcript commitment configuration.
|
||||
pub transcript_commit: Option<TranscriptCommitRequest>,
|
||||
}
|
||||
|
||||
/// Prover output.
|
||||
pub struct ProverOutput {
|
||||
/// Transcript commitments.
|
||||
pub transcript_commitments: Vec<TranscriptCommitment>,
|
||||
/// Transcript commitment secrets.
|
||||
pub transcript_secrets: Vec<TranscriptSecret>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(ProverOutput);
|
||||
|
||||
/// Verifier output.
|
||||
pub struct VerifierOutput {
|
||||
/// Server identity.
|
||||
pub server_name: Option<ServerName>,
|
||||
/// Transcript data.
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
/// Transcript commitments.
|
||||
pub transcript_commitments: Vec<TranscriptCommitment>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(VerifierOutput);
|
||||
|
||||
@@ -84,7 +84,13 @@ impl Presentation {
|
||||
.transpose()?;
|
||||
|
||||
let transcript = transcript
|
||||
.map(|transcript| transcript.verify_with_provider(provider, &attestation.body))
|
||||
.map(|transcript| {
|
||||
transcript.verify_with_provider(
|
||||
provider,
|
||||
&attestation.body.connection_info().transcript_length,
|
||||
attestation.body.transcript_commitments(),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let connection_info = attestation.body.connection_info().clone();
|
||||
|
||||
@@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
attestation::{Attestation, Extension},
|
||||
connection::ServerCertCommitment,
|
||||
hash::{HashAlgId, TypedHash},
|
||||
hash::HashAlgId,
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ pub struct Request {
|
||||
pub(crate) signature_alg: SignatureAlgId,
|
||||
pub(crate) hash_alg: HashAlgId,
|
||||
pub(crate) server_cert_commitment: ServerCertCommitment,
|
||||
pub(crate) encoding_commitment_root: Option<TypedHash>,
|
||||
pub(crate) extensions: Vec<Extension>,
|
||||
}
|
||||
|
||||
@@ -66,20 +65,6 @@ impl Request {
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(encoding_commitment_root) = &self.encoding_commitment_root {
|
||||
let Some(encoding_commitment) = attestation.body.encoding_commitment() else {
|
||||
return Err(InconsistentAttestation(
|
||||
"encoding commitment is missing".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
if &encoding_commitment.root != encoding_commitment_root {
|
||||
return Err(InconsistentAttestation(
|
||||
"encoding commitment root does not match".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: improve the O(M*N) complexity of this check.
|
||||
for extension in &self.extensions {
|
||||
if !attestation.body.extensions().any(|e| e == extension) {
|
||||
@@ -102,15 +87,13 @@ pub struct InconsistentAttestation(String);
|
||||
mod test {
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertOpening, TranscriptLength},
|
||||
fixtures::{
|
||||
attestation_fixture, encoder_secret, encoding_provider, request_fixture,
|
||||
ConnectionFixture, RequestFixture,
|
||||
attestation_fixture, encoding_provider, request_fixture, ConnectionFixture,
|
||||
RequestFixture,
|
||||
},
|
||||
hash::{Blake3, Hash, HashAlgId},
|
||||
hash::{Blake3, HashAlgId},
|
||||
signing::SignatureAlgId,
|
||||
transcript::Transcript,
|
||||
CryptoProvider,
|
||||
@@ -129,12 +112,8 @@ mod test {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
assert!(request.validate(&attestation).is_ok())
|
||||
}
|
||||
@@ -152,12 +131,8 @@ mod test {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
request.signature_alg = SignatureAlgId::SECP256R1;
|
||||
|
||||
@@ -178,12 +153,8 @@ mod test {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
request.hash_alg = HashAlgId::SHA256;
|
||||
|
||||
@@ -204,12 +175,8 @@ mod test {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
let ConnectionFixture {
|
||||
server_cert_data, ..
|
||||
@@ -226,33 +193,4 @@ mod test {
|
||||
let res = request.validate(&attestation);
|
||||
assert!(res.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_encoding_commitment_root() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture { mut request, .. } = request_fixture(
|
||||
transcript,
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
|
||||
request.encoding_commitment_root = Some(TypedHash {
|
||||
alg: HashAlgId::BLAKE3,
|
||||
value: Hash::default(),
|
||||
});
|
||||
|
||||
let res = request.validate(&attestation);
|
||||
assert!(res.is_err())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
connection::{ServerCertData, ServerCertOpening, ServerName},
|
||||
request::{Request, RequestConfig},
|
||||
secrets::Secrets,
|
||||
transcript::{encoding::EncodingTree, Transcript},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
@@ -11,8 +11,9 @@ pub struct RequestBuilder<'a> {
|
||||
config: &'a RequestConfig,
|
||||
server_name: Option<ServerName>,
|
||||
server_cert_data: Option<ServerCertData>,
|
||||
encoding_tree: Option<EncodingTree>,
|
||||
transcript: Option<Transcript>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
transcript_commitment_secrets: Vec<TranscriptSecret>,
|
||||
}
|
||||
|
||||
impl<'a> RequestBuilder<'a> {
|
||||
@@ -22,8 +23,9 @@ impl<'a> RequestBuilder<'a> {
|
||||
config,
|
||||
server_name: None,
|
||||
server_cert_data: None,
|
||||
encoding_tree: None,
|
||||
transcript: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
transcript_commitment_secrets: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,18 +41,23 @@ impl<'a> RequestBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tree to commit to the transcript encodings.
|
||||
pub fn encoding_tree(&mut self, tree: EncodingTree) -> &mut Self {
|
||||
self.encoding_tree = Some(tree);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript.
|
||||
pub fn transcript(&mut self, transcript: Transcript) -> &mut Self {
|
||||
self.transcript = Some(transcript);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript commitments.
|
||||
pub fn transcript_commitments(
|
||||
&mut self,
|
||||
secrets: Vec<TranscriptSecret>,
|
||||
commitments: Vec<TranscriptCommitment>,
|
||||
) -> &mut Self {
|
||||
self.transcript_commitment_secrets = secrets;
|
||||
self.transcript_commitments = commitments;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the attestation request and returns the corresponding secrets.
|
||||
pub fn build(
|
||||
self,
|
||||
@@ -60,8 +67,9 @@ impl<'a> RequestBuilder<'a> {
|
||||
config,
|
||||
server_name,
|
||||
server_cert_data,
|
||||
encoding_tree,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
transcript_commitment_secrets,
|
||||
} = self;
|
||||
|
||||
let signature_alg = *config.signature_alg();
|
||||
@@ -84,23 +92,21 @@ impl<'a> RequestBuilder<'a> {
|
||||
|
||||
let server_cert_commitment = server_cert_opening.commit(hasher);
|
||||
|
||||
let encoding_commitment_root = encoding_tree.as_ref().map(|tree| tree.root());
|
||||
|
||||
let extensions = config.extensions().to_vec();
|
||||
|
||||
let request = Request {
|
||||
signature_alg,
|
||||
hash_alg,
|
||||
server_cert_commitment,
|
||||
encoding_commitment_root,
|
||||
extensions,
|
||||
};
|
||||
|
||||
let secrets = Secrets {
|
||||
server_name,
|
||||
server_cert_opening,
|
||||
encoding_tree,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
transcript_commitment_secrets,
|
||||
};
|
||||
|
||||
Ok((request, secrets))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{attestation::Extension, hash::HashAlgId, signing::SignatureAlgId};
|
||||
use crate::{
|
||||
attestation::Extension, hash::HashAlgId, signing::SignatureAlgId,
|
||||
transcript::TranscriptCommitConfig,
|
||||
};
|
||||
|
||||
/// Request configuration.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -6,6 +9,7 @@ pub struct RequestConfig {
|
||||
signature_alg: SignatureAlgId,
|
||||
hash_alg: HashAlgId,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl Default for RequestConfig {
|
||||
@@ -34,6 +38,11 @@ impl RequestConfig {
|
||||
pub fn extensions(&self) -> &[Extension] {
|
||||
&self.extensions
|
||||
}
|
||||
|
||||
/// Returns the transcript commitment configuration.
|
||||
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
|
||||
self.transcript_commit.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`RequestConfig`].
|
||||
@@ -42,6 +51,7 @@ pub struct RequestConfigBuilder {
|
||||
signature_alg: SignatureAlgId,
|
||||
hash_alg: HashAlgId,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl Default for RequestConfigBuilder {
|
||||
@@ -50,6 +60,7 @@ impl Default for RequestConfigBuilder {
|
||||
signature_alg: SignatureAlgId::SECP256K1,
|
||||
hash_alg: HashAlgId::BLAKE3,
|
||||
extensions: Vec::new(),
|
||||
transcript_commit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,12 +84,19 @@ impl RequestConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript commitment configuration.
|
||||
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
|
||||
self.transcript_commit = Some(transcript_commit);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the config.
|
||||
pub fn build(self) -> Result<RequestConfig, RequestConfigBuilderError> {
|
||||
Ok(RequestConfig {
|
||||
signature_alg: self.signature_alg,
|
||||
hash_alg: self.hash_alg,
|
||||
extensions: self.extensions,
|
||||
transcript_commit: self.transcript_commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertOpening, ServerIdentityProof, ServerName},
|
||||
transcript::{encoding::EncodingTree, Transcript, TranscriptProofBuilder},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptProofBuilder, TranscriptSecret},
|
||||
};
|
||||
|
||||
/// Secret data of an [`Attestation`](crate::attestation::Attestation).
|
||||
@@ -10,8 +10,9 @@ use crate::{
|
||||
pub struct Secrets {
|
||||
pub(crate) server_name: ServerName,
|
||||
pub(crate) server_cert_opening: ServerCertOpening,
|
||||
pub(crate) encoding_tree: Option<EncodingTree>,
|
||||
pub(crate) transcript: Transcript,
|
||||
pub(crate) transcript_commitments: Vec<TranscriptCommitment>,
|
||||
pub(crate) transcript_commitment_secrets: Vec<TranscriptSecret>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Secrets);
|
||||
@@ -34,6 +35,17 @@ impl Secrets {
|
||||
|
||||
/// Returns a transcript proof builder.
|
||||
pub fn transcript_proof_builder(&self) -> TranscriptProofBuilder<'_> {
|
||||
TranscriptProofBuilder::new(&self.transcript, self.encoding_tree.as_ref())
|
||||
let encoding_secret = self
|
||||
.transcript_commitment_secrets
|
||||
.iter()
|
||||
.find_map(|secret| {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let TranscriptSecret::Encoding(secret) = secret {
|
||||
Some(secret)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
TranscriptProofBuilder::new(&self.transcript, encoding_secret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ use crate::connection::TranscriptLength;
|
||||
|
||||
pub use commit::{
|
||||
TranscriptCommitConfig, TranscriptCommitConfigBuilder, TranscriptCommitConfigBuilderError,
|
||||
TranscriptCommitmentKind,
|
||||
TranscriptCommitRequest, TranscriptCommitment, TranscriptCommitmentKind, TranscriptSecret,
|
||||
};
|
||||
pub use proof::{
|
||||
TranscriptProof, TranscriptProofBuilder, TranscriptProofBuilderError, TranscriptProofError,
|
||||
|
||||
@@ -6,8 +6,11 @@ use rangeset::ToRangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
hash::HashAlgId,
|
||||
transcript::{Direction, Idx, Transcript},
|
||||
hash::{impl_domain_separator, HashAlgId},
|
||||
transcript::{
|
||||
encoding::{EncodingCommitment, EncodingTree},
|
||||
Direction, Idx, Transcript,
|
||||
},
|
||||
};
|
||||
|
||||
/// The maximum allowed total bytelength of committed data for a single
|
||||
@@ -41,10 +44,31 @@ impl fmt::Display for TranscriptCommitmentKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transcript commitment.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum TranscriptCommitment {
|
||||
/// Encoding commitment.
|
||||
Encoding(EncodingCommitment),
|
||||
}
|
||||
|
||||
impl_domain_separator!(TranscriptCommitment);
|
||||
|
||||
/// Secret for a transcript commitment.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum TranscriptSecret {
|
||||
/// Encoding tree.
|
||||
Encoding(EncodingTree),
|
||||
}
|
||||
|
||||
impl_domain_separator!(TranscriptSecret);
|
||||
|
||||
/// Configuration for transcript commitments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TranscriptCommitConfig {
|
||||
encoding_hash_alg: HashAlgId,
|
||||
has_encoding: bool,
|
||||
commits: Vec<((Direction, Idx), TranscriptCommitmentKind)>,
|
||||
}
|
||||
|
||||
@@ -61,9 +85,7 @@ impl TranscriptCommitConfig {
|
||||
|
||||
/// Returns whether the configuration has any encoding commitments.
|
||||
pub fn has_encoding(&self) -> bool {
|
||||
self.commits
|
||||
.iter()
|
||||
.any(|(_, kind)| matches!(kind, TranscriptCommitmentKind::Encoding))
|
||||
self.has_encoding
|
||||
}
|
||||
|
||||
/// Returns an iterator over the encoding commitment indices.
|
||||
@@ -81,6 +103,13 @@ impl TranscriptCommitConfig {
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a request for the transcript commitments.
|
||||
pub fn to_request(&self) -> TranscriptCommitRequest {
|
||||
TranscriptCommitRequest {
|
||||
encoding: self.has_encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for [`TranscriptCommitConfig`].
|
||||
@@ -91,6 +120,7 @@ impl TranscriptCommitConfig {
|
||||
pub struct TranscriptCommitConfigBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
encoding_hash_alg: HashAlgId,
|
||||
has_encoding: bool,
|
||||
default_kind: TranscriptCommitmentKind,
|
||||
commits: HashSet<((Direction, Idx), TranscriptCommitmentKind)>,
|
||||
}
|
||||
@@ -101,6 +131,7 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
Self {
|
||||
transcript,
|
||||
encoding_hash_alg: HashAlgId::BLAKE3,
|
||||
has_encoding: false,
|
||||
default_kind: TranscriptCommitmentKind::Encoding,
|
||||
commits: HashSet::default(),
|
||||
}
|
||||
@@ -145,6 +176,10 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
));
|
||||
}
|
||||
|
||||
if let TranscriptCommitmentKind::Encoding = kind {
|
||||
self.has_encoding = true;
|
||||
}
|
||||
|
||||
self.commits.insert(((direction, idx), kind));
|
||||
|
||||
Ok(self)
|
||||
@@ -192,6 +227,7 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
pub fn build(self) -> Result<TranscriptCommitConfig, TranscriptCommitConfigBuilderError> {
|
||||
Ok(TranscriptCommitConfig {
|
||||
encoding_hash_alg: self.encoding_hash_alg,
|
||||
has_encoding: self.has_encoding,
|
||||
commits: Vec::from_iter(self.commits),
|
||||
})
|
||||
}
|
||||
@@ -235,6 +271,19 @@ impl fmt::Display for TranscriptCommitConfigBuilderError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request to compute transcript commitments.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TranscriptCommitRequest {
|
||||
encoding: bool,
|
||||
}
|
||||
|
||||
impl TranscriptCommitRequest {
|
||||
/// Returns `true` if an encoding commitment is requested.
|
||||
pub fn encoding(&self) -> bool {
|
||||
self.encoding
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -11,7 +11,7 @@ mod tree;
|
||||
pub use encoder::{new_encoder, Encoder, EncoderSecret};
|
||||
pub use proof::{EncodingProof, EncodingProofError};
|
||||
pub use provider::{EncodingProvider, EncodingProviderError};
|
||||
pub use tree::EncodingTree;
|
||||
pub use tree::{EncodingTree, EncodingTreeError};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -231,7 +231,6 @@ mod test {
|
||||
use tlsn_data_fixtures::http::{request::POST_JSON, response::OK_JSON};
|
||||
|
||||
use crate::{
|
||||
connection::TranscriptLength,
|
||||
fixtures::{encoder_secret, encoder_secret_tampered_seed, encoding_provider},
|
||||
hash::Blake3,
|
||||
transcript::{
|
||||
@@ -255,17 +254,7 @@ mod test {
|
||||
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len()));
|
||||
|
||||
let provider = encoding_provider(transcript.sent(), transcript.received());
|
||||
let transcript_length = TranscriptLength {
|
||||
sent: transcript.sent().len() as u32,
|
||||
received: transcript.received().len() as u32,
|
||||
};
|
||||
let tree = EncodingTree::new(
|
||||
&Blake3::default(),
|
||||
[&idx_0, &idx_1],
|
||||
&provider,
|
||||
&transcript_length,
|
||||
)
|
||||
.unwrap();
|
||||
let tree = EncodingTree::new(&Blake3::default(), [&idx_0, &idx_1], &provider).unwrap();
|
||||
|
||||
let proof = tree.proof([&idx_0, &idx_1].into_iter()).unwrap();
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use bimap::BiMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::TranscriptLength,
|
||||
hash::{Blinder, HashAlgId, HashAlgorithm, TypedHash},
|
||||
merkle::MerkleTree,
|
||||
transcript::{
|
||||
@@ -67,12 +66,10 @@ impl EncodingTree {
|
||||
/// * `hasher` - The hash algorithm to use.
|
||||
/// * `idxs` - The subsequence indices to commit to.
|
||||
/// * `provider` - The encoding provider.
|
||||
/// * `transcript_length` - The length of the transcript.
|
||||
pub fn new<'idx>(
|
||||
hasher: &dyn HashAlgorithm,
|
||||
idxs: impl IntoIterator<Item = &'idx (Direction, Idx)>,
|
||||
provider: &dyn EncodingProvider,
|
||||
transcript_length: &TranscriptLength,
|
||||
) -> Result<Self, EncodingTreeError> {
|
||||
let mut this = Self {
|
||||
tree: MerkleTree::new(hasher.id()),
|
||||
@@ -93,18 +90,6 @@ impl EncodingTree {
|
||||
continue;
|
||||
}
|
||||
|
||||
let len = match direction {
|
||||
Direction::Sent => transcript_length.sent as usize,
|
||||
Direction::Received => transcript_length.received as usize,
|
||||
};
|
||||
|
||||
if idx.end() > len {
|
||||
return Err(EncodingTreeError::OutOfBounds {
|
||||
index: idx.clone(),
|
||||
transcript_length: len,
|
||||
});
|
||||
}
|
||||
|
||||
if this.idxs.contains_right(dir_idx) {
|
||||
// The subsequence is already in the tree.
|
||||
continue;
|
||||
@@ -219,11 +204,8 @@ mod tests {
|
||||
idxs: impl Iterator<Item = &'seq (Direction, Idx)>,
|
||||
) -> Result<EncodingTree, EncodingTreeError> {
|
||||
let provider = encoding_provider(transcript.sent(), transcript.received());
|
||||
let transcript_length = TranscriptLength {
|
||||
sent: transcript.sent().len() as u32,
|
||||
received: transcript.received().len() as u32,
|
||||
};
|
||||
EncodingTree::new(&Blake3::default(), idxs, &provider, &transcript_length)
|
||||
|
||||
EncodingTree::new(&Blake3::default(), idxs, &provider)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -328,25 +310,20 @@ mod tests {
|
||||
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len() + 1));
|
||||
|
||||
let result = new_tree(&transcript, [&idx_0].into_iter()).unwrap_err();
|
||||
assert!(matches!(result, EncodingTreeError::OutOfBounds { .. }));
|
||||
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));
|
||||
|
||||
let result = new_tree(&transcript, [&idx_1].into_iter()).unwrap_err();
|
||||
assert!(matches!(result, EncodingTreeError::OutOfBounds { .. }));
|
||||
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encoding_tree_missing_encoding() {
|
||||
let provider = encoding_provider(&[], &[]);
|
||||
let transcript_length = TranscriptLength {
|
||||
sent: 8,
|
||||
received: 8,
|
||||
};
|
||||
|
||||
let result = EncodingTree::new(
|
||||
&Blake3::default(),
|
||||
[(Direction::Sent, Idx::new(0..8))].iter(),
|
||||
&provider,
|
||||
&transcript_length,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));
|
||||
|
||||
@@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, fmt};
|
||||
|
||||
use crate::{
|
||||
attestation::Body,
|
||||
connection::TranscriptLength,
|
||||
transcript::{
|
||||
commit::TranscriptCommitmentKind,
|
||||
commit::{TranscriptCommitment, TranscriptCommitmentKind},
|
||||
encoding::{EncodingProof, EncodingProofError, EncodingTree},
|
||||
Direction, Idx, PartialTranscript, Transcript,
|
||||
},
|
||||
@@ -36,15 +36,16 @@ impl TranscriptProof {
|
||||
///
|
||||
/// * `provider` - The crypto provider to use for verification.
|
||||
/// * `attestation_body` - The attestation body to verify against.
|
||||
pub fn verify_with_provider(
|
||||
pub fn verify_with_provider<'a>(
|
||||
self,
|
||||
provider: &CryptoProvider,
|
||||
attestation_body: &Body,
|
||||
length: &TranscriptLength,
|
||||
commitments: impl IntoIterator<Item = &'a TranscriptCommitment>,
|
||||
) -> Result<PartialTranscript, TranscriptProofError> {
|
||||
let info = attestation_body.connection_info();
|
||||
let commitments: Vec<_> = commitments.into_iter().collect();
|
||||
|
||||
if self.transcript.sent_unsafe().len() != info.transcript_length.sent as usize
|
||||
|| self.transcript.received_unsafe().len() != info.transcript_length.received as usize
|
||||
if self.transcript.sent_unsafe().len() != length.sent as usize
|
||||
|| self.transcript.received_unsafe().len() != length.received as usize
|
||||
{
|
||||
return Err(TranscriptProofError::new(
|
||||
ErrorKind::Proof,
|
||||
@@ -57,12 +58,23 @@ impl TranscriptProof {
|
||||
|
||||
// Verify encoding proof.
|
||||
if let Some(proof) = self.encoding_proof {
|
||||
let commitment = attestation_body.encoding_commitment().ok_or_else(|| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
"contains an encoding proof but attestation is missing encoding commitment",
|
||||
)
|
||||
})?;
|
||||
let commitment = commitments
|
||||
.iter()
|
||||
.find_map(|commitment| {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let TranscriptCommitment::Encoding(encoding) = commitment {
|
||||
Some(encoding)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
"contains an encoding proof but attestation is missing encoding commitment",
|
||||
)
|
||||
})?;
|
||||
|
||||
let (auth_sent, auth_recv) = proof.verify_with_provider(
|
||||
provider,
|
||||
commitment,
|
||||
@@ -456,12 +468,8 @@ mod tests {
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
use crate::{
|
||||
fixtures::{
|
||||
attestation_fixture, encoder_secret, encoding_provider, request_fixture,
|
||||
ConnectionFixture, RequestFixture,
|
||||
},
|
||||
fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
|
||||
hash::{Blake3, HashAlgId},
|
||||
signing::SignatureAlgId,
|
||||
transcript::TranscriptCommitConfigBuilder,
|
||||
};
|
||||
|
||||
@@ -472,10 +480,7 @@ mod tests {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture {
|
||||
mut request,
|
||||
encoding_tree,
|
||||
} = request_fixture(
|
||||
let RequestFixture { encoding_tree, .. } = request_fixture(
|
||||
transcript.clone(),
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
@@ -489,19 +494,12 @@ mod tests {
|
||||
|
||||
let transcript_proof = builder.build().unwrap();
|
||||
|
||||
request.encoding_commitment_root = None;
|
||||
let attestation = attestation_fixture(
|
||||
request,
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
|
||||
let provider = CryptoProvider::default();
|
||||
let err = transcript_proof
|
||||
.verify_with_provider(&provider, &attestation.body)
|
||||
.verify_with_provider(&provider, &transcript.length(), &[])
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(err.kind, ErrorKind::Encoding));
|
||||
}
|
||||
|
||||
@@ -620,7 +618,6 @@ mod tests {
|
||||
&Blake3::default(),
|
||||
transcripts_commitment_config.iter_encoding(),
|
||||
&encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
&transcript.length(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -693,7 +690,6 @@ mod tests {
|
||||
&Blake3::default(),
|
||||
transcripts_commitment_config.iter_encoding(),
|
||||
&encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
&transcript.length(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -6,7 +6,11 @@ use tlsn_core::{
|
||||
presentation::PresentationOutput,
|
||||
request::{Request, RequestConfig},
|
||||
signing::SignatureAlgId,
|
||||
transcript::{encoding::EncodingTree, Direction, Transcript, TranscriptCommitConfigBuilder},
|
||||
transcript::{
|
||||
encoding::{EncodingCommitment, EncodingTree},
|
||||
Direction, Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
|
||||
TranscriptSecret,
|
||||
},
|
||||
CryptoProvider,
|
||||
};
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
@@ -54,10 +58,14 @@ fn test_api() {
|
||||
&Blake3::default(),
|
||||
transcripts_commitment_config.iter_encoding(),
|
||||
&encodings_provider,
|
||||
&transcript.length(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoding_commitment = EncodingCommitment {
|
||||
root: encoding_tree.root(),
|
||||
secret: encoder_secret(),
|
||||
};
|
||||
|
||||
let request_config = RequestConfig::default();
|
||||
let mut request_builder = Request::builder(&request_config);
|
||||
|
||||
@@ -65,7 +73,10 @@ fn test_api() {
|
||||
.server_name(server_name.clone())
|
||||
.server_cert_data(server_cert_data)
|
||||
.transcript(transcript)
|
||||
.encoding_tree(encoding_tree);
|
||||
.transcript_commitments(
|
||||
vec![TranscriptSecret::Encoding(encoding_tree)],
|
||||
vec![TranscriptCommitment::Encoding(encoding_commitment.clone())],
|
||||
);
|
||||
|
||||
let (request, secrets) = request_builder.build(&provider).unwrap();
|
||||
|
||||
@@ -84,7 +95,7 @@ fn test_api() {
|
||||
.connection_info(connection_info.clone())
|
||||
// Server key Notary received during handshake
|
||||
.server_ephemeral_key(server_ephemeral_key)
|
||||
.encoder_secret(encoder_secret());
|
||||
.transcript_commitments(vec![TranscriptCommitment::Encoding(encoding_commitment)]);
|
||||
|
||||
let attestation = attestation_builder.build(&provider).unwrap();
|
||||
|
||||
|
||||
@@ -171,10 +171,7 @@ async fn notarize(
|
||||
assert!(response.status() == StatusCode::OK);
|
||||
|
||||
// The prover task should be done now, so we can await it.
|
||||
let prover = prover_task.await??;
|
||||
|
||||
// Prepare for notarization.
|
||||
let mut prover = prover.start_notarize();
|
||||
let mut prover = prover_task.await??;
|
||||
|
||||
// Parse the HTTP transcript.
|
||||
let transcript = HttpTranscript::parse(prover.transcript())?;
|
||||
@@ -201,10 +198,12 @@ async fn notarize(
|
||||
// for other strategies that can be used to generate commitments.
|
||||
DefaultHttpCommitter::default().commit_transcript(&mut builder, &transcript)?;
|
||||
|
||||
prover.transcript_commit(builder.build()?);
|
||||
let transcript_commit = builder.build()?;
|
||||
|
||||
// Build an attestation request.
|
||||
let builder = RequestConfig::builder();
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
builder.transcript_commit(transcript_commit);
|
||||
|
||||
// Optionally, add an extension to the attestation if the notary supports it.
|
||||
// builder.extension(Extension {
|
||||
@@ -214,7 +213,8 @@ async fn notarize(
|
||||
|
||||
let request_config = builder.build()?;
|
||||
|
||||
let (attestation, secrets) = prover.finalize(&request_config).await?;
|
||||
#[allow(deprecated)]
|
||||
let (attestation, secrets) = prover.notarize(&request_config).await?;
|
||||
|
||||
println!("Notarization complete!");
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ use tracing::instrument;
|
||||
use tls_core::verify::WebPkiVerifier;
|
||||
use tls_server_fixture::CA_CERT_DER;
|
||||
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
|
||||
use tlsn_core::{transcript::Idx, CryptoProvider};
|
||||
use tlsn_prover::{state::Prove, Prover, ProverConfig};
|
||||
use tlsn_core::{
|
||||
transcript::PartialTranscript, CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig,
|
||||
};
|
||||
use tlsn_prover::{Prover, ProverConfig};
|
||||
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
|
||||
use tlsn_server_fixture_certs::SERVER_DOMAIN;
|
||||
use tlsn_verifier::{SessionInfo, Verifier, VerifierConfig};
|
||||
use tlsn_verifier::{Verifier, VerifierConfig};
|
||||
|
||||
const SECRET: &str = "TLSNotary's private key 🤡";
|
||||
|
||||
@@ -45,13 +47,16 @@ async fn main() {
|
||||
let (prover_socket, verifier_socket) = tokio::io::duplex(1 << 23);
|
||||
let prover = prover(prover_socket, &server_addr, &uri);
|
||||
let verifier = verifier(verifier_socket);
|
||||
let (_, (sent, received, _session_info)) = tokio::join!(prover, verifier);
|
||||
let (_, transcript) = tokio::join!(prover, verifier);
|
||||
|
||||
println!("Successfully verified {}", &uri);
|
||||
println!("Verified sent data:\n{}", bytes_to_redacted_string(&sent));
|
||||
println!(
|
||||
"Verified sent data:\n{}",
|
||||
bytes_to_redacted_string(transcript.sent_unsafe())
|
||||
);
|
||||
println!(
|
||||
"Verified received data:\n{}",
|
||||
bytes_to_redacted_string(&received)
|
||||
bytes_to_redacted_string(transcript.received_unsafe())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,21 +141,51 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
assert!(response.status() == StatusCode::OK);
|
||||
|
||||
// Create proof for the Verifier.
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
|
||||
// Reveal parts of the transcript.
|
||||
let idx_sent = revealed_ranges_sent(&mut prover);
|
||||
let idx_recv = revealed_ranges_received(&mut prover);
|
||||
prover.prove_transcript(idx_sent, idx_recv).await.unwrap();
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
// Finalize.
|
||||
prover.finalize().await.unwrap()
|
||||
// Reveal the DNS name.
|
||||
builder.server_identity();
|
||||
|
||||
// Find the secret in the request.
|
||||
let pos = prover
|
||||
.transcript()
|
||||
.sent()
|
||||
.windows(SECRET.len())
|
||||
.position(|w| w == SECRET.as_bytes())
|
||||
.expect("the secret should be in the sent data");
|
||||
|
||||
// Reveal everything except for the secret.
|
||||
builder.reveal_sent(&(0..pos)).unwrap();
|
||||
builder
|
||||
.reveal_sent(&(pos + SECRET.len()..prover.transcript().sent().len()))
|
||||
.unwrap();
|
||||
|
||||
// Find the substring "Dick".
|
||||
let pos = prover
|
||||
.transcript()
|
||||
.received()
|
||||
.windows(4)
|
||||
.position(|w| w == b"Dick")
|
||||
.expect("the substring 'Dick' should be in the received data");
|
||||
|
||||
// Reveal everything except for the substring.
|
||||
builder.reveal_recv(&(0..pos)).unwrap();
|
||||
builder
|
||||
.reveal_recv(&(pos + 4..prover.transcript().received().len()))
|
||||
.unwrap();
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.prove(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
}
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: T,
|
||||
) -> (Vec<u8>, Vec<u8>, SessionInfo) {
|
||||
) -> PartialTranscript {
|
||||
// Set up Verifier.
|
||||
let config_validator = ProtocolConfigValidator::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
@@ -179,60 +214,37 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
.unwrap();
|
||||
let verifier = Verifier::new(verifier_config);
|
||||
|
||||
// Verify MPC-TLS and wait for (redacted) data.
|
||||
let (mut partial_transcript, session_info) = verifier.verify(socket.compat()).await.unwrap();
|
||||
partial_transcript.set_unauthed(0);
|
||||
// Receive authenticated data.
|
||||
let VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
..
|
||||
} = verifier
|
||||
.verify(socket.compat(), &VerifyConfig::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server_name = server_name.expect("prover should have revealed server name");
|
||||
let transcript = transcript.expect("prover should have revealed transcript data");
|
||||
|
||||
// Check sent data.
|
||||
let sent = partial_transcript.sent_unsafe().to_vec();
|
||||
let sent = transcript.sent_unsafe().to_vec();
|
||||
let sent_data = String::from_utf8(sent.clone()).expect("Verifier expected sent data");
|
||||
sent_data
|
||||
.find(SERVER_DOMAIN)
|
||||
.unwrap_or_else(|| panic!("Verification failed: Expected host {}", SERVER_DOMAIN));
|
||||
|
||||
// Check received data.
|
||||
let received = partial_transcript.received_unsafe().to_vec();
|
||||
let received = transcript.received_unsafe().to_vec();
|
||||
let response = String::from_utf8(received.clone()).expect("Verifier expected received data");
|
||||
response
|
||||
.find("Herman Melville")
|
||||
.unwrap_or_else(|| panic!("Expected valid data from {}", SERVER_DOMAIN));
|
||||
|
||||
// Check Session info: server name.
|
||||
assert_eq!(session_info.server_name.as_str(), SERVER_DOMAIN);
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
|
||||
(sent, received, session_info)
|
||||
}
|
||||
|
||||
/// Returns the received ranges to be revealed to the verifier.
|
||||
fn revealed_ranges_received(prover: &mut Prover<Prove>) -> Idx {
|
||||
let recv_transcript = prover.transcript().received();
|
||||
let recv_transcript_len = recv_transcript.len();
|
||||
|
||||
// Get the received data as a string.
|
||||
let received_string = String::from_utf8(recv_transcript.to_vec()).unwrap();
|
||||
// Find the substring "illustrative".
|
||||
let start = received_string
|
||||
.find("Dick")
|
||||
.expect("Error: The substring 'Dick' was not found in the received data.");
|
||||
let end = start + "Dick".len();
|
||||
|
||||
Idx::new([0..start, end..recv_transcript_len])
|
||||
}
|
||||
|
||||
/// Returns the sent ranges to be revealed to the verifier.
|
||||
fn revealed_ranges_sent(prover: &mut Prover<Prove>) -> Idx {
|
||||
let sent_transcript = prover.transcript().sent();
|
||||
let sent_transcript_len = sent_transcript.len();
|
||||
|
||||
let sent_string = String::from_utf8(sent_transcript.to_vec()).unwrap();
|
||||
|
||||
let secret_start = sent_string.find(SECRET).unwrap();
|
||||
|
||||
// Reveal everything except for the SECRET.
|
||||
Idx::new([
|
||||
0..secret_start,
|
||||
secret_start + SECRET.len()..sent_transcript_len,
|
||||
])
|
||||
transcript
|
||||
}
|
||||
|
||||
/// Render redacted bytes as `🙈`.
|
||||
|
||||
@@ -223,6 +223,7 @@ pub async fn notary_service<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
.crypto_provider(crypto_provider)
|
||||
.build()?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
timeout(
|
||||
Duration::from_secs(notary_globals.notarization_config.timeout),
|
||||
Verifier::new(config).notarize(socket.compat(), &att_config),
|
||||
|
||||
@@ -250,7 +250,7 @@ async fn test_tcp_prover<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
|
||||
server_task.await.unwrap().unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
|
||||
let (sent_len, recv_len) = prover.transcript().len();
|
||||
|
||||
@@ -259,13 +259,17 @@ async fn test_tcp_prover<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
builder.commit_sent(&(0..sent_len)).unwrap();
|
||||
builder.commit_recv(&(0..recv_len)).unwrap();
|
||||
|
||||
let commit_config = builder.build().unwrap();
|
||||
let transcript_commit = builder.build().unwrap();
|
||||
|
||||
prover.transcript_commit(commit_config);
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
let request = RequestConfig::builder().build().unwrap();
|
||||
builder.transcript_commit(transcript_commit);
|
||||
|
||||
_ = prover.finalize(&request).await.unwrap();
|
||||
let request = builder.build().unwrap();
|
||||
|
||||
#[allow(deprecated)]
|
||||
prover.notarize(&request).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
|
||||
debug!("Done notarization!");
|
||||
}
|
||||
@@ -439,7 +443,7 @@ async fn test_websocket_prover() {
|
||||
|
||||
server_task.await.unwrap().unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
|
||||
let (sent_len, recv_len) = prover.transcript().len();
|
||||
|
||||
@@ -448,14 +452,17 @@ async fn test_websocket_prover() {
|
||||
builder.commit_sent(&(0..sent_len)).unwrap();
|
||||
builder.commit_recv(&(0..recv_len)).unwrap();
|
||||
|
||||
let commit_config = builder.build().unwrap();
|
||||
let transcript_commit = builder.build().unwrap();
|
||||
|
||||
prover.transcript_commit(commit_config);
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
let request = RequestConfig::builder().build().unwrap();
|
||||
builder.transcript_commit(transcript_commit);
|
||||
|
||||
_ = prover.finalize(&request).await.unwrap();
|
||||
let request = builder.build().unwrap();
|
||||
|
||||
#[allow(deprecated)]
|
||||
prover.notarize(&request).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
debug!("Done notarization!");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ use std::pin::Pin;
|
||||
/// Prover future which must be polled for the TLS connection to make progress.
|
||||
pub struct ProverFuture {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fut:
|
||||
Pin<Box<dyn Future<Output = Result<Prover<state::Closed>, ProverError>> + Send + 'static>>,
|
||||
pub(crate) fut: Pin<
|
||||
Box<dyn Future<Output = Result<Prover<state::Committed>, ProverError>> + Send + 'static>,
|
||||
>,
|
||||
pub(crate) ctrl: ProverControl,
|
||||
}
|
||||
|
||||
@@ -20,7 +21,7 @@ impl ProverFuture {
|
||||
}
|
||||
|
||||
impl Future for ProverFuture {
|
||||
type Output = Result<Prover<state::Closed>, ProverError>;
|
||||
type Output = Result<Prover<state::Committed>, ProverError>;
|
||||
|
||||
fn poll(
|
||||
mut self: Pin<&mut Self>,
|
||||
|
||||
@@ -7,22 +7,22 @@
|
||||
mod config;
|
||||
mod error;
|
||||
mod future;
|
||||
mod notarize;
|
||||
mod prove;
|
||||
pub mod state;
|
||||
|
||||
pub use config::{ProverConfig, ProverConfigBuilder, ProverConfigBuilderError};
|
||||
pub use error::ProverError;
|
||||
pub use future::ProverFuture;
|
||||
pub use tlsn_core::{ProveConfig, ProveConfigBuilder, ProveConfigBuilderError, ProverOutput};
|
||||
|
||||
use mpz_common::Context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble_core::Delta;
|
||||
use state::{Notarize, Prove};
|
||||
use mpz_vm_core::prelude::*;
|
||||
|
||||
use futures::{AsyncRead, AsyncWrite, TryFutureExt};
|
||||
use mpc_tls::{LeaderCtrl, MpcTlsLeader, SessionKeys};
|
||||
use rand::Rng;
|
||||
use serio::SinkExt;
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use std::sync::Arc;
|
||||
use tls_client::{ClientConnection, ServerName as TlsServerName};
|
||||
use tls_client_async::{bind_client, TlsConnection};
|
||||
@@ -30,17 +30,21 @@ use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_common::{
|
||||
commit::commit_records,
|
||||
context::build_mt_context,
|
||||
encoding,
|
||||
mux::attach_mux,
|
||||
transcript::{Record, TlsTranscript},
|
||||
transcript::{decode_transcript, Record, TlsTranscript},
|
||||
zk_aes::ZkAesCtr,
|
||||
Role,
|
||||
};
|
||||
use tlsn_core::{
|
||||
attestation::Attestation,
|
||||
connection::{
|
||||
ConnectionInfo, HandshakeData, HandshakeDataV1_2, ServerCertData, ServerSignature,
|
||||
TranscriptLength,
|
||||
},
|
||||
transcript::Transcript,
|
||||
request::{Request, RequestConfig},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
ProvePayload, Secrets,
|
||||
};
|
||||
use tlsn_deap::Deap;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -62,7 +66,7 @@ pub(crate) type Zk = mpz_zk::Prover<RCOTReceiver>;
|
||||
|
||||
/// A prover instance.
|
||||
#[derive(Debug)]
|
||||
pub struct Prover<T: state::ProverState> {
|
||||
pub struct Prover<T: state::ProverState = state::Initialized> {
|
||||
config: ProverConfig,
|
||||
span: Span,
|
||||
state: T,
|
||||
@@ -131,7 +135,6 @@ impl Prover<state::Initialized> {
|
||||
state: state::Setup {
|
||||
mux_ctrl,
|
||||
mux_fut,
|
||||
mt,
|
||||
mpc_tls,
|
||||
zk_aes,
|
||||
keys,
|
||||
@@ -158,11 +161,11 @@ impl Prover<state::Setup> {
|
||||
let state::Setup {
|
||||
mux_ctrl,
|
||||
mut mux_fut,
|
||||
mt,
|
||||
mpc_tls,
|
||||
mut zk_aes,
|
||||
keys,
|
||||
vm,
|
||||
..
|
||||
} = self.state;
|
||||
|
||||
let (mpc_ctrl, mpc_fut) = mpc_tls.run();
|
||||
@@ -292,10 +295,9 @@ impl Prover<state::Setup> {
|
||||
Ok(Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: state::Closed {
|
||||
state: state::Committed {
|
||||
mux_ctrl,
|
||||
mux_fut,
|
||||
mt,
|
||||
ctx,
|
||||
_keys: keys,
|
||||
vm,
|
||||
@@ -319,35 +321,182 @@ impl Prover<state::Setup> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Prover<state::Closed> {
|
||||
impl Prover<state::Committed> {
|
||||
/// Returns the connection information.
|
||||
pub fn connection_info(&self) -> &ConnectionInfo {
|
||||
&self.state.connection_info
|
||||
}
|
||||
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> &Transcript {
|
||||
&self.state.transcript
|
||||
}
|
||||
|
||||
/// Starts notarization of the TLS session.
|
||||
/// Proves information to the verifier.
|
||||
///
|
||||
/// Used when the TLS verifier is a Notary to transition the prover to the
|
||||
/// next state where it can generate commitments to the transcript prior
|
||||
/// to finalization.
|
||||
pub fn start_notarize(self) -> Prover<Notarize> {
|
||||
Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The disclosure configuration.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn prove(&mut self, config: &ProveConfig) -> Result<ProverOutput, ProverError> {
|
||||
let state::Committed {
|
||||
mux_fut,
|
||||
ctx,
|
||||
vm,
|
||||
server_cert_data,
|
||||
transcript_refs,
|
||||
..
|
||||
} = &mut self.state;
|
||||
|
||||
let mut output = ProverOutput {
|
||||
transcript_commitments: Vec::new(),
|
||||
transcript_secrets: Vec::new(),
|
||||
};
|
||||
|
||||
let payload = ProvePayload {
|
||||
server_identity: config
|
||||
.server_identity()
|
||||
.then(|| (self.config.server_name().clone(), server_cert_data.clone())),
|
||||
transcript: config.transcript().cloned(),
|
||||
transcript_commit: config.transcript_commit().map(|config| config.to_request()),
|
||||
};
|
||||
|
||||
// Send payload.
|
||||
mux_fut
|
||||
.poll_with(ctx.io_mut().send(payload).map_err(ProverError::from))
|
||||
.await?;
|
||||
|
||||
if let Some(partial_transcript) = config.transcript() {
|
||||
decode_transcript(
|
||||
vm,
|
||||
partial_transcript.sent_authed(),
|
||||
partial_transcript.received_authed(),
|
||||
transcript_refs,
|
||||
)
|
||||
.map_err(ProverError::zk)?;
|
||||
}
|
||||
|
||||
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 (commitment, tree) = mux_fut
|
||||
.poll_with(
|
||||
encoding::receive(
|
||||
ctx,
|
||||
hasher,
|
||||
transcript_refs,
|
||||
|plaintext| vm.get_macs(plaintext).expect("reference is valid"),
|
||||
commit_config.iter_encoding(),
|
||||
)
|
||||
.map_err(ProverError::commit),
|
||||
)
|
||||
.await?;
|
||||
|
||||
output
|
||||
.transcript_commitments
|
||||
.push(TranscriptCommitment::Encoding(commitment));
|
||||
output
|
||||
.transcript_secrets
|
||||
.push(TranscriptSecret::Encoding(tree));
|
||||
}
|
||||
|
||||
// TODO: Other commitment types.
|
||||
}
|
||||
|
||||
mux_fut
|
||||
.poll_with(vm.execute_all(ctx).map_err(ProverError::zk))
|
||||
.await?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Starts proving the TLS session.
|
||||
/// Requests an attestation from the verifier.
|
||||
///
|
||||
/// This function transitions the prover into a state where it can prove
|
||||
/// content of the transcript.
|
||||
pub fn start_prove(self) -> Prover<Prove> {
|
||||
Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The attestation request configuration.
|
||||
#[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(
|
||||
&mut self,
|
||||
config: &RequestConfig,
|
||||
) -> Result<(Attestation, Secrets), ProverError> {
|
||||
let mut builder = ProveConfig::builder(self.transcript());
|
||||
|
||||
if let Some(config) = config.transcript_commit() {
|
||||
builder.transcript_commit(config.clone());
|
||||
}
|
||||
|
||||
let disclosure_config = builder.build().map_err(ProverError::attestation)?;
|
||||
|
||||
let ProverOutput {
|
||||
transcript_commitments,
|
||||
transcript_secrets,
|
||||
..
|
||||
} = self.prove(&disclosure_config).await?;
|
||||
|
||||
let state::Committed {
|
||||
mux_fut,
|
||||
ctx,
|
||||
server_cert_data,
|
||||
transcript,
|
||||
..
|
||||
} = &mut self.state;
|
||||
|
||||
let mut builder = Request::builder(config);
|
||||
|
||||
builder
|
||||
.server_name(self.config.server_name().clone())
|
||||
.server_cert_data(server_cert_data.clone())
|
||||
.transcript(transcript.clone())
|
||||
.transcript_commitments(transcript_secrets, transcript_commitments);
|
||||
|
||||
let (request, secrets) = builder
|
||||
.build(self.config.crypto_provider())
|
||||
.map_err(ProverError::attestation)?;
|
||||
|
||||
let attestation = mux_fut
|
||||
.poll_with(async {
|
||||
debug!("sending attestation request");
|
||||
|
||||
ctx.io_mut().send(request.clone()).await?;
|
||||
|
||||
let attestation: Attestation = ctx.io_mut().expect_next().await?;
|
||||
|
||||
Ok::<_, ProverError>(attestation)
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Check the attestation is consistent with the Prover's view.
|
||||
request
|
||||
.validate(&attestation)
|
||||
.map_err(ProverError::attestation)?;
|
||||
|
||||
Ok((attestation, secrets))
|
||||
}
|
||||
|
||||
/// Closes the connection with the verifier.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn close(self) -> Result<(), ProverError> {
|
||||
let state::Committed {
|
||||
mux_ctrl, mux_fut, ..
|
||||
} = self.state;
|
||||
|
||||
// Wait for the verifier to correctly close the connection.
|
||||
if !mux_fut.is_complete() {
|
||||
mux_ctrl.close();
|
||||
mux_fut.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
//! This module handles the notarization phase of the prover.
|
||||
//!
|
||||
//! The prover interacts with a TLS verifier who acts as a Notary, i.e. the
|
||||
//! verifier produces an attestation but does not verify transcript data.
|
||||
|
||||
use super::{state::Notarize, Prover, ProverError};
|
||||
use serio::{stream::IoStreamExt as _, SinkExt as _};
|
||||
use tlsn_common::encoding;
|
||||
use tlsn_core::{
|
||||
attestation::Attestation,
|
||||
request::{Request, RequestConfig},
|
||||
transcript::{encoding::EncodingTree, Transcript, TranscriptCommitConfig},
|
||||
Secrets,
|
||||
};
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
impl Prover<Notarize> {
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> &Transcript {
|
||||
&self.state.transcript
|
||||
}
|
||||
|
||||
/// Configures transcript commitments.
|
||||
pub fn transcript_commit(&mut self, config: TranscriptCommitConfig) {
|
||||
self.state.transcript_commit_config = Some(config);
|
||||
}
|
||||
|
||||
/// Finalizes the notarization.
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn finalize(
|
||||
self,
|
||||
config: &RequestConfig,
|
||||
) -> Result<(Attestation, Secrets), ProverError> {
|
||||
let Notarize {
|
||||
mux_ctrl,
|
||||
mut mux_fut,
|
||||
mut ctx,
|
||||
vm,
|
||||
connection_info,
|
||||
server_cert_data,
|
||||
transcript,
|
||||
transcript_refs,
|
||||
transcript_commit_config,
|
||||
..
|
||||
} = self.state;
|
||||
|
||||
let sent_macs = transcript_refs
|
||||
.sent()
|
||||
.iter()
|
||||
.flat_map(|plaintext| vm.get_macs(*plaintext).expect("reference is valid"))
|
||||
.map(|mac| mac.as_block());
|
||||
let recv_macs = transcript_refs
|
||||
.recv()
|
||||
.iter()
|
||||
.flat_map(|plaintext| vm.get_macs(*plaintext).expect("reference is valid"))
|
||||
.map(|mac| mac.as_block());
|
||||
|
||||
let encoding_provider = mux_fut
|
||||
.poll_with(encoding::receive(&mut ctx, sent_macs, recv_macs))
|
||||
.await?;
|
||||
|
||||
let provider = self.config.crypto_provider();
|
||||
|
||||
let hasher = provider
|
||||
.hash
|
||||
.get(config.hash_alg())
|
||||
.map_err(ProverError::config)?;
|
||||
|
||||
let mut builder = Request::builder(config);
|
||||
|
||||
builder
|
||||
.server_name(self.config.server_name().clone())
|
||||
.server_cert_data(server_cert_data)
|
||||
.transcript(transcript);
|
||||
|
||||
if let Some(config) = transcript_commit_config {
|
||||
if config.has_encoding() {
|
||||
builder.encoding_tree(
|
||||
EncodingTree::new(
|
||||
hasher,
|
||||
config.iter_encoding(),
|
||||
&encoding_provider,
|
||||
&connection_info.transcript_length,
|
||||
)
|
||||
.map_err(ProverError::commit)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let (request, secrets) = builder.build(provider).map_err(ProverError::attestation)?;
|
||||
|
||||
let attestation = mux_fut
|
||||
.poll_with(async {
|
||||
debug!("sending attestation request");
|
||||
|
||||
ctx.io_mut().send(request.clone()).await?;
|
||||
|
||||
let attestation: Attestation = ctx.io_mut().expect_next().await?;
|
||||
|
||||
Ok::<_, ProverError>(attestation)
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Wait for the notary to correctly close the connection.
|
||||
if !mux_fut.is_complete() {
|
||||
mux_ctrl.close();
|
||||
mux_fut.await?;
|
||||
}
|
||||
|
||||
// Check the attestation is consistent with the Prover's view.
|
||||
request
|
||||
.validate(&attestation)
|
||||
.map_err(ProverError::attestation)?;
|
||||
|
||||
Ok((attestation, secrets))
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
//! This module handles the proving phase of the prover.
|
||||
//!
|
||||
//! The prover interacts with a TLS verifier directly, without involving a
|
||||
//! Notary. The verifier verifies transcript data.
|
||||
|
||||
use mpz_memory_core::MemoryExt;
|
||||
use mpz_vm_core::Execute;
|
||||
use serio::SinkExt as _;
|
||||
use tlsn_common::msg::ServerIdentityProof;
|
||||
use tlsn_core::transcript::{Direction, Idx, Transcript};
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::{state::Prove as ProveState, Prover, ProverError};
|
||||
|
||||
impl Prover<ProveState> {
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> &Transcript {
|
||||
&self.state.transcript
|
||||
}
|
||||
|
||||
/// Proves subsequences in the transcript to the verifier.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `sent` - Indices of the sent data.
|
||||
/// * `recv` - Indices of the received data.
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn prove_transcript(&mut self, sent: Idx, recv: Idx) -> Result<(), ProverError> {
|
||||
let partial_transcript = self.transcript().to_partial(sent.clone(), recv.clone());
|
||||
|
||||
let sent_refs = self
|
||||
.state
|
||||
.transcript_refs
|
||||
.get(Direction::Sent, &sent)
|
||||
.expect("index is in bounds");
|
||||
let recv_refs = self
|
||||
.state
|
||||
.transcript_refs
|
||||
.get(Direction::Received, &recv)
|
||||
.expect("index is in bounds");
|
||||
|
||||
for slice in sent_refs.into_iter().chain(recv_refs) {
|
||||
// Drop the future, we don't need it.
|
||||
drop(self.state.vm.decode(slice).map_err(ProverError::zk)?);
|
||||
}
|
||||
|
||||
self.state
|
||||
.mux_fut
|
||||
.poll_with(async {
|
||||
// Send the partial transcript to the verifier.
|
||||
self.state.ctx.io_mut().send(partial_transcript).await?;
|
||||
|
||||
info!("Sent partial transcript");
|
||||
|
||||
// Prove the partial transcript to the verifier.
|
||||
self.state
|
||||
.vm
|
||||
.flush(&mut self.state.ctx)
|
||||
.await
|
||||
.map_err(ProverError::zk)?;
|
||||
|
||||
info!("Proved partial transcript");
|
||||
|
||||
Ok::<_, ProverError>(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finalizes the proving.
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn finalize(self) -> Result<(), ProverError> {
|
||||
let ProveState {
|
||||
mux_ctrl,
|
||||
mut mux_fut,
|
||||
mut ctx,
|
||||
server_cert_data,
|
||||
..
|
||||
} = self.state;
|
||||
|
||||
mux_fut
|
||||
.poll_with(async move {
|
||||
// Send identity proof to the verifier.
|
||||
ctx.io_mut()
|
||||
.send(ServerIdentityProof {
|
||||
name: self.config.server_name().clone(),
|
||||
data: server_cert_data,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok::<_, ProverError>(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Wait for the verifier to correctly close the connection.
|
||||
if !mux_fut.is_complete() {
|
||||
mux_ctrl.close();
|
||||
mux_fut.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_common::{context::Multithread, Context};
|
||||
use mpz_common::Context;
|
||||
|
||||
use mpc_tls::{MpcTlsLeader, SessionKeys};
|
||||
use tlsn_common::{
|
||||
@@ -12,7 +12,7 @@ use tlsn_common::{
|
||||
};
|
||||
use tlsn_core::{
|
||||
connection::{ConnectionInfo, ServerCertData},
|
||||
transcript::{Transcript, TranscriptCommitConfig},
|
||||
transcript::Transcript,
|
||||
};
|
||||
use tlsn_deap::Deap;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -28,7 +28,6 @@ opaque_debug::implement!(Initialized);
|
||||
pub struct Setup {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) mt: Multithread,
|
||||
pub(crate) mpc_tls: MpcTlsLeader,
|
||||
pub(crate) zk_aes: ZkAesCtr,
|
||||
pub(crate) keys: SessionKeys,
|
||||
@@ -37,11 +36,10 @@ pub struct Setup {
|
||||
|
||||
opaque_debug::implement!(Setup);
|
||||
|
||||
/// State after the TLS connection has been closed.
|
||||
pub struct Closed {
|
||||
/// State after the TLS connection has been committed and closed.
|
||||
pub struct Committed {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) mt: Multithread,
|
||||
pub(crate) ctx: Context,
|
||||
pub(crate) _keys: SessionKeys,
|
||||
pub(crate) vm: Zk,
|
||||
@@ -51,84 +49,18 @@ pub struct Closed {
|
||||
pub(crate) transcript_refs: TranscriptRefs,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Closed);
|
||||
|
||||
/// Notarizing state.
|
||||
pub struct Notarize {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) _mt: Multithread,
|
||||
pub(crate) ctx: Context,
|
||||
pub(crate) vm: Zk,
|
||||
pub(crate) connection_info: ConnectionInfo,
|
||||
pub(crate) server_cert_data: ServerCertData,
|
||||
pub(crate) transcript: Transcript,
|
||||
pub(crate) transcript_refs: TranscriptRefs,
|
||||
pub(crate) transcript_commit_config: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Notarize);
|
||||
|
||||
impl From<Closed> for Notarize {
|
||||
fn from(state: Closed) -> Self {
|
||||
Self {
|
||||
mux_ctrl: state.mux_ctrl,
|
||||
mux_fut: state.mux_fut,
|
||||
_mt: state.mt,
|
||||
ctx: state.ctx,
|
||||
vm: state.vm,
|
||||
connection_info: state.connection_info,
|
||||
server_cert_data: state.server_cert_data,
|
||||
transcript: state.transcript,
|
||||
transcript_refs: state.transcript_refs,
|
||||
transcript_commit_config: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proving state.
|
||||
pub struct Prove {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) _mt: Multithread,
|
||||
pub(crate) ctx: Context,
|
||||
pub(crate) vm: Zk,
|
||||
pub(crate) _connection_info: ConnectionInfo,
|
||||
pub(crate) server_cert_data: ServerCertData,
|
||||
pub(crate) transcript: Transcript,
|
||||
pub(crate) transcript_refs: TranscriptRefs,
|
||||
}
|
||||
|
||||
impl From<Closed> for Prove {
|
||||
fn from(state: Closed) -> Self {
|
||||
Self {
|
||||
mux_ctrl: state.mux_ctrl,
|
||||
mux_fut: state.mux_fut,
|
||||
_mt: state.mt,
|
||||
ctx: state.ctx,
|
||||
vm: state.vm,
|
||||
_connection_info: state.connection_info,
|
||||
server_cert_data: state.server_cert_data,
|
||||
transcript: state.transcript,
|
||||
transcript_refs: state.transcript_refs,
|
||||
}
|
||||
}
|
||||
}
|
||||
opaque_debug::implement!(Committed);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub trait ProverState: sealed::Sealed {}
|
||||
|
||||
impl ProverState for Initialized {}
|
||||
impl ProverState for Setup {}
|
||||
impl ProverState for Closed {}
|
||||
impl ProverState for Notarize {}
|
||||
impl ProverState for Prove {}
|
||||
impl ProverState for Committed {}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl Sealed for super::Initialized {}
|
||||
impl Sealed for super::Setup {}
|
||||
impl Sealed for super::Closed {}
|
||||
impl Sealed for super::Notarize {}
|
||||
impl Sealed for super::Prove {}
|
||||
impl Sealed for super::Committed {}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ async fn test_defer_decryption() {
|
||||
}
|
||||
|
||||
#[instrument(skip(notary_socket))]
|
||||
#[allow(deprecated)]
|
||||
async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socket: T) {
|
||||
let (client_socket, server_socket) = tokio::io::duplex(2 << 16);
|
||||
|
||||
@@ -83,7 +84,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
|
||||
let _ = server_task.await.unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
let sent_tx_len = prover.transcript().sent().len();
|
||||
let recv_tx_len = prover.transcript().received().len();
|
||||
|
||||
@@ -93,16 +94,20 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
builder.commit_sent(&(0..sent_tx_len)).unwrap();
|
||||
builder.commit_recv(&(0..recv_tx_len)).unwrap();
|
||||
|
||||
let transcript_commit = builder.build().unwrap();
|
||||
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
builder.transcript_commit(transcript_commit);
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.transcript_commit(config);
|
||||
|
||||
let config = RequestConfig::default();
|
||||
|
||||
prover.finalize(&config).await.unwrap();
|
||||
prover.notarize(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
}
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
#[allow(deprecated)]
|
||||
async fn notary<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(socket: T) {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
|
||||
@@ -35,6 +35,7 @@ async fn notarize() {
|
||||
}
|
||||
|
||||
#[instrument(skip(notary_socket))]
|
||||
#[allow(deprecated)]
|
||||
async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socket: T) {
|
||||
let (client_socket, server_socket) = tokio::io::duplex(2 << 16);
|
||||
|
||||
@@ -98,7 +99,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
|
||||
let _ = server_task.await.unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
let sent_tx_len = prover.transcript().sent().len();
|
||||
let recv_tx_len = prover.transcript().received().len();
|
||||
|
||||
@@ -108,12 +109,11 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
builder.commit_sent(&(0..sent_tx_len)).unwrap();
|
||||
builder.commit_recv(&(0..recv_tx_len)).unwrap();
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.transcript_commit(config);
|
||||
let transcript_commit = builder.build().unwrap();
|
||||
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
builder.transcript_commit(transcript_commit);
|
||||
builder.extension(Extension {
|
||||
id: b"foo".to_vec(),
|
||||
value: b"bar".to_vec(),
|
||||
@@ -121,12 +121,14 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
let (attestation, _) = prover.finalize(&config).await.unwrap();
|
||||
let (attestation, _) = prover.notarize(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
|
||||
assert_eq!(attestation.body.extensions().count(), 1);
|
||||
}
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
#[allow(deprecated)]
|
||||
async fn notary<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(socket: T) {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
|
||||
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
|
||||
use tlsn_core::{
|
||||
transcript::{Idx, PartialTranscript},
|
||||
CryptoProvider,
|
||||
};
|
||||
use tlsn_core::{transcript::Idx, CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig};
|
||||
use tlsn_prover::{Prover, ProverConfig};
|
||||
use tlsn_server_fixture::bind;
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
use tlsn_verifier::{SessionInfo, Verifier, VerifierConfig};
|
||||
use tlsn_verifier::{Verifier, VerifierConfig};
|
||||
|
||||
use http_body_util::{BodyExt as _, Empty};
|
||||
use hyper::{body::Bytes, Request, StatusCode};
|
||||
@@ -29,17 +26,27 @@ async fn verify() {
|
||||
|
||||
let (socket_0, socket_1) = tokio::io::duplex(1 << 23);
|
||||
|
||||
let (_, (partial_transcript, info)) = tokio::join!(prover(socket_0), verifier(socket_1));
|
||||
let (
|
||||
_,
|
||||
VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
..
|
||||
},
|
||||
) = tokio::join!(prover(socket_0), verifier(socket_1));
|
||||
|
||||
let server_name = server_name.unwrap();
|
||||
let transcript = transcript.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
partial_transcript.sent_authed(),
|
||||
&Idx::new(0..partial_transcript.len_sent() - 1)
|
||||
transcript.sent_authed(),
|
||||
&Idx::new(0..transcript.len_sent() - 1)
|
||||
);
|
||||
assert_eq!(
|
||||
partial_transcript.received_authed(),
|
||||
&Idx::new(2..partial_transcript.len_received())
|
||||
transcript.received_authed(),
|
||||
&Idx::new(2..transcript.len_received())
|
||||
);
|
||||
assert_eq!(info.server_name.as_str(), SERVER_DOMAIN);
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
}
|
||||
|
||||
#[instrument(skip(notary_socket))]
|
||||
@@ -105,22 +112,29 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
|
||||
let _ = server_task.await.unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
|
||||
let (sent_len, recv_len) = prover.transcript().len();
|
||||
|
||||
let idx_sent = Idx::new(0..sent_len - 1);
|
||||
let idx_recv = Idx::new(2..recv_len);
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
// Reveal parts of the transcript
|
||||
prover.prove_transcript(idx_sent, idx_recv).await.unwrap();
|
||||
prover.finalize().await.unwrap();
|
||||
builder
|
||||
.server_identity()
|
||||
.reveal_sent(&(0..sent_len - 1))
|
||||
.unwrap()
|
||||
.reveal_recv(&(2..recv_len))
|
||||
.unwrap();
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.prove(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
}
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: T,
|
||||
) -> (PartialTranscript, SessionInfo) {
|
||||
) -> VerifierOutput {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
@@ -145,5 +159,8 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
|
||||
let verifier = Verifier::new(config);
|
||||
|
||||
verifier.verify(socket.compat()).await.unwrap()
|
||||
verifier
|
||||
.verify(socket.compat(), &VerifyConfig::default())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -6,36 +6,38 @@
|
||||
|
||||
pub(crate) mod config;
|
||||
mod error;
|
||||
mod notarize;
|
||||
pub mod state;
|
||||
mod verify;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use config::{VerifierConfig, VerifierConfigBuilder, VerifierConfigBuilderError};
|
||||
pub use error::VerifierError;
|
||||
pub use tlsn_core::{VerifierOutput, VerifyConfig, VerifyConfigBuilder, VerifyConfigBuilderError};
|
||||
|
||||
use futures::{AsyncRead, AsyncWrite};
|
||||
use futures::{AsyncRead, AsyncWrite, TryFutureExt};
|
||||
use mpc_tls::{FollowerData, MpcTlsFollower, SessionKeys};
|
||||
use mpz_common::Context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble_core::Delta;
|
||||
use serio::stream::IoStreamExt;
|
||||
use state::{Notarize, Verify};
|
||||
use mpz_vm_core::prelude::*;
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_common::{
|
||||
commit::commit_records,
|
||||
config::ProtocolConfig,
|
||||
context::build_mt_context,
|
||||
encoding,
|
||||
mux::attach_mux,
|
||||
transcript::{Record, TlsTranscript},
|
||||
transcript::{decode_transcript, verify_transcript, Record, TlsTranscript},
|
||||
zk_aes::ZkAesCtr,
|
||||
Role,
|
||||
};
|
||||
use tlsn_core::{
|
||||
attestation::{Attestation, AttestationConfig},
|
||||
connection::{ConnectionInfo, ServerName, TlsVersion, TranscriptLength},
|
||||
transcript::PartialTranscript,
|
||||
request::Request,
|
||||
transcript::TranscriptCommitment,
|
||||
ProvePayload,
|
||||
};
|
||||
use tlsn_deap::Deap;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -66,7 +68,7 @@ pub struct SessionInfo {
|
||||
}
|
||||
|
||||
/// A Verifier instance.
|
||||
pub struct Verifier<T: state::VerifierState> {
|
||||
pub struct Verifier<T: state::VerifierState = state::Initialized> {
|
||||
config: VerifierConfig,
|
||||
span: Span,
|
||||
state: T,
|
||||
@@ -138,7 +140,6 @@ impl Verifier<state::Initialized> {
|
||||
state: state::Setup {
|
||||
mux_ctrl,
|
||||
mux_fut,
|
||||
mt,
|
||||
delta,
|
||||
mpc_tls,
|
||||
zk_aes,
|
||||
@@ -148,7 +149,7 @@ impl Verifier<state::Initialized> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the TLS verifier to completion, notarizing the TLS session.
|
||||
/// Runs the verifier to completion and attests to the TLS session.
|
||||
///
|
||||
/// This is a convenience method which runs all the steps needed for
|
||||
/// notarization.
|
||||
@@ -158,18 +159,22 @@ impl Verifier<state::Initialized> {
|
||||
/// * `socket` - The socket to the prover.
|
||||
/// * `config` - The attestation configuration.
|
||||
#[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<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
config: &AttestationConfig,
|
||||
) -> Result<Attestation, VerifierError> {
|
||||
self.setup(socket)
|
||||
.await?
|
||||
.run()
|
||||
.await?
|
||||
.start_notarize()
|
||||
.finalize(config)
|
||||
.await
|
||||
let mut verifier = self.setup(socket).await?.run().await?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let attestation = verifier.notarize(config).await?;
|
||||
|
||||
verifier.close().await?;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
/// Runs the TLS verifier to completion, verifying the TLS session.
|
||||
@@ -184,23 +189,25 @@ impl Verifier<state::Initialized> {
|
||||
pub async fn verify<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
) -> Result<(PartialTranscript, SessionInfo), VerifierError> {
|
||||
let mut verifier = self.setup(socket).await?.run().await?.start_verify();
|
||||
let transcript = verifier.receive().await?;
|
||||
config: &VerifyConfig,
|
||||
) -> Result<VerifierOutput, VerifierError> {
|
||||
let mut verifier = self.setup(socket).await?.run().await?;
|
||||
|
||||
let session_info = verifier.finalize().await?;
|
||||
Ok((transcript, session_info))
|
||||
let output = verifier.verify(config).await?;
|
||||
|
||||
verifier.close().await?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier<state::Setup> {
|
||||
/// Runs the verifier until the TLS connection is closed.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn run(self) -> Result<Verifier<state::Closed>, VerifierError> {
|
||||
pub async fn run(self) -> Result<Verifier<state::Committed>, VerifierError> {
|
||||
let state::Setup {
|
||||
mux_ctrl,
|
||||
mut mux_fut,
|
||||
mt,
|
||||
delta,
|
||||
mpc_tls,
|
||||
mut zk_aes,
|
||||
@@ -220,7 +227,7 @@ impl Verifier<state::Setup> {
|
||||
FollowerData {
|
||||
server_key,
|
||||
mut transcript,
|
||||
keys,
|
||||
..
|
||||
},
|
||||
) = mux_fut.poll_with(mpc_tls.run()).await?;
|
||||
|
||||
@@ -288,13 +295,11 @@ impl Verifier<state::Setup> {
|
||||
Ok(Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: state::Closed {
|
||||
state: state::Committed {
|
||||
mux_ctrl,
|
||||
mux_fut,
|
||||
mt,
|
||||
delta,
|
||||
ctx,
|
||||
keys,
|
||||
vm,
|
||||
server_ephemeral_key: server_key
|
||||
.try_into()
|
||||
@@ -306,30 +311,192 @@ impl Verifier<state::Setup> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier<state::Closed> {
|
||||
/// Starts notarization of the TLS session.
|
||||
///
|
||||
/// If the verifier is a Notary, this function will transition the verifier
|
||||
/// to the next state where it can sign the prover's commitments to the
|
||||
/// transcript.
|
||||
pub fn start_notarize(self) -> Verifier<Notarize> {
|
||||
Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
}
|
||||
impl Verifier<state::Committed> {
|
||||
/// Returns the connection information.
|
||||
pub fn connection_info(&self) -> &ConnectionInfo {
|
||||
&self.state.connection_info
|
||||
}
|
||||
|
||||
/// Starts verification of the TLS session.
|
||||
/// Verifies information from the prover.
|
||||
///
|
||||
/// This function transitions the verifier into a state where it can verify
|
||||
/// the contents of the transcript.
|
||||
pub fn start_verify(self) -> Verifier<Verify> {
|
||||
Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - Verification configuration.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn verify(
|
||||
&mut self,
|
||||
#[allow(unused_variables)] config: &VerifyConfig,
|
||||
) -> Result<VerifierOutput, VerifierError> {
|
||||
let state::Committed {
|
||||
mux_fut,
|
||||
ctx,
|
||||
delta,
|
||||
vm,
|
||||
connection_info,
|
||||
server_ephemeral_key,
|
||||
transcript_refs,
|
||||
..
|
||||
} = &mut self.state;
|
||||
|
||||
let ProvePayload {
|
||||
server_identity,
|
||||
transcript,
|
||||
transcript_commit,
|
||||
} = mux_fut
|
||||
.poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from))
|
||||
.await?;
|
||||
|
||||
let server_name = if let Some((name, cert_data)) = server_identity {
|
||||
cert_data
|
||||
.verify_with_provider(
|
||||
self.config.crypto_provider(),
|
||||
connection_info.time,
|
||||
server_ephemeral_key,
|
||||
&name,
|
||||
)
|
||||
.map_err(VerifierError::verify)?;
|
||||
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(partial_transcript) = &transcript {
|
||||
// Check ranges.
|
||||
if partial_transcript.len_sent() != connection_info.transcript_length.sent as usize
|
||||
|| partial_transcript.len_received()
|
||||
!= connection_info.transcript_length.received as usize
|
||||
{
|
||||
return Err(VerifierError::verify(
|
||||
"prover sent transcript with incorrect length",
|
||||
));
|
||||
}
|
||||
|
||||
decode_transcript(
|
||||
vm,
|
||||
partial_transcript.sent_authed(),
|
||||
partial_transcript.received_authed(),
|
||||
transcript_refs,
|
||||
)
|
||||
.map_err(VerifierError::zk)?;
|
||||
}
|
||||
|
||||
let mut transcript_commitments = Vec::new();
|
||||
if let Some(commit_config) = transcript_commit {
|
||||
if commit_config.encoding() {
|
||||
let commitment = mux_fut
|
||||
.poll_with(encoding::transfer(
|
||||
ctx,
|
||||
transcript_refs,
|
||||
delta,
|
||||
|plaintext| vm.get_keys(plaintext).expect("reference is valid"),
|
||||
))
|
||||
.await?;
|
||||
|
||||
transcript_commitments.push(TranscriptCommitment::Encoding(commitment));
|
||||
}
|
||||
|
||||
// TODO: Other commitment types.
|
||||
}
|
||||
|
||||
mux_fut
|
||||
.poll_with(vm.execute_all(ctx).map_err(VerifierError::zk))
|
||||
.await?;
|
||||
|
||||
// Verify revealed data.
|
||||
if let Some(partial_transcript) = &transcript {
|
||||
verify_transcript(vm, partial_transcript, transcript_refs)
|
||||
.map_err(VerifierError::verify)?;
|
||||
}
|
||||
|
||||
Ok(VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attests to the TLS session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - Attestation configuration.
|
||||
#[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(
|
||||
&mut self,
|
||||
config: &AttestationConfig,
|
||||
) -> Result<Attestation, VerifierError> {
|
||||
let VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
} = self.verify(&VerifyConfig::default()).await?;
|
||||
|
||||
if server_name.is_some() {
|
||||
return Err(VerifierError::attestation(
|
||||
"server name can not be revealed to a notary",
|
||||
));
|
||||
} else if transcript.is_some() {
|
||||
return Err(VerifierError::attestation(
|
||||
"transcript data can not be revealed to a notary",
|
||||
));
|
||||
}
|
||||
|
||||
let state::Committed {
|
||||
mux_fut,
|
||||
ctx,
|
||||
server_ephemeral_key,
|
||||
connection_info,
|
||||
..
|
||||
} = &mut self.state;
|
||||
|
||||
let request: Request = mux_fut
|
||||
.poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from))
|
||||
.await?;
|
||||
|
||||
let mut builder = Attestation::builder(config)
|
||||
.accept_request(request)
|
||||
.map_err(VerifierError::attestation)?;
|
||||
|
||||
builder
|
||||
.connection_info(connection_info.clone())
|
||||
.server_ephemeral_key(server_ephemeral_key.clone())
|
||||
.transcript_commitments(transcript_commitments);
|
||||
|
||||
let attestation = builder
|
||||
.build(self.config.crypto_provider())
|
||||
.map_err(VerifierError::attestation)?;
|
||||
|
||||
mux_fut
|
||||
.poll_with(
|
||||
ctx.io_mut()
|
||||
.send(attestation.clone())
|
||||
.map_err(VerifierError::from),
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Sent attestation");
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
/// Closes the connection with the prover.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn close(self) -> Result<(), VerifierError> {
|
||||
let state::Committed {
|
||||
mux_ctrl, mux_fut, ..
|
||||
} = self.state;
|
||||
|
||||
// Wait for the prover to correctly close the connection.
|
||||
if !mux_fut.is_complete() {
|
||||
mux_ctrl.close();
|
||||
mux_fut.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
//! This module handles the notarization phase of the verifier.
|
||||
//!
|
||||
//! The TLS verifier acts as a Notary, i.e. the verifier produces an
|
||||
//! attestation but does not verify transcript data.
|
||||
|
||||
use super::{state::Notarize, Verifier, VerifierError};
|
||||
use rand::Rng;
|
||||
use serio::{stream::IoStreamExt, SinkExt as _};
|
||||
|
||||
use tlsn_common::encoding;
|
||||
use tlsn_core::{
|
||||
attestation::{Attestation, AttestationConfig},
|
||||
request::Request,
|
||||
transcript::encoding::EncoderSecret,
|
||||
};
|
||||
use tracing::{info, instrument};
|
||||
|
||||
impl Verifier<Notarize> {
|
||||
/// Notarizes the TLS session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The attestation configuration.
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn finalize(self, config: &AttestationConfig) -> Result<Attestation, VerifierError> {
|
||||
let Notarize {
|
||||
mux_ctrl,
|
||||
mut mux_fut,
|
||||
delta,
|
||||
mut ctx,
|
||||
vm,
|
||||
server_ephemeral_key,
|
||||
connection_info,
|
||||
transcript_refs,
|
||||
..
|
||||
} = self.state;
|
||||
|
||||
let encoder_secret = EncoderSecret::new(rand::rng().random(), delta.as_block().to_bytes());
|
||||
|
||||
let attestation = mux_fut
|
||||
.poll_with(async {
|
||||
let sent_keys = transcript_refs
|
||||
.sent()
|
||||
.iter()
|
||||
.flat_map(|plaintext| vm.get_keys(*plaintext).expect("reference is valid"))
|
||||
.map(|key| key.as_block());
|
||||
let recv_keys = transcript_refs
|
||||
.recv()
|
||||
.iter()
|
||||
.flat_map(|plaintext| vm.get_keys(*plaintext).expect("reference is valid"))
|
||||
.map(|key| key.as_block());
|
||||
|
||||
// Convert encodings into a structured format.
|
||||
encoding::transfer(&mut ctx, &encoder_secret, sent_keys, recv_keys).await?;
|
||||
|
||||
// Receive attestation request, which also contains commitments required before
|
||||
// finalization.
|
||||
let request: Request = ctx.io_mut().expect_next().await?;
|
||||
|
||||
let mut builder = Attestation::builder(config)
|
||||
.accept_request(request)
|
||||
.map_err(VerifierError::attestation)?;
|
||||
|
||||
builder
|
||||
.connection_info(connection_info)
|
||||
.server_ephemeral_key(server_ephemeral_key)
|
||||
.encoder_secret(encoder_secret);
|
||||
|
||||
let attestation = builder
|
||||
.build(self.config.crypto_provider())
|
||||
.map_err(VerifierError::attestation)?;
|
||||
|
||||
ctx.io_mut().send(attestation.clone()).await?;
|
||||
|
||||
info!("Sent attestation");
|
||||
|
||||
Ok::<_, VerifierError>(attestation)
|
||||
})
|
||||
.await?;
|
||||
|
||||
if !mux_fut.is_complete() {
|
||||
mux_ctrl.close();
|
||||
mux_fut.await?;
|
||||
}
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::{Mpc, Zk};
|
||||
use mpc_tls::{MpcTlsFollower, SessionKeys};
|
||||
use mpz_common::{context::Multithread, Context};
|
||||
use mpz_common::Context;
|
||||
use mpz_memory_core::correlated::Delta;
|
||||
use tlsn_common::{
|
||||
mux::{MuxControl, MuxFuture},
|
||||
@@ -27,7 +27,6 @@ opaque_debug::implement!(Initialized);
|
||||
pub struct Setup {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) mt: Multithread,
|
||||
pub(crate) delta: Delta,
|
||||
pub(crate) mpc_tls: MpcTlsFollower,
|
||||
pub(crate) zk_aes: ZkAesCtr,
|
||||
@@ -36,96 +35,26 @@ pub struct Setup {
|
||||
}
|
||||
|
||||
/// State after the TLS connection has been closed.
|
||||
pub struct Closed {
|
||||
pub struct Committed {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) mt: Multithread,
|
||||
pub(crate) delta: Delta,
|
||||
pub(crate) ctx: Context,
|
||||
pub(crate) keys: SessionKeys,
|
||||
pub(crate) vm: Zk,
|
||||
pub(crate) server_ephemeral_key: ServerEphemKey,
|
||||
pub(crate) connection_info: ConnectionInfo,
|
||||
pub(crate) transcript_refs: TranscriptRefs,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Closed);
|
||||
|
||||
/// Notarizing state.
|
||||
pub struct Notarize {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) _mt: Multithread,
|
||||
pub(crate) delta: Delta,
|
||||
pub(crate) ctx: Context,
|
||||
pub(crate) _keys: SessionKeys,
|
||||
pub(crate) vm: Zk,
|
||||
pub(crate) server_ephemeral_key: ServerEphemKey,
|
||||
pub(crate) connection_info: ConnectionInfo,
|
||||
pub(crate) transcript_refs: TranscriptRefs,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Notarize);
|
||||
|
||||
impl From<Closed> for Notarize {
|
||||
fn from(value: Closed) -> Self {
|
||||
Self {
|
||||
mux_ctrl: value.mux_ctrl,
|
||||
mux_fut: value.mux_fut,
|
||||
_mt: value.mt,
|
||||
delta: value.delta,
|
||||
ctx: value.ctx,
|
||||
_keys: value.keys,
|
||||
vm: value.vm,
|
||||
server_ephemeral_key: value.server_ephemeral_key,
|
||||
connection_info: value.connection_info,
|
||||
transcript_refs: value.transcript_refs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifying state.
|
||||
pub struct Verify {
|
||||
pub(crate) mux_ctrl: MuxControl,
|
||||
pub(crate) mux_fut: MuxFuture,
|
||||
pub(crate) _mt: Multithread,
|
||||
pub(crate) ctx: Context,
|
||||
pub(crate) _keys: SessionKeys,
|
||||
pub(crate) vm: Zk,
|
||||
pub(crate) server_ephemeral_key: ServerEphemKey,
|
||||
pub(crate) connection_info: ConnectionInfo,
|
||||
pub(crate) transcript_refs: TranscriptRefs,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Verify);
|
||||
|
||||
impl From<Closed> for Verify {
|
||||
fn from(value: Closed) -> Self {
|
||||
Self {
|
||||
mux_ctrl: value.mux_ctrl,
|
||||
mux_fut: value.mux_fut,
|
||||
_mt: value.mt,
|
||||
ctx: value.ctx,
|
||||
_keys: value.keys,
|
||||
vm: value.vm,
|
||||
server_ephemeral_key: value.server_ephemeral_key,
|
||||
connection_info: value.connection_info,
|
||||
transcript_refs: value.transcript_refs,
|
||||
}
|
||||
}
|
||||
}
|
||||
opaque_debug::implement!(Committed);
|
||||
|
||||
impl VerifierState for Initialized {}
|
||||
impl VerifierState for Setup {}
|
||||
impl VerifierState for Closed {}
|
||||
impl VerifierState for Notarize {}
|
||||
impl VerifierState for Verify {}
|
||||
impl VerifierState for Committed {}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl Sealed for super::Initialized {}
|
||||
impl Sealed for super::Setup {}
|
||||
impl Sealed for super::Closed {}
|
||||
impl Sealed for super::Notarize {}
|
||||
impl Sealed for super::Verify {}
|
||||
impl Sealed for super::Committed {}
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
//! This module handles the verification phase of the verifier.
|
||||
//!
|
||||
//! The TLS verifier is an application-specific verifier.
|
||||
|
||||
use crate::SessionInfo;
|
||||
|
||||
use super::{state::Verify as VerifyState, Verifier, VerifierError};
|
||||
use mpz_memory_core::MemoryExt;
|
||||
use mpz_vm_core::Execute;
|
||||
use serio::stream::IoStreamExt;
|
||||
use tlsn_common::msg::ServerIdentityProof;
|
||||
use tlsn_core::transcript::{Direction, PartialTranscript};
|
||||
|
||||
use tracing::{info, instrument};
|
||||
|
||||
impl Verifier<VerifyState> {
|
||||
/// Receives the **purported** transcript from the Prover.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The content of the received transcript can not be considered authentic
|
||||
/// until after finalization.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn receive(&mut self) -> Result<PartialTranscript, VerifierError> {
|
||||
self.state
|
||||
.mux_fut
|
||||
.poll_with(async {
|
||||
// Receive partial transcript from the prover.
|
||||
let partial_transcript: PartialTranscript =
|
||||
self.state.ctx.io_mut().expect_next().await?;
|
||||
|
||||
info!("Received partial transcript from prover");
|
||||
|
||||
// Check ranges.
|
||||
if partial_transcript.len_sent()
|
||||
!= self.state.connection_info.transcript_length.sent as usize
|
||||
|| partial_transcript.len_received()
|
||||
!= self.state.connection_info.transcript_length.received as usize
|
||||
{
|
||||
return Err(VerifierError::verify(
|
||||
"prover sent transcript with incorrect length",
|
||||
));
|
||||
}
|
||||
|
||||
// Now verify the transcript parts which the prover wants to reveal.
|
||||
let sent_refs = self
|
||||
.state
|
||||
.transcript_refs
|
||||
.get(Direction::Sent, partial_transcript.sent_authed())
|
||||
.expect("index is in bounds");
|
||||
let recv_refs = self
|
||||
.state
|
||||
.transcript_refs
|
||||
.get(Direction::Received, partial_transcript.received_authed())
|
||||
.expect("index is in bounds");
|
||||
|
||||
let plaintext_futs = sent_refs
|
||||
.into_iter()
|
||||
.chain(recv_refs)
|
||||
.map(|slice| self.state.vm.decode(slice).map_err(VerifierError::zk))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
self.state.vm.flush(&mut self.state.ctx).await.unwrap();
|
||||
|
||||
let mut authenticated_data = Vec::new();
|
||||
for mut fut in plaintext_futs {
|
||||
let plaintext = fut
|
||||
.try_recv()
|
||||
.map_err(VerifierError::zk)?
|
||||
.expect("plaintext should be decoded");
|
||||
authenticated_data.extend_from_slice(&plaintext);
|
||||
}
|
||||
|
||||
// Check that the purported data in the partial transcript is
|
||||
// correct.
|
||||
if authenticated_data
|
||||
.into_iter()
|
||||
.zip(
|
||||
partial_transcript
|
||||
.iter(Direction::Sent)
|
||||
.chain(partial_transcript.iter(Direction::Received)),
|
||||
)
|
||||
.any(|(a, b)| a != b)
|
||||
{
|
||||
return Err(VerifierError::verify("purported transcript is incorrect"));
|
||||
}
|
||||
|
||||
info!("Successfully verified purported transcript");
|
||||
|
||||
Ok::<_, VerifierError>(partial_transcript)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Verifies the TLS session.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn finalize(self) -> Result<SessionInfo, VerifierError> {
|
||||
let VerifyState {
|
||||
mux_ctrl,
|
||||
mut mux_fut,
|
||||
mut ctx,
|
||||
server_ephemeral_key,
|
||||
connection_info,
|
||||
..
|
||||
} = self.state;
|
||||
|
||||
let ServerIdentityProof {
|
||||
name: server_name,
|
||||
data,
|
||||
} = mux_fut.poll_with(ctx.io_mut().expect_next()).await?;
|
||||
|
||||
// Verify the server identity data.
|
||||
data.verify_with_provider(
|
||||
self.config.crypto_provider(),
|
||||
connection_info.time,
|
||||
&server_ephemeral_key,
|
||||
&server_name,
|
||||
)
|
||||
.map_err(VerifierError::verify)?;
|
||||
|
||||
info!("Successfully verified session");
|
||||
|
||||
if !mux_fut.is_complete() {
|
||||
mux_ctrl.close();
|
||||
mux_fut.await?;
|
||||
}
|
||||
|
||||
Ok(SessionInfo {
|
||||
server_name,
|
||||
connection_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ use futures::{AsyncReadExt, AsyncWriteExt, Future};
|
||||
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
|
||||
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
|
||||
use tlsn_core::{
|
||||
attestation::AttestationConfig, signing::SignatureAlgId, transcript::Idx, CryptoProvider,
|
||||
attestation::AttestationConfig, signing::SignatureAlgId, CryptoProvider, ProveConfig,
|
||||
VerifyConfig,
|
||||
};
|
||||
use tlsn_prover::{Prover, ProverConfig};
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
@@ -90,7 +91,9 @@ async fn handle_verifier(io: TcpStream) -> Result<()> {
|
||||
|
||||
let verifier = Verifier::new(config);
|
||||
|
||||
verifier.verify(io.compat()).await?;
|
||||
verifier
|
||||
.verify(io.compat(), &VerifyConfig::default())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -120,6 +123,7 @@ async fn handle_notary(io: TcpStream) -> Result<()> {
|
||||
|
||||
let attestation_config = builder.build().unwrap();
|
||||
|
||||
#[allow(deprecated)]
|
||||
verifier.notarize(io.compat(), &attestation_config).await?;
|
||||
|
||||
Ok(())
|
||||
@@ -176,18 +180,20 @@ async fn handle_prover(io: TcpStream) -> Result<()> {
|
||||
let mut response = vec![0u8; 1024];
|
||||
tls_connection.read_to_end(&mut response).await.unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
|
||||
let sent_transcript_len = prover.transcript().sent().len();
|
||||
let recv_transcript_len = prover.transcript().received().len();
|
||||
|
||||
let sent_idx = Idx::new(0..sent_transcript_len - 1);
|
||||
let recv_idx = Idx::new(2..recv_transcript_len);
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
// Reveal parts of the transcript
|
||||
prover.prove_transcript(sent_idx, recv_idx).await.unwrap();
|
||||
builder.reveal_sent(&(0..sent_transcript_len - 1)).unwrap();
|
||||
builder.reveal_recv(&(2..recv_transcript_len)).unwrap();
|
||||
|
||||
prover.finalize().await.unwrap();
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.prove(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,11 +7,8 @@ use futures::TryFutureExt;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::body::Bytes;
|
||||
use tls_client_async::TlsConnection;
|
||||
use tlsn_core::{
|
||||
request::RequestConfig,
|
||||
transcript::{Idx, TranscriptCommitConfigBuilder},
|
||||
};
|
||||
use tlsn_prover::{state, Prover};
|
||||
use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfigBuilder};
|
||||
use tlsn_prover::{state, ProveConfig, Prover};
|
||||
use tracing::info;
|
||||
use wasm_bindgen::{prelude::*, JsError};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
@@ -31,7 +28,7 @@ pub struct JsProver {
|
||||
enum State {
|
||||
Initialized(Prover<state::Initialized>),
|
||||
Setup(Prover<state::Setup>),
|
||||
Closed(Prover<state::Closed>),
|
||||
Committed(Prover<state::Committed>),
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
@@ -96,21 +93,21 @@ impl JsProver {
|
||||
|
||||
info!("response received");
|
||||
|
||||
self.state = State::Closed(prover);
|
||||
self.state = State::Committed(prover);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> Result<Transcript> {
|
||||
let prover = self.state.try_as_closed()?;
|
||||
let prover = self.state.try_as_committed()?;
|
||||
|
||||
Ok(Transcript::from(prover.transcript()))
|
||||
}
|
||||
|
||||
/// Runs the notarization protocol.
|
||||
pub async fn notarize(&mut self, commit: Commit) -> Result<NotarizationOutput> {
|
||||
let mut prover = self.state.take().try_into_closed()?.start_notarize();
|
||||
let mut prover = self.state.take().try_into_committed()?;
|
||||
|
||||
info!("starting notarization");
|
||||
|
||||
@@ -124,12 +121,17 @@ impl JsProver {
|
||||
builder.commit_recv(&range)?;
|
||||
}
|
||||
|
||||
let config = builder.build()?;
|
||||
let transcript_commit = builder.build()?;
|
||||
|
||||
prover.transcript_commit(config);
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
let request_config = RequestConfig::default();
|
||||
let (attestation, secrets) = prover.finalize(&request_config).await?;
|
||||
builder.transcript_commit(transcript_commit);
|
||||
|
||||
let request_config = builder.build()?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let (attestation, secrets) = prover.notarize(&request_config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
info!("notarization complete");
|
||||
|
||||
@@ -143,15 +145,24 @@ impl JsProver {
|
||||
|
||||
/// Reveals data to the verifier and finalizes the protocol.
|
||||
pub async fn reveal(&mut self, reveal: Reveal) -> Result<()> {
|
||||
let mut prover = self.state.take().try_into_closed()?.start_prove();
|
||||
let mut prover = self.state.take().try_into_committed()?;
|
||||
|
||||
info!("revealing data");
|
||||
|
||||
let sent = Idx::new(reveal.sent);
|
||||
let recv = Idx::new(reveal.recv);
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
prover.prove_transcript(sent, recv).await?;
|
||||
prover.finalize().await?;
|
||||
for range in reveal.sent {
|
||||
builder.reveal_sent(&range)?;
|
||||
}
|
||||
|
||||
for range in reveal.recv {
|
||||
builder.reveal_recv(&range)?;
|
||||
}
|
||||
|
||||
let config = builder.build()?;
|
||||
|
||||
prover.prove(&config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
info!("Finalized");
|
||||
|
||||
|
||||
@@ -304,9 +304,9 @@ pub struct NotarizationOutput {
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct VerifierOutput {
|
||||
pub server_name: String,
|
||||
pub server_name: Option<String>,
|
||||
pub connection_info: ConnectionInfo,
|
||||
pub transcript: PartialTranscript,
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use config::VerifierConfig;
|
||||
use enum_try_as_inner::EnumTryAsInner;
|
||||
use tlsn_verifier::{
|
||||
state::{self, Initialized},
|
||||
Verifier,
|
||||
Verifier, VerifyConfig,
|
||||
};
|
||||
use tracing::info;
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -69,14 +69,19 @@ impl JsVerifier {
|
||||
pub async fn verify(&mut self) -> Result<VerifierOutput> {
|
||||
let (verifier, prover_conn) = self.state.take().try_into_connected()?;
|
||||
|
||||
let (transcript, info) = verifier.verify(prover_conn.into_io()).await?;
|
||||
let mut verifier = verifier.setup(prover_conn.into_io()).await?.run().await?;
|
||||
|
||||
let connection_info = verifier.connection_info().clone();
|
||||
|
||||
let output = verifier.verify(&VerifyConfig::default()).await?;
|
||||
verifier.close().await?;
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(VerifierOutput {
|
||||
server_name: info.server_name.as_str().to_string(),
|
||||
connection_info: info.connection_info.into(),
|
||||
transcript: transcript.into(),
|
||||
server_name: output.server_name.map(|s| s.as_str().to_string()),
|
||||
connection_info: connection_info.into(),
|
||||
transcript: output.transcript.map(|t| t.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user