actpub_httpsig/rfc9421/
verify.rs1use 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 let mut last_err: Option<Error> = None;
106 for (label, input) in inputs {
107 let Some((_, sig_bytes)) = sigs.iter().find(|(l, _)| l == &label) else {
108 last_err = Some(Error::MalformedSignatureHeader(format!(
109 "no Signature entry for label `{label}`"
110 )));
111 continue;
112 };
113
114 if let Err(e) = policy.check(input.created, input.expires, date_header.as_deref(), now) {
117 last_err = Some(e);
118 continue;
119 }
120
121 let Some(key_id) = input.keyid.as_deref() else {
122 last_err = Some(Error::MissingSignatureParameter("keyid"));
123 continue;
124 };
125
126 let key = match resolve_key(key_id) {
127 Ok(k) => k,
128 Err(e) => {
129 last_err = Some(Error::KeyResolution(e.to_string()));
130 continue;
131 }
132 };
133
134 if let Some(hint) = input.algorithm.as_deref()
135 && let Some(hinted) = parse_alg_hint(hint)?
136 && hinted != key.algorithm()
137 {
138 last_err = Some(Error::VerificationFailed);
139 continue;
140 }
141
142 let inner_list = input.serialise_inner_list();
143 let base = build_signature_base(req, &input.components, &inner_list)?;
144
145 if key.verify(base.as_bytes(), sig_bytes).is_err() {
146 last_err = Some(Error::VerificationFailed);
147 continue;
148 }
149
150 return Ok(Rfc9421Verified {
151 label,
152 input,
153 signature_base: base,
154 });
155 }
156
157 Err(last_err.unwrap_or(Error::VerificationFailed))
158}
159
160fn parse_alg_hint(hint: &str) -> Result<Option<Algorithm>, Error> {
161 match hint {
162 "rsa-v1_5-sha256" | "rsa-sha256" => Ok(Some(Algorithm::RsaSha256)),
163 "ed25519" => Ok(Some(Algorithm::Ed25519)),
164 other => Err(Error::UnsupportedAlgorithm(other.to_owned())),
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use http::{Method, Request};
171 use pretty_assertions::assert_eq;
172
173 use super::*;
174 use crate::digest::sha256_digest_header;
175 use crate::key::{RsaBits, SigningKey};
176 use crate::rfc9421::sign::Rfc9421Signer;
177
178 fn signed_request(key: &SigningKey) -> Request<Vec<u8>> {
179 let body = b"{}";
180 let mut req = Request::builder()
181 .method(Method::POST)
182 .uri("https://example.com/inbox?a=1")
183 .header("host", "example.com")
184 .header("date", "Sun, 05 Jan 2014 21:31:40 GMT")
185 .header("digest", sha256_digest_header(body))
186 .body(body.to_vec())
187 .expect("valid");
188 Rfc9421Signer::new(key, "https://example.com/actor#sig")
189 .with_created(1_700_000_000)
190 .sign(&mut req)
191 .expect("sign");
192 req
193 }
194
195 #[test]
196 fn ed25519_roundtrips_sign_then_verify() {
197 let key = SigningKey::generate_ed25519();
198 let public = key.verifying_key();
199 let req = signed_request(&key);
200
201 let report = rfc9421_verify(&req, |kid| {
202 assert_eq!(kid, "https://example.com/actor#sig");
203 Ok(public.clone())
204 })
205 .expect("verify");
206
207 assert_eq!(report.label, "sig1");
208 assert!(report.signature_base.contains(r#""@method": POST"#));
209 }
210
211 #[test]
212 fn rsa_sha256_roundtrips_sign_then_verify() {
213 let key = SigningKey::generate_rsa(RsaBits::Rsa2048).expect("rng");
214 let public = key.verifying_key();
215 let req = signed_request(&key);
216 rfc9421_verify(&req, |_| Ok(public.clone())).expect("verify");
217 }
218
219 #[test]
220 fn tampered_date_header_fails_verification() {
221 let key = SigningKey::generate_ed25519();
222 let public = key.verifying_key();
223 let mut req = signed_request(&key);
224 req.headers_mut().insert(
225 "date",
226 "Mon, 06 Jan 2014 00:00:00 GMT".parse().expect("valid"),
227 );
228 let err =
229 rfc9421_verify(&req, |_| Ok(public.clone())).expect_err("tampered date must fail");
230 assert!(matches!(err, Error::VerificationFailed));
231 }
232
233 #[test]
234 fn algorithm_mismatch_between_hint_and_key_is_rejected() {
235 let key = SigningKey::generate_ed25519();
236 let rsa_public = SigningKey::generate_rsa(RsaBits::Rsa2048)
238 .expect("rng")
239 .verifying_key();
240 let req = signed_request(&key);
241 let err =
242 rfc9421_verify(&req, |_| Ok(rsa_public.clone())).expect_err("mismatched alg must fail");
243 assert!(matches!(err, Error::VerificationFailed));
244 }
245
246 #[test]
247 fn missing_input_header_is_reported() {
248 let key = SigningKey::generate_ed25519();
249 let mut req = signed_request(&key);
250 req.headers_mut().remove(SIGNATURE_INPUT_HEADER);
251 let err = rfc9421_verify(&req, |_| panic!("resolver must not be called"))
252 .expect_err("missing input");
253 assert!(matches!(err, Error::MissingHeader(SIGNATURE_INPUT_HEADER)));
254 }
255
256 #[test]
257 fn missing_signature_header_is_reported() {
258 let key = SigningKey::generate_ed25519();
259 let mut req = signed_request(&key);
260 req.headers_mut().remove(SIGNATURE_HEADER);
261 let err = rfc9421_verify(&req, |_| panic!("resolver must not be called"))
262 .expect_err("missing signature");
263 assert!(matches!(err, Error::MissingHeader(SIGNATURE_HEADER)));
264 }
265}