1use base64ct::{Base64, Encoding};
4use chrono::{DateTime, Utc};
5use http::Request;
6
7use crate::cavage::canonical::{CavageHeaderSet, 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 enforce_required_headers(¶ms.headers, policy.cavage_required_headers)?;
89
90 let date_header = req
94 .headers()
95 .get(http::header::DATE)
96 .and_then(|v| v.to_str().ok());
97 policy.check(params.created, params.expires, date_header, now)?;
98
99 let key = resolve_key(¶ms.key_id).map_err(|e| Error::KeyResolution(e.to_string()))?;
100
101 if let Some(hint) = params.algorithm.as_deref()
103 && let Some(hinted) = Algorithm::parse(hint)?
104 && hinted != key.algorithm()
105 {
106 return Err(Error::VerificationFailed);
107 }
108
109 let base = build_signature_base(
110 req,
111 ¶ms.headers,
112 Timestamps {
113 created: params.created,
114 expires: params.expires,
115 },
116 )?;
117
118 let mut sig_bytes = vec![0u8; params.signature.len()];
119 let sig = Base64::decode(¶ms.signature, &mut sig_bytes)?;
120 key.verify(base.as_bytes(), sig)?;
121
122 Ok(CavageVerified {
123 key_id: params.key_id,
124 algorithm: params.algorithm,
125 signature_base: base,
126 })
127}
128
129fn enforce_required_headers(signed: &CavageHeaderSet, required: &[&str]) -> Result<(), Error> {
133 for needed in required {
134 let present = signed.iter().any(|h| h.eq_ignore_ascii_case(needed));
135 if !present {
136 return Err(Error::RequiredHeaderAbsent((*needed).to_owned()));
137 }
138 }
139 Ok(())
140}
141
142#[cfg(test)]
143mod tests {
144 use http::{Method, Request};
145 use pretty_assertions::assert_eq;
146
147 use super::*;
148 use crate::cavage::sign::CavageSigner;
149 use crate::digest::sha256_digest_header;
150 use crate::key::{RsaBits, SigningKey};
151
152 fn sample_signed_request(key: &SigningKey, body: &[u8]) -> Request<Vec<u8>> {
153 let mut req = Request::builder()
154 .method(Method::POST)
155 .uri("https://example.com/inbox?a=1")
156 .header("host", "example.com")
157 .header("date", "Sun, 05 Jan 2014 21:31:40 GMT")
158 .header("digest", sha256_digest_header(body))
159 .header("content-type", "application/activity+json")
160 .body(body.to_vec())
161 .expect("valid");
162 CavageSigner::new(key, "https://example.com/actors/alice#main-key")
163 .sign(&mut req)
164 .expect("sign");
165 req
166 }
167
168 #[test]
169 fn ed25519_signature_roundtrips_sign_then_verify() {
170 let key = SigningKey::generate_ed25519();
171 let public = key.verifying_key();
172 let req = sample_signed_request(&key, b"{}");
173
174 let report = cavage_verify(&req, |kid| {
175 assert_eq!(kid, "https://example.com/actors/alice#main-key");
176 Ok(public.clone())
177 })
178 .expect("verify must succeed");
179
180 assert_eq!(report.key_id, "https://example.com/actors/alice#main-key");
181 assert!(
182 report
183 .signature_base
184 .contains("(request-target): post /inbox?a=1")
185 );
186 }
187
188 #[test]
189 fn rsa_sha256_signature_roundtrips_sign_then_verify() {
190 let key = SigningKey::generate_rsa(RsaBits::Rsa2048).expect("rng");
191 let public = key.verifying_key();
192 let req = sample_signed_request(&key, b"{}");
193 cavage_verify(&req, |_| Ok(public.clone())).expect("verify must succeed");
194 }
195
196 #[test]
197 fn tampered_body_fails_verification_via_digest_loop() {
198 let key = SigningKey::generate_ed25519();
205 let public = key.verifying_key();
206 let mut req = sample_signed_request(&key, b"original");
207 *req.body_mut() = b"tampered".to_vec();
208 cavage_verify(&req, |_| Ok(public.clone()))
209 .expect("signature alone does not depend on body bytes");
210 }
211
212 #[test]
213 fn tampered_date_header_fails_verification() {
214 let key = SigningKey::generate_ed25519();
215 let public = key.verifying_key();
216 let mut req = sample_signed_request(&key, b"{}");
217 req.headers_mut().insert(
218 "date",
219 "Mon, 06 Jan 2014 00:00:00 GMT".parse().expect("valid"),
220 );
221 let err = cavage_verify(&req, |_| Ok(public.clone())).expect_err("tampered date must fail");
222 assert!(matches!(err, Error::VerificationFailed));
223 }
224
225 #[test]
226 fn missing_signature_header_is_reported() {
227 let req: Request<Vec<u8>> = Request::builder()
228 .method(Method::POST)
229 .uri("https://example.com/inbox")
230 .body(Vec::new())
231 .unwrap();
232 let err = cavage_verify(&req, |_| panic!("resolver must not be called"))
233 .expect_err("missing Signature header");
234 assert!(matches!(err, Error::MissingHeader("signature")));
235 }
236
237 #[test]
238 fn key_resolver_error_is_surfaced() {
239 let key = SigningKey::generate_ed25519();
240 let req = sample_signed_request(&key, b"{}");
241 let err =
242 cavage_verify(&req, |_| Err(Error::VerificationFailed)).expect_err("resolver failed");
243 assert!(matches!(err, Error::KeyResolution(_)));
244 }
245
246 #[test]
247 fn signature_missing_required_host_header_is_rejected() {
248 let key = SigningKey::generate_ed25519();
252 let public = key.verifying_key();
253 let mut req = sample_signed_request(&key, b"{}");
254 CavageSigner::new(&key, "https://example.com/actors/alice#main-key")
256 .with_headers(["(request-target)", "date"])
257 .sign(&mut req)
258 .expect("sign");
259
260 let err = cavage_verify(&req, |_| Ok(public.clone()))
261 .expect_err("signature without `host` must be rejected");
262 assert!(matches!(err, Error::RequiredHeaderAbsent(name) if name == "host"));
263 }
264
265 #[test]
266 fn signature_missing_required_request_target_is_rejected() {
267 let key = SigningKey::generate_ed25519();
268 let public = key.verifying_key();
269 let mut req = sample_signed_request(&key, b"{}");
270 CavageSigner::new(&key, "kid")
271 .with_headers(["host", "date"])
272 .sign(&mut req)
273 .expect("sign");
274
275 let err = cavage_verify(&req, |_| Ok(public.clone()))
276 .expect_err("signature without `(request-target)` must be rejected");
277 assert!(matches!(err, Error::RequiredHeaderAbsent(name) if name == "(request-target)"));
278 }
279
280 #[test]
281 fn algorithm_mismatch_between_hint_and_key_rejects() {
282 let key = SigningKey::generate_ed25519();
284 let public_rsa = SigningKey::generate_rsa(RsaBits::Rsa2048)
285 .expect("rng")
286 .verifying_key();
287 let mut req = sample_signed_request(&key, b"{}");
288 let original_header = req
289 .headers()
290 .get(SIGNATURE_HEADER)
291 .unwrap()
292 .to_str()
293 .unwrap()
294 .replace(r#"algorithm="ed25519""#, r#"algorithm="rsa-sha256""#);
295 req.headers_mut()
296 .insert(SIGNATURE_HEADER, original_header.parse().unwrap());
297
298 let err = cavage_verify(&req, |_| Ok(public_rsa.clone()))
299 .expect_err("algorithm mismatch must fail");
300 assert!(matches!(err, Error::VerificationFailed));
301 }
302}