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,
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) extensions: Vec<Extension>,
38}
39
40impl Request {
41    /// Returns a new request builder.
42    pub fn builder(config: &RequestConfig) -> RequestBuilder {
43        RequestBuilder::new(config)
44    }
45
46    /// Validates the content of the attestation against this request.
47    pub fn validate(&self, attestation: &Attestation) -> Result<(), InconsistentAttestation> {
48        if attestation.signature.alg != self.signature_alg {
49            return Err(InconsistentAttestation(format!(
50                "signature algorithm: expected {:?}, got {:?}",
51                self.signature_alg, attestation.signature.alg
52            )));
53        }
54
55        if attestation.header.root.alg != self.hash_alg {
56            return Err(InconsistentAttestation(format!(
57                "hash algorithm: expected {:?}, got {:?}",
58                self.hash_alg, attestation.header.root.alg
59            )));
60        }
61
62        if attestation.body.cert_commitment() != &self.server_cert_commitment {
63            return Err(InconsistentAttestation(
64                "server certificate commitment does not match".to_string(),
65            ));
66        }
67
68        // TODO: improve the O(M*N) complexity of this check.
69        for extension in &self.extensions {
70            if !attestation.body.extensions().any(|e| e == extension) {
71                return Err(InconsistentAttestation(
72                    "extension is missing from the attestation".to_string(),
73                ));
74            }
75        }
76
77        Ok(())
78    }
79}
80
81/// Error for [`Request::validate`].
82#[derive(Debug, thiserror::Error)]
83#[error("inconsistent attestation: {0}")]
84pub struct InconsistentAttestation(String);
85
86#[cfg(test)]
87mod test {
88    use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
89
90    use crate::{
91        connection::{ServerCertOpening, TranscriptLength},
92        fixtures::{
93            attestation_fixture, encoding_provider, request_fixture, ConnectionFixture,
94            RequestFixture,
95        },
96        hash::{Blake3, HashAlgId},
97        signing::SignatureAlgId,
98        transcript::Transcript,
99        CryptoProvider,
100    };
101
102    #[test]
103    fn test_success() {
104        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
105        let connection = ConnectionFixture::tlsnotary(transcript.length());
106
107        let RequestFixture { request, .. } = request_fixture(
108            transcript,
109            encoding_provider(GET_WITH_HEADER, OK_JSON),
110            connection.clone(),
111            Blake3::default(),
112            Vec::new(),
113        );
114
115        let attestation =
116            attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
117
118        assert!(request.validate(&attestation).is_ok())
119    }
120
121    #[test]
122    fn test_wrong_signature_alg() {
123        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
124        let connection = ConnectionFixture::tlsnotary(transcript.length());
125
126        let RequestFixture { mut request, .. } = request_fixture(
127            transcript,
128            encoding_provider(GET_WITH_HEADER, OK_JSON),
129            connection.clone(),
130            Blake3::default(),
131            Vec::new(),
132        );
133
134        let attestation =
135            attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
136
137        request.signature_alg = SignatureAlgId::SECP256R1;
138
139        let res = request.validate(&attestation);
140        assert!(res.is_err());
141    }
142
143    #[test]
144    fn test_wrong_hash_alg() {
145        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
146        let connection = ConnectionFixture::tlsnotary(transcript.length());
147
148        let RequestFixture { mut request, .. } = request_fixture(
149            transcript,
150            encoding_provider(GET_WITH_HEADER, OK_JSON),
151            connection.clone(),
152            Blake3::default(),
153            Vec::new(),
154        );
155
156        let attestation =
157            attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
158
159        request.hash_alg = HashAlgId::SHA256;
160
161        let res = request.validate(&attestation);
162        assert!(res.is_err())
163    }
164
165    #[test]
166    fn test_wrong_server_commitment() {
167        let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
168        let connection = ConnectionFixture::tlsnotary(transcript.length());
169
170        let RequestFixture { mut request, .. } = request_fixture(
171            transcript,
172            encoding_provider(GET_WITH_HEADER, OK_JSON),
173            connection.clone(),
174            Blake3::default(),
175            Vec::new(),
176        );
177
178        let attestation =
179            attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
180
181        let ConnectionFixture {
182            server_cert_data, ..
183        } = ConnectionFixture::appliedzkp(TranscriptLength {
184            sent: 100,
185            received: 100,
186        });
187        let opening = ServerCertOpening::new(server_cert_data);
188
189        let crypto_provider = CryptoProvider::default();
190        request.server_cert_commitment =
191            opening.commit(crypto_provider.hash.get(&HashAlgId::BLAKE3).unwrap());
192
193        let res = request.validate(&attestation);
194        assert!(res.is_err())
195    }
196}