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