1use chrono::{DateTime, Utc};
4use http::Request;
5
6use crate::error::Error;
7use crate::key::{Algorithm, VerifyingKey};
8use crate::policy::VerifyPolicy;
9use crate::rfc9421::components::build_signature_base;
10use crate::rfc9421::signature::{SIGNATURE_HEADER, parse_signature_dict};
11use crate::rfc9421::signature_input::{
12 SIGNATURE_INPUT_HEADER, SignatureInput, parse_signature_input_dict,
13};
14
15#[derive(Debug, Clone)]
17#[non_exhaustive]
18pub struct Rfc9421Verified {
19 pub label: String,
21 pub input: SignatureInput,
23 pub signature_base: String,
25}
26
27pub fn rfc9421_verify<B, F>(req: &Request<B>, resolve_key: F) -> Result<Rfc9421Verified, Error>
41where
42 F: FnMut(&str) -> Result<VerifyingKey, Error>,
43{
44 rfc9421_verify_with_policy(
45 req,
46 &VerifyPolicy::no_freshness_check(),
47 Utc::now(),
48 resolve_key,
49 )
50}
51
52pub fn rfc9421_verify_with_policy<B, F>(
64 req: &Request<B>,
65 policy: &VerifyPolicy,
66 now: DateTime<Utc>,
67 mut resolve_key: F,
68) -> Result<Rfc9421Verified, Error>
69where
70 F: FnMut(&str) -> Result<VerifyingKey, Error>,
71{
72 let date_header = req
73 .headers()
74 .get(http::header::DATE)
75 .and_then(|v| v.to_str().ok())
76 .map(str::to_owned);
77 let input_raw = req
78 .headers()
79 .get(SIGNATURE_INPUT_HEADER)
80 .ok_or(Error::MissingHeader(SIGNATURE_INPUT_HEADER))?
81 .to_str()
82 .map_err(|e| Error::InvalidHeader {
83 name: SIGNATURE_INPUT_HEADER,
84 reason: e.to_string(),
85 })?;
86 let sig_raw = req
87 .headers()
88 .get(SIGNATURE_HEADER)
89 .ok_or(Error::MissingHeader(SIGNATURE_HEADER))?
90 .to_str()
91 .map_err(|e| Error::InvalidHeader {
92 name: SIGNATURE_HEADER,
93 reason: e.to_string(),
94 })?;
95
96 let inputs = parse_signature_input_dict(input_raw)?;
97 let sigs = parse_signature_dict(sig_raw)?;
98
99 if inputs.is_empty() {
100 return Err(Error::MalformedSignatureHeader(
101 "empty Signature-Input dictionary".into(),
102 ));
103 }
104
105 if !policy.allow_multiple_signatures && inputs.len() > 1 {
106 return Err(Error::MalformedSignatureHeader(format!(
107 "Signature-Input carries {} labels but policy allows only one",
108 inputs.len()
109 )));
110 }
111
112 let mut last_err: Option<Error> = None;
113 for (label, input) in inputs {
114 let Some((_, sig_bytes)) = sigs.iter().find(|(l, _)| l == &label) else {
115 last_err = Some(Error::MalformedSignatureHeader(format!(
116 "no Signature entry for label `{label}`"
117 )));
118 continue;
119 };
120
121 if let Err(e) = policy.check(input.created, input.expires, date_header.as_deref(), now) {
124 last_err = Some(e);
125 continue;
126 }
127
128 let Some(key_id) = input.keyid.as_deref() else {
129 last_err = Some(Error::MissingSignatureParameter("keyid"));
130 continue;
131 };
132
133 let key = match resolve_key(key_id) {
134 Ok(k) => k,
135 Err(e) => {
136 last_err = Some(Error::KeyResolution(e.to_string()));
137 continue;
138 }
139 };
140
141 if let Some(hint) = input.algorithm.as_deref() {
142 match parse_alg_hint(hint) {
143 Ok(Some(hinted)) if hinted != key.algorithm() => {
144 last_err = Some(Error::VerificationFailed);
145 continue;
146 }
147 Ok(_) => {}
148 Err(e) => {
149 last_err = Some(e);
150 continue;
151 }
152 }
153 }
154
155 let inner_list = input.serialise_inner_list();
156 let base = build_signature_base(req, &input.components, &inner_list)?;
157
158 if key.verify(base.as_bytes(), sig_bytes).is_err() {
159 last_err = Some(Error::VerificationFailed);
160 continue;
161 }
162
163 return Ok(Rfc9421Verified {
164 label,
165 input,
166 signature_base: base,
167 });
168 }
169
170 Err(last_err.unwrap_or(Error::VerificationFailed))
171}
172
173fn parse_alg_hint(hint: &str) -> Result<Option<Algorithm>, Error> {
174 match hint {
175 "rsa-v1_5-sha256" | "rsa-sha256" => Ok(Some(Algorithm::RsaSha256)),
176 "ed25519" => Ok(Some(Algorithm::Ed25519)),
177 other => Err(Error::UnsupportedAlgorithm(other.to_owned())),
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use http::{Method, Request};
184 use pretty_assertions::assert_eq;
185
186 use super::*;
187 use crate::content_digest::content_digest_header;
188 use crate::key::{RsaBits, SigningKey};
189 use crate::rfc9421::sign::Rfc9421Signer;
190
191 fn signed_request(key: &SigningKey) -> Request<Vec<u8>> {
192 let body = b"{}";
193 let mut req = Request::builder()
194 .method(Method::POST)
195 .uri("https://example.com/inbox?a=1")
196 .header("host", "example.com")
197 .header("date", "Sun, 05 Jan 2014 21:31:40 GMT")
198 .header("content-digest", content_digest_header(body))
199 .body(body.to_vec())
200 .expect("valid");
201 Rfc9421Signer::new(key, "https://example.com/actor#sig")
202 .with_created(1_700_000_000)
203 .sign(&mut req)
204 .expect("sign");
205 req
206 }
207
208 #[test]
209 fn ed25519_roundtrips_sign_then_verify() {
210 let key = SigningKey::generate_ed25519();
211 let public = key.verifying_key();
212 let req = signed_request(&key);
213
214 let report = rfc9421_verify(&req, |kid| {
215 assert_eq!(kid, "https://example.com/actor#sig");
216 Ok(public.clone())
217 })
218 .expect("verify");
219
220 assert_eq!(report.label, "sig1");
221 assert!(report.signature_base.contains(r#""@method": POST"#));
222 }
223
224 #[test]
225 fn rsa_sha256_roundtrips_sign_then_verify() {
226 let key = SigningKey::generate_rsa(RsaBits::Rsa2048).expect("rng");
227 let public = key.verifying_key();
228 let req = signed_request(&key);
229 rfc9421_verify(&req, |_| Ok(public.clone())).expect("verify");
230 }
231
232 #[test]
233 fn tampered_date_header_fails_verification() {
234 let key = SigningKey::generate_ed25519();
235 let public = key.verifying_key();
236 let mut req = signed_request(&key);
237 req.headers_mut().insert(
238 "date",
239 "Mon, 06 Jan 2014 00:00:00 GMT".parse().expect("valid"),
240 );
241 let err =
242 rfc9421_verify(&req, |_| Ok(public.clone())).expect_err("tampered date must fail");
243 assert!(matches!(err, Error::VerificationFailed));
244 }
245
246 #[test]
247 fn algorithm_mismatch_between_hint_and_key_is_rejected() {
248 let key = SigningKey::generate_ed25519();
249 let rsa_public = SigningKey::generate_rsa(RsaBits::Rsa2048)
251 .expect("rng")
252 .verifying_key();
253 let req = signed_request(&key);
254 let err =
255 rfc9421_verify(&req, |_| Ok(rsa_public.clone())).expect_err("mismatched alg must fail");
256 assert!(matches!(err, Error::VerificationFailed));
257 }
258
259 #[test]
260 fn missing_input_header_is_reported() {
261 let key = SigningKey::generate_ed25519();
262 let mut req = signed_request(&key);
263 req.headers_mut().remove(SIGNATURE_INPUT_HEADER);
264 let err = rfc9421_verify(&req, |_| panic!("resolver must not be called"))
265 .expect_err("missing input");
266 assert!(matches!(err, Error::MissingHeader(SIGNATURE_INPUT_HEADER)));
267 }
268
269 #[test]
270 fn missing_signature_header_is_reported() {
271 let key = SigningKey::generate_ed25519();
272 let mut req = signed_request(&key);
273 req.headers_mut().remove(SIGNATURE_HEADER);
274 let err = rfc9421_verify(&req, |_| panic!("resolver must not be called"))
275 .expect_err("missing signature");
276 assert!(matches!(err, Error::MissingHeader(SIGNATURE_HEADER)));
277 }
278
279 #[test]
280 fn multi_label_signature_input_is_rejected_by_default() {
281 let key = SigningKey::generate_ed25519();
285 let public = key.verifying_key();
286 let mut req = signed_request(&key);
287 let input_raw = req
289 .headers()
290 .get(SIGNATURE_INPUT_HEADER)
291 .unwrap()
292 .to_str()
293 .unwrap()
294 .to_owned()
295 + r", attacker=()";
296 req.headers_mut()
297 .insert(SIGNATURE_INPUT_HEADER, input_raw.parse().unwrap());
298
299 let err = rfc9421_verify(&req, |_| Ok(public.clone()))
300 .expect_err("multiple labels must be rejected");
301 assert!(matches!(err, Error::MalformedSignatureHeader(_)));
302 }
303
304 #[test]
305 fn multi_label_signature_input_is_accepted_when_policy_allows_it() {
306 use chrono::DateTime;
310
311 let key = SigningKey::generate_ed25519();
312 let public = key.verifying_key();
313 let mut req = signed_request(&key);
314 let input_raw = req
315 .headers()
316 .get(SIGNATURE_INPUT_HEADER)
317 .unwrap()
318 .to_str()
319 .unwrap()
320 .to_owned()
321 + r", attacker=()";
322 req.headers_mut()
323 .insert(SIGNATURE_INPUT_HEADER, input_raw.parse().unwrap());
324
325 let policy = VerifyPolicy {
326 allow_multiple_signatures: true,
327 ..VerifyPolicy::no_freshness_check()
328 };
329 rfc9421_verify_with_policy(
330 &req,
331 &policy,
332 DateTime::<Utc>::from_timestamp(1_700_000_000, 0).unwrap(),
333 |_| Ok(public.clone()),
334 )
335 .expect("the valid sig1 label must still verify");
336 }
337
338 #[test]
339 fn unknown_alg_hint_does_not_short_circuit_multi_label_verification() {
340 let key = SigningKey::generate_ed25519();
345 let public = key.verifying_key();
346 let mut req = signed_request(&key);
347
348 let input_raw = req
354 .headers()
355 .get(SIGNATURE_INPUT_HEADER)
356 .unwrap()
357 .to_str()
358 .unwrap()
359 .replace(r#"alg="ed25519""#, r#"alg="bogus-alg""#);
360 req.headers_mut()
361 .insert(SIGNATURE_INPUT_HEADER, input_raw.parse().unwrap());
362
363 let err = rfc9421_verify(&req, |_| Ok(public.clone()))
364 .expect_err("unknown alg hint must surface as the last recorded error");
365 assert!(matches!(err, Error::UnsupportedAlgorithm(_)));
366 }
367}