1mod 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#[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 pub fn builder(config: &RequestConfig) -> RequestBuilder {
44 RequestBuilder::new(config)
45 }
46
47 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 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#[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}