tlsn_core/
request.rs

1//! Attestation requests.
2//!
3//! After the TLS connection, a Prover can request an attestation from the
4//! Notary which contains various information about the connection. During this
5//! process the Prover has the opportunity to configure certain aspects of the
6//! attestation, such as which signature algorithm the Notary should use to sign
7//! the attestation. Or which hash algorithm the Notary should use to merkelize
8//! the fields.
9//!
10//! A [`Request`] can be created using a [`RequestBuilder`]. The builder will
11//! take both configuration via a [`RequestConfig`] as well as the Prover's
12//! secret data. The [`Secrets`](crate::Secrets) are of course not shared with
13//! the Notary but are used to create commitments which are included in the
14//! attestation.
15
16mod builder;
17mod config;
18
19use serde::{Deserialize, Serialize};
20
21use crate::{
22    attestation::{Attestation, Extension},
23    connection::ServerCertCommitment,
24    hash::{HashAlgId, TypedHash},
25    signing::SignatureAlgId,
26};
27
28pub use builder::{RequestBuilder, RequestBuilderError};
29pub use config::{RequestConfig, RequestConfigBuilder, RequestConfigBuilderError};
30
31/// Attestation request.
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Request {
34    pub(crate) signature_alg: SignatureAlgId,
35    pub(crate) hash_alg: HashAlgId,
36    pub(crate) server_cert_commitment: ServerCertCommitment,
37    pub(crate) encoding_commitment_root: Option<TypedHash>,
38    pub(crate) extensions: Vec<Extension>,
39}
40
41impl Request {
42    /// Returns a new request builder.
43    pub fn builder(config: &RequestConfig) -> RequestBuilder {
44        RequestBuilder::new(config)
45    }
46
47    /// Validates the content of the attestation against this request.
48    pub fn validate(&self, attestation: &Attestation) -> Result<(), InconsistentAttestation> {
49        if attestation.signature.alg != self.signature_alg {
50            return Err(InconsistentAttestation(format!(
51                "signature algorithm: expected {:?}, got {:?}",
52                self.signature_alg, attestation.signature.alg
53            )));
54        }
55
56        if attestation.header.root.alg != self.hash_alg {
57            return Err(InconsistentAttestation(format!(
58                "hash algorithm: expected {:?}, got {:?}",
59                self.hash_alg, attestation.header.root.alg
60            )));
61        }
62
63        if attestation.body.cert_commitment() != &self.server_cert_commitment {
64            return Err(InconsistentAttestation(
65                "server certificate commitment does not match".to_string(),
66            ));
67        }
68
69        if let Some(encoding_commitment_root) = &self.encoding_commitment_root {
70            let Some(encoding_commitment) = attestation.body.encoding_commitment() else {
71                return Err(InconsistentAttestation(
72                    "encoding commitment is missing".to_string(),
73                ));
74            };
75
76            if &encoding_commitment.root != encoding_commitment_root {
77                return Err(InconsistentAttestation(
78                    "encoding commitment root does not match".to_string(),
79                ));
80            }
81        }
82
83        // TODO: improve the O(M*N) complexity of this check.
84        for extension in &self.extensions {
85            if !attestation.body.extensions().any(|e| e == extension) {
86                return Err(InconsistentAttestation(
87                    "extension is missing from the attestation".to_string(),
88                ));
89            }
90        }
91
92        Ok(())
93    }
94}
95
96/// Error for [`Request::validate`].
97#[derive(Debug, thiserror::Error)]
98#[error("inconsistent attestation: {0}")]
99pub struct InconsistentAttestation(String);
100
101#[cfg(test)]
102mod test {
103    use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
104
105    use super::*;
106
107    use crate::{
108        connection::{ServerCertOpening, TranscriptLength},
109        fixtures::{
110            attestation_fixture, encoder_secret, encoding_provider, request_fixture,
111            ConnectionFixture, RequestFixture,
112        },
113        hash::{Blake3, Hash, HashAlgId},
114        signing::SignatureAlgId,
115        transcript::Transcript,
116        CryptoProvider,
117    };
118
119    #[test]
120    fn test_success() {
121        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
122        let connection = ConnectionFixture::tlsnotary(transcript.length());
123
124        let RequestFixture { request, .. } = request_fixture(
125            transcript,
126            encoding_provider(GET_WITH_HEADER, OK_JSON),
127            connection.clone(),
128            Blake3::default(),
129            Vec::new(),
130        );
131
132        let attestation = attestation_fixture(
133            request.clone(),
134            connection,
135            SignatureAlgId::SECP256K1,
136            encoder_secret(),
137        );
138
139        assert!(request.validate(&attestation).is_ok())
140    }
141
142    #[test]
143    fn test_wrong_signature_alg() {
144        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
145        let connection = ConnectionFixture::tlsnotary(transcript.length());
146
147        let RequestFixture { mut request, .. } = request_fixture(
148            transcript,
149            encoding_provider(GET_WITH_HEADER, OK_JSON),
150            connection.clone(),
151            Blake3::default(),
152            Vec::new(),
153        );
154
155        let attestation = attestation_fixture(
156            request.clone(),
157            connection,
158            SignatureAlgId::SECP256K1,
159            encoder_secret(),
160        );
161
162        request.signature_alg = SignatureAlgId::SECP256R1;
163
164        let res = request.validate(&attestation);
165        assert!(res.is_err());
166    }
167
168    #[test]
169    fn test_wrong_hash_alg() {
170        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
171        let connection = ConnectionFixture::tlsnotary(transcript.length());
172
173        let RequestFixture { mut request, .. } = request_fixture(
174            transcript,
175            encoding_provider(GET_WITH_HEADER, OK_JSON),
176            connection.clone(),
177            Blake3::default(),
178            Vec::new(),
179        );
180
181        let attestation = attestation_fixture(
182            request.clone(),
183            connection,
184            SignatureAlgId::SECP256K1,
185            encoder_secret(),
186        );
187
188        request.hash_alg = HashAlgId::SHA256;
189
190        let res = request.validate(&attestation);
191        assert!(res.is_err())
192    }
193
194    #[test]
195    fn test_wrong_server_commitment() {
196        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
197        let connection = ConnectionFixture::tlsnotary(transcript.length());
198
199        let RequestFixture { mut request, .. } = request_fixture(
200            transcript,
201            encoding_provider(GET_WITH_HEADER, OK_JSON),
202            connection.clone(),
203            Blake3::default(),
204            Vec::new(),
205        );
206
207        let attestation = attestation_fixture(
208            request.clone(),
209            connection,
210            SignatureAlgId::SECP256K1,
211            encoder_secret(),
212        );
213
214        let ConnectionFixture {
215            server_cert_data, ..
216        } = ConnectionFixture::appliedzkp(TranscriptLength {
217            sent: 100,
218            received: 100,
219        });
220        let opening = ServerCertOpening::new(server_cert_data);
221
222        let crypto_provider = CryptoProvider::default();
223        request.server_cert_commitment =
224            opening.commit(crypto_provider.hash.get(&HashAlgId::BLAKE3).unwrap());
225
226        let res = request.validate(&attestation);
227        assert!(res.is_err())
228    }
229
230    #[test]
231    fn test_wrong_encoding_commitment_root() {
232        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
233        let connection = ConnectionFixture::tlsnotary(transcript.length());
234
235        let RequestFixture { mut request, .. } = request_fixture(
236            transcript,
237            encoding_provider(GET_WITH_HEADER, OK_JSON),
238            connection.clone(),
239            Blake3::default(),
240            Vec::new(),
241        );
242
243        let attestation = attestation_fixture(
244            request.clone(),
245            connection,
246            SignatureAlgId::SECP256K1,
247            encoder_secret(),
248        );
249
250        request.encoding_commitment_root = Some(TypedHash {
251            alg: HashAlgId::BLAKE3,
252            value: Hash::default(),
253        });
254
255        let res = request.validate(&attestation);
256        assert!(res.is_err())
257    }
258}