1use chrono::{DateTime, Utc};
10use http::Request;
11
12use crate::cavage::{CavageVerified, cavage_verify, cavage_verify_with_policy};
13use crate::error::Error;
14use crate::key::VerifyingKey;
15use crate::policy::VerifyPolicy;
16use crate::rfc9421::{
17 Rfc9421Verified, SIGNATURE_INPUT_HEADER, rfc9421_verify, rfc9421_verify_with_policy,
18};
19
20#[derive(Debug, Clone)]
22#[non_exhaustive]
23pub enum Verified {
24 Cavage(CavageVerified),
26 Rfc9421(Rfc9421Verified),
28}
29
30impl Verified {
31 #[must_use]
33 pub fn key_id(&self) -> &str {
34 match self {
35 Self::Cavage(c) => &c.key_id,
36 Self::Rfc9421(r) => r.input.keyid.as_deref().unwrap_or_default(),
37 }
38 }
39
40 #[must_use]
43 pub fn signature_base(&self) -> &str {
44 match self {
45 Self::Cavage(c) => &c.signature_base,
46 Self::Rfc9421(r) => &r.signature_base,
47 }
48 }
49}
50
51pub fn verify<B, F>(req: &Request<B>, mut resolve_key: F) -> Result<Verified, Error>
64where
65 F: FnMut(&str) -> Result<VerifyingKey, Error>,
66{
67 if req.headers().contains_key(SIGNATURE_INPUT_HEADER) {
68 return rfc9421_verify(req, &mut resolve_key).map(Verified::Rfc9421);
69 }
70 cavage_verify(req, |kid| resolve_key(kid)).map(Verified::Cavage)
71}
72
73pub fn verify_with_policy<B, F>(
84 req: &Request<B>,
85 policy: &VerifyPolicy,
86 now: DateTime<Utc>,
87 mut resolve_key: F,
88) -> Result<Verified, Error>
89where
90 F: FnMut(&str) -> Result<VerifyingKey, Error>,
91{
92 if req.headers().contains_key(SIGNATURE_INPUT_HEADER) {
93 return rfc9421_verify_with_policy(req, policy, now, &mut resolve_key)
94 .map(Verified::Rfc9421);
95 }
96 cavage_verify_with_policy(req, policy, now, |kid| resolve_key(kid)).map(Verified::Cavage)
97}
98
99#[cfg(test)]
100mod tests {
101 use http::{Method, Request};
102 use pretty_assertions::assert_eq;
103
104 use super::*;
105 use crate::cavage::CavageSigner;
106 use crate::digest::sha256_digest_header;
107 use crate::key::SigningKey;
108 use crate::rfc9421::Rfc9421Signer;
109
110 fn base_request(body: &[u8]) -> Request<Vec<u8>> {
111 Request::builder()
112 .method(Method::POST)
113 .uri("https://example.com/inbox")
114 .header("host", "example.com")
115 .header("date", "Sun, 05 Jan 2014 21:31:40 GMT")
116 .header("digest", sha256_digest_header(body))
117 .header("content-type", "application/activity+json")
118 .body(body.to_vec())
119 .expect("valid")
120 }
121
122 #[test]
123 fn cavage_signed_request_is_dispatched_to_cavage_verifier() {
124 let key = SigningKey::generate_ed25519();
125 let public = key.verifying_key();
126 let mut req = base_request(b"{}");
127 CavageSigner::new(&key, "https://example.com/actor#kid")
128 .sign(&mut req)
129 .expect("sign");
130
131 let report = verify(&req, |_| Ok(public.clone())).expect("verify");
132 assert!(matches!(report, Verified::Cavage(_)));
133 assert_eq!(report.key_id(), "https://example.com/actor#kid");
134 }
135
136 #[test]
137 fn rfc9421_signed_request_is_dispatched_to_rfc9421_verifier() {
138 let key = SigningKey::generate_ed25519();
139 let public = key.verifying_key();
140 let mut req = base_request(b"{}");
141 Rfc9421Signer::new(&key, "https://example.com/actor#kid")
142 .sign(&mut req)
143 .expect("sign");
144
145 let report = verify(&req, |_| Ok(public.clone())).expect("verify");
146 assert!(matches!(report, Verified::Rfc9421(_)));
147 assert_eq!(report.key_id(), "https://example.com/actor#kid");
148 }
149
150 #[test]
151 fn rfc9421_takes_precedence_over_cavage_when_both_are_present() {
152 let key = SigningKey::generate_ed25519();
155 let public = key.verifying_key();
156 let mut req = base_request(b"{}");
157 CavageSigner::new(&key, "cavage-kid")
158 .sign(&mut req)
159 .expect("sign cavage");
160 Rfc9421Signer::new(&key, "rfc9421-kid")
161 .sign(&mut req)
162 .expect("sign 9421");
163
164 let report = verify(&req, |_| Ok(public.clone())).expect("verify");
165 assert!(matches!(report, Verified::Rfc9421(_)));
166 assert_eq!(report.key_id(), "rfc9421-kid");
167 }
168
169 #[test]
170 fn unsigned_request_returns_missing_header_error() {
171 let req = base_request(b"{}");
172 let err =
173 verify(&req, |_| panic!("resolver must not be called")).expect_err("unsigned request");
174 assert!(matches!(err, Error::MissingHeader(_)));
175 }
176
177 #[test]
178 fn policy_rejects_cavage_signature_older_than_max_age() {
179 let key = SigningKey::generate_ed25519();
180 let public = key.verifying_key();
181 let mut req = base_request(b"{}");
182 CavageSigner::new(&key, "kid")
183 .with_created(1_700_000_000)
184 .sign(&mut req)
185 .expect("sign");
186
187 let now = DateTime::<Utc>::from_timestamp(1_700_000_000 + 20 * 3600, 0).expect("valid");
189 let err = verify_with_policy(&req, &VerifyPolicy::mastodon(), now, |_| Ok(public.clone()))
190 .expect_err("stale signature must be rejected");
191 assert!(matches!(err, Error::TimestampTooOld { .. }));
192 }
193
194 #[test]
195 fn policy_rejects_rfc9421_signature_in_the_future() {
196 let key = SigningKey::generate_ed25519();
197 let public = key.verifying_key();
198 let mut req = base_request(b"{}");
199 Rfc9421Signer::new(&key, "kid")
201 .with_created(1_700_000_000 + 15 * 60)
202 .sign(&mut req)
203 .expect("sign");
204
205 let now = DateTime::<Utc>::from_timestamp(1_700_000_000, 0).expect("valid");
206 let err = verify_with_policy(&req, &VerifyPolicy::mastodon(), now, |_| Ok(public.clone()))
207 .expect_err("future-dated signature must be rejected");
208 assert!(matches!(err, Error::TimestampInFuture { .. }));
209 }
210
211 #[test]
212 fn policy_accepts_signature_within_skew_tolerance() {
213 let key = SigningKey::generate_ed25519();
214 let public = key.verifying_key();
215 let mut req = base_request(b"{}");
216 Rfc9421Signer::new(&key, "kid")
218 .with_created(1_700_000_000 + 60)
219 .sign(&mut req)
220 .expect("sign");
221
222 let now = DateTime::<Utc>::from_timestamp(1_700_000_000, 0).expect("valid");
223 verify_with_policy(&req, &VerifyPolicy::mastodon(), now, |_| Ok(public.clone()))
224 .expect("signature within skew tolerance must verify");
225 }
226}