1use crate::httpsig::components::{extract_authority, extract_method, extract_path};
4use crate::httpsig::digest::verify_content_digest;
5use crate::httpsig::error::HttpSigError;
6use crate::httpsig::parser::{parse_signature, parse_signature_input};
7use crate::httpsig::signature_base::build_signature_base;
8use crate::keys::{Ed25519PublicKey, Signature};
9use std::time::{SystemTime, UNIX_EPOCH};
10
11#[derive(Debug, Clone)]
13pub struct VerificationResult {
14 pub key_id: String,
16 pub algorithm: String,
18 pub created: u64,
20 pub signed_components: Vec<String>,
22}
23
24#[cfg(not(feature = "rhai"))]
26pub(crate) type KeyGetter =
27 Box<dyn Fn(&str) -> Result<Ed25519PublicKey, HttpSigError> + Send + Sync>;
28
29#[cfg_attr(feature = "rhai", derive(Clone))]
49pub struct HttpVerifier {
50 #[cfg(not(feature = "rhai"))]
51 key_getter: Option<KeyGetter>,
52 default_key: Option<Ed25519PublicKey>,
53 tolerance_secs: u64,
54 required_components: Vec<String>,
55}
56
57impl HttpVerifier {
58 pub fn new() -> Self {
63 Self {
64 #[cfg(not(feature = "rhai"))]
65 key_getter: None,
66 default_key: None,
67 tolerance_secs: 60, required_components: vec![
69 "@method".to_string(),
70 "@path".to_string(),
71 "@authority".to_string(),
72 "content-digest".to_string(),
73 ],
74 }
75 }
76
77 pub fn with_key(mut self, public_key: Ed25519PublicKey) -> Self {
79 self.default_key = Some(public_key);
80 self
81 }
82
83 #[cfg(not(feature = "rhai"))]
104 pub fn with_key_getter(mut self, getter: KeyGetter) -> Self {
105 self.key_getter = Some(getter);
106 self
107 }
108
109 pub fn with_tolerance(mut self, seconds: u64) -> Self {
114 self.tolerance_secs = seconds;
115 self
116 }
117
118 pub fn with_required_components(mut self, components: Vec<String>) -> Self {
120 self.required_components.extend(components);
121 self
122 }
123
124 pub fn verify_request<B>(
133 &self,
134 request: &http::Request<B>,
135 body: &[u8],
136 ) -> Result<VerificationResult, HttpSigError> {
137 let method = request.method().as_str();
139 let uri = request.uri();
140 let path = uri.path();
141 let authority = uri
142 .authority()
143 .ok_or(HttpSigError::MissingAuthority)?
144 .as_str();
145
146 let headers: Vec<(String, String)> = request
148 .headers()
149 .iter()
150 .map(|(k, v)| {
151 (
152 k.as_str().to_string(),
153 v.to_str().unwrap_or("").to_string(),
154 )
155 })
156 .collect();
157
158 self.verify_components(method, path, authority, &headers, body)
160 }
161
162 pub fn verify_response<B>(
171 &self,
172 response: &http::Response<B>,
173 body: &[u8],
174 ) -> Result<VerificationResult, HttpSigError> {
175 let method = "POST";
177 let path = "/";
178 let authority = "response.local";
179
180 let headers: Vec<(String, String)> = response
182 .headers()
183 .iter()
184 .map(|(k, v)| {
185 (
186 k.as_str().to_string(),
187 v.to_str().unwrap_or("").to_string(),
188 )
189 })
190 .collect();
191
192 self.verify_components(method, path, authority, &headers, body)
194 }
195
196 pub(crate) fn verify_components(
201 &self,
202 method: &str,
203 path: &str,
204 authority: &str,
205 headers: &[(String, String)],
206 body: &[u8],
207 ) -> Result<VerificationResult, HttpSigError> {
208 let signature_input = self.find_header(headers, "signature-input")?;
210 let signature_header = self.find_header(headers, "signature")?;
211 let content_digest = self.find_header(headers, "content-digest")?;
212
213 let params = parse_signature_input(signature_input)?;
215
216 if params.alg != "ed25519" {
218 return Err(HttpSigError::UnsupportedAlgorithm(params.alg.clone()));
219 }
220
221 for required in &self.required_components {
223 if !params.components.contains(required) {
224 return Err(HttpSigError::MissingComponent(required.clone()));
225 }
226 }
227
228 self.verify_timestamp(params.created)?;
230
231 verify_content_digest(body, content_digest)?;
233
234 let public_key = self.get_public_key(¶ms.keyid)?;
236
237 let method = extract_method(method);
239 let path = extract_path(path)?;
240 let authority = extract_authority(authority);
241
242 let extra_headers: Vec<(String, String)> = params
244 .components
245 .iter()
246 .filter(|c| !c.starts_with('@') && *c != "content-digest")
247 .filter_map(|name| {
248 headers
249 .iter()
250 .find(|(h, _)| h.eq_ignore_ascii_case(name))
251 .map(|(_, v)| (name.clone(), v.clone()))
252 })
253 .collect();
254
255 let sig_base = build_signature_base(
257 &method,
258 &path,
259 &authority,
260 content_digest,
261 &extra_headers,
262 ¶ms.keyid,
263 params.created,
264 )?;
265
266 let canonical = sig_base.to_canonical_string();
268
269 let sig_bytes = parse_signature(signature_header, ¶ms.label)?;
271 let signature = Signature::from_bytes(&sig_bytes)?;
272
273 let valid = public_key.verify(canonical.as_bytes(), &signature)?;
275
276 if !valid {
277 return Err(HttpSigError::VerificationFailed);
278 }
279
280 Ok(VerificationResult {
281 key_id: params.keyid,
282 algorithm: params.alg,
283 created: params.created,
284 signed_components: params.components,
285 })
286 }
287
288 fn find_header<'a>(
289 &self,
290 headers: &'a [(String, String)],
291 name: &str,
292 ) -> Result<&'a str, HttpSigError> {
293 headers
294 .iter()
295 .find(|(k, _)| k.eq_ignore_ascii_case(name))
296 .map(|(_, v)| v.as_str())
297 .ok_or_else(|| HttpSigError::MissingHeader(name.to_string()))
298 }
299
300 fn verify_timestamp(&self, created: u64) -> Result<(), HttpSigError> {
301 let now = SystemTime::now()
302 .duration_since(UNIX_EPOCH)
303 .map_err(|_| HttpSigError::ParseError("System time error".to_string()))?
304 .as_secs();
305
306 let diff = if now > created {
307 now - created
308 } else {
309 created - now
310 };
311
312 if diff > self.tolerance_secs {
313 return Err(HttpSigError::TimestampOutOfBounds {
314 created,
315 now,
316 tolerance: self.tolerance_secs,
317 });
318 }
319
320 Ok(())
321 }
322
323 fn get_public_key(&self, _key_id: &str) -> Result<Ed25519PublicKey, HttpSigError> {
324 #[cfg(not(feature = "rhai"))]
325 if let Some(getter) = &self.key_getter {
326 return getter(_key_id);
327 }
328
329 if let Some(key) = &self.default_key {
330 return Ok(key.clone());
331 }
332
333 Err(HttpSigError::NoKeyConfigured)
334 }
335}
336
337impl Default for HttpVerifier {
338 fn default() -> Self {
339 Self::new()
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use crate::httpsig::signer::HttpSigner;
347 use crate::keys::Ed25519Keypair;
348 use http::Request;
349
350 #[test]
351 fn test_sign_and_verify_roundtrip() {
352 let keypair = Ed25519Keypair::generate().unwrap();
353 let public_key = keypair.public_key();
354
355 let signer = HttpSigner::new(keypair, "test-key");
356 let verifier = HttpVerifier::new().with_key(public_key).with_tolerance(60);
357
358 let body = b"test body";
359 let mut request = Request::post("https://example.com/api/test")
360 .body(body.to_vec())
361 .unwrap();
362
363 signer.sign_request(&mut request, body).unwrap();
364
365 let result = verifier.verify_request(&request, body).unwrap();
366
367 assert_eq!(result.key_id, "test-key");
368 assert_eq!(result.algorithm, "ed25519");
369 }
370
371 #[test]
372 fn test_verify_wrong_body() {
373 let keypair = Ed25519Keypair::generate().unwrap();
374 let public_key = keypair.public_key();
375
376 let signer = HttpSigner::new(keypair, "test-key");
377 let verifier = HttpVerifier::new().with_key(public_key);
378
379 let original_body = b"original";
380 let mut request = Request::post("https://example.com/api/test")
381 .body(original_body.to_vec())
382 .unwrap();
383
384 signer.sign_request(&mut request, original_body).unwrap();
385
386 let result = verifier.verify_request(&request, b"tampered");
388
389 assert!(matches!(result, Err(HttpSigError::DigestMismatch)));
390 }
391
392 #[test]
393 fn test_verify_wrong_signature() {
394 let keypair1 = Ed25519Keypair::generate().unwrap();
395 let keypair2 = Ed25519Keypair::generate().unwrap();
396 let wrong_key = keypair2.public_key();
397
398 let signer = HttpSigner::new(keypair1, "test-key");
399 let verifier = HttpVerifier::new().with_key(wrong_key);
400
401 let body = b"test";
402 let mut request = Request::post("https://example.com/api/test")
403 .body(body.to_vec())
404 .unwrap();
405
406 signer.sign_request(&mut request, body).unwrap();
407
408 let result = verifier.verify_request(&request, body);
410
411 assert!(matches!(result, Err(HttpSigError::VerificationFailed)));
412 }
413}