actpub_httpsig/cavage/
verify.rs1use base64ct::{Base64, Encoding};
4use chrono::{DateTime, Utc};
5use http::Request;
6
7use crate::cavage::canonical::{Timestamps, build_signature_base};
8use crate::cavage::header::{CavageHeaderParams, SIGNATURE_HEADER};
9use crate::error::Error;
10use crate::key::{Algorithm, VerifyingKey};
11use crate::policy::VerifyPolicy;
12
13#[derive(Debug, Clone)]
15#[non_exhaustive]
16pub struct CavageVerified {
17 pub key_id: String,
19 pub algorithm: Option<String>,
21 pub signature_base: String,
23}
24
25pub fn cavage_verify<B, F>(req: &Request<B>, resolve_key: F) -> Result<CavageVerified, Error>
41where
42 F: FnOnce(&str) -> Result<VerifyingKey, Error>,
43{
44 cavage_verify_with_policy(
45 req,
46 &VerifyPolicy::no_freshness_check(),
47 Utc::now(),
48 resolve_key,
49 )
50}
51
52pub fn cavage_verify_with_policy<B, F>(
65 req: &Request<B>,
66 policy: &VerifyPolicy,
67 now: DateTime<Utc>,
68 resolve_key: F,
69) -> Result<CavageVerified, Error>
70where
71 F: FnOnce(&str) -> Result<VerifyingKey, Error>,
72{
73 let header = req
74 .headers()
75 .get(SIGNATURE_HEADER)
76 .ok_or(Error::MissingHeader(SIGNATURE_HEADER))?;
77 let raw = header.to_str().map_err(|e| Error::InvalidHeader {
78 name: SIGNATURE_HEADER,
79 reason: e.to_string(),
80 })?;
81
82 let params = CavageHeaderParams::parse(raw)?;
83
84 let date_header = req
88 .headers()
89 .get(http::header::DATE)
90 .and_then(|v| v.to_str().ok());
91 policy.check(params.created, params.expires, date_header, now)?;
92
93 let key = resolve_key(¶ms.key_id).map_err(|e| Error::KeyResolution(e.to_string()))?;
94
95 if let Some(hint) = params.algorithm.as_deref()
97 && let Some(hinted) = Algorithm::parse(hint)?
98 && hinted != key.algorithm()
99 {
100 return Err(Error::VerificationFailed);
101 }
102
103 let base = build_signature_base(
104 req,
105 ¶ms.headers,
106 Timestamps {
107 created: params.created,
108 expires: params.expires,
109 },
110 )?;
111
112 let mut sig_bytes = vec![0u8; params.signature.len()];
113 let sig = Base64::decode(¶ms.signature, &mut sig_bytes)?;
114 key.verify(base.as_bytes(), sig)?;
115
116 Ok(CavageVerified {
117 key_id: params.key_id,
118 algorithm: params.algorithm,
119 signature_base: base,
120 })
121}
122
123#[cfg(test)]
124mod tests {
125 use http::{Method, Request};
126 use pretty_assertions::assert_eq;
127
128 use super::*;
129 use crate::cavage::sign::CavageSigner;
130 use crate::digest::sha256_digest_header;
131 use crate::key::{RsaBits, SigningKey};
132
133 fn sample_signed_request(key: &SigningKey, body: &[u8]) -> Request<Vec<u8>> {
134 let mut req = Request::builder()
135 .method(Method::POST)
136 .uri("https://example.com/inbox?a=1")
137 .header("host", "example.com")
138 .header("date", "Sun, 05 Jan 2014 21:31:40 GMT")
139 .header("digest", sha256_digest_header(body))
140 .header("content-type", "application/activity+json")
141 .body(body.to_vec())
142 .expect("valid");
143 CavageSigner::new(key, "https://example.com/actors/alice#main-key")
144 .sign(&mut req)
145 .expect("sign");
146 req
147 }
148
149 #[test]
150 fn ed25519_signature_roundtrips_sign_then_verify() {
151 let key = SigningKey::generate_ed25519();
152 let public = key.verifying_key();
153 let req = sample_signed_request(&key, b"{}");
154
155 let report = cavage_verify(&req, |kid| {
156 assert_eq!(kid, "https://example.com/actors/alice#main-key");
157 Ok(public.clone())
158 })
159 .expect("verify must succeed");
160
161 assert_eq!(report.key_id, "https://example.com/actors/alice#main-key");
162 assert!(
163 report
164 .signature_base
165 .contains("(request-target): post /inbox?a=1")
166 );
167 }
168
169 #[test]
170 fn rsa_sha256_signature_roundtrips_sign_then_verify() {
171 let key = SigningKey::generate_rsa(RsaBits::Rsa2048).expect("rng");
172 let public = key.verifying_key();
173 let req = sample_signed_request(&key, b"{}");
174 cavage_verify(&req, |_| Ok(public.clone())).expect("verify must succeed");
175 }
176
177 #[test]
178 fn tampered_body_fails_verification_via_digest_loop() {
179 let key = SigningKey::generate_ed25519();
186 let public = key.verifying_key();
187 let mut req = sample_signed_request(&key, b"original");
188 *req.body_mut() = b"tampered".to_vec();
189 cavage_verify(&req, |_| Ok(public.clone()))
190 .expect("signature alone does not depend on body bytes");
191 }
192
193 #[test]
194 fn tampered_date_header_fails_verification() {
195 let key = SigningKey::generate_ed25519();
196 let public = key.verifying_key();
197 let mut req = sample_signed_request(&key, b"{}");
198 req.headers_mut().insert(
199 "date",
200 "Mon, 06 Jan 2014 00:00:00 GMT".parse().expect("valid"),
201 );
202 let err = cavage_verify(&req, |_| Ok(public.clone())).expect_err("tampered date must fail");
203 assert!(matches!(err, Error::VerificationFailed));
204 }
205
206 #[test]
207 fn missing_signature_header_is_reported() {
208 let req: Request<Vec<u8>> = Request::builder()
209 .method(Method::POST)
210 .uri("https://example.com/inbox")
211 .body(Vec::new())
212 .unwrap();
213 let err = cavage_verify(&req, |_| panic!("resolver must not be called"))
214 .expect_err("missing Signature header");
215 assert!(matches!(err, Error::MissingHeader("signature")));
216 }
217
218 #[test]
219 fn key_resolver_error_is_surfaced() {
220 let key = SigningKey::generate_ed25519();
221 let req = sample_signed_request(&key, b"{}");
222 let err =
223 cavage_verify(&req, |_| Err(Error::VerificationFailed)).expect_err("resolver failed");
224 assert!(matches!(err, Error::KeyResolution(_)));
225 }
226
227 #[test]
228 fn algorithm_mismatch_between_hint_and_key_rejects() {
229 let key = SigningKey::generate_ed25519();
231 let public_rsa = SigningKey::generate_rsa(RsaBits::Rsa2048)
232 .expect("rng")
233 .verifying_key();
234 let mut req = sample_signed_request(&key, b"{}");
235 let original_header = req
236 .headers()
237 .get(SIGNATURE_HEADER)
238 .unwrap()
239 .to_str()
240 .unwrap()
241 .replace(r#"algorithm="ed25519""#, r#"algorithm="rsa-sha256""#);
242 req.headers_mut()
243 .insert(SIGNATURE_HEADER, original_header.parse().unwrap());
244
245 let err = cavage_verify(&req, |_| Ok(public_rsa.clone()))
246 .expect_err("algorithm mismatch must fail");
247 assert!(matches!(err, Error::VerificationFailed));
248 }
249}