open_payments/http_signature/
validation.rs1use crate::http_signature::error::{HttpSignatureError, Result};
2use base64::{engine::general_purpose::STANDARD, Engine};
3use ed25519_dalek::{Signature, Verifier, VerifyingKey};
4use http::{HeaderMap, Request};
5
6pub struct ValidationOptions<'a> {
7 pub request: &'a Request<Option<String>>,
8 pub headers: &'a HeaderMap,
9 pub public_key: &'a VerifyingKey,
10}
11
12impl ValidationOptions<'_> {
13 pub fn new<'a>(
14 request: &'a Request<Option<String>>,
15 headers: &'a HeaderMap,
16 public_key: &'a VerifyingKey,
17 ) -> ValidationOptions<'a> {
18 ValidationOptions {
19 request,
20 headers,
21 public_key,
22 }
23 }
24}
25
26fn create_signature_base_string(
27 request: &Request<Option<String>>,
28 components: &[&str],
29 created: i64,
30 keyid: &str,
31) -> String {
32 let mut parts = Vec::new();
33
34 for component in components {
35 let value = match *component {
36 "@method" => request.method().as_str(),
37 "@target-uri" => &request.uri().to_string(),
38 "authorization" => request
39 .headers()
40 .get("Authorization")
41 .and_then(|v| v.to_str().ok())
42 .unwrap_or(""),
43 "content-digest" => request
44 .headers()
45 .get("Content-Digest")
46 .and_then(|v| v.to_str().ok())
47 .unwrap_or(""),
48 "content-length" => request
49 .headers()
50 .get("Content-Length")
51 .and_then(|v| v.to_str().ok())
52 .unwrap_or(""),
53 "content-type" => request
54 .headers()
55 .get("Content-Type")
56 .and_then(|v| v.to_str().ok())
57 .unwrap_or(""),
58 _ => "",
59 };
60 parts.push(format!("\"{component}\": {value}"));
61 }
62
63 let sig_params = format!(
64 "({});created={};keyid=\"{}\"",
65 components.join(" "),
66 created,
67 keyid
68 );
69 parts.push(format!("\"@signature-params\": {sig_params}"));
70
71 parts.join("\n")
72}
73
74fn parse_signature_input(signature_input: &str) -> Result<(Vec<&str>, i64, String)> {
75 let mut components = Vec::new();
76 let mut created = None;
77 let mut keyid = None;
78
79 let signature_input = signature_input
81 .strip_prefix("sig1=")
82 .unwrap_or(signature_input);
83
84 for part in signature_input.split(';') {
85 if let Some(inner) = part.strip_prefix('(').and_then(|p| p.strip_suffix(')')) {
86 components = inner.split(' ').map(|s| s.trim()).collect();
87 } else if let Some(value) = part.strip_prefix("created=") {
88 created = value.parse::<i64>().ok();
89 } else if let Some(value) = part.strip_prefix("keyid=") {
90 keyid = Some(value.trim_matches('"').to_string());
91 }
92 }
93
94 let created = created
95 .ok_or_else(|| HttpSignatureError::Validation("Missing created field".to_string()))?;
96 let keyid =
97 keyid.ok_or_else(|| HttpSignatureError::Validation("Missing keyid field".to_string()))?;
98
99 Ok((components, created, keyid))
100}
101
102pub fn validate_signature(options: ValidationOptions<'_>) -> Result<()> {
103 let signature_input = options
104 .headers
105 .get("Signature-Input")
106 .and_then(|v| v.to_str().ok())
107 .ok_or_else(|| {
108 HttpSignatureError::Validation("Missing Signature-Input header".to_string())
109 })?;
110
111 let (components, created, keyid) = parse_signature_input(signature_input)?;
112
113 let signature = options
114 .headers
115 .get("Signature")
116 .and_then(|v| v.to_str().ok())
117 .ok_or_else(|| HttpSignatureError::Validation("Missing Signature header".to_string()))?;
118
119 let signature_base =
120 create_signature_base_string(options.request, &components, created, &keyid);
121
122 let signature_bytes = STANDARD
123 .decode(signature)
124 .map_err(|_| HttpSignatureError::Validation("Base64 decode failed".to_string()))?;
125
126 let signature_bytes: [u8; 64] = signature_bytes
127 .try_into()
128 .map_err(|_| HttpSignatureError::Validation("Invalid signature length".to_string()))?;
129 let signature = Signature::from_bytes(&signature_bytes);
130
131 options
132 .public_key
133 .verify(signature_base.as_bytes(), &signature)
134 .map_err(|_| HttpSignatureError::Validation("Signature verification failed".to_string()))?;
135
136 Ok(())
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::http_signature::{create_signature_headers, SignOptions};
143 use ed25519_dalek::{SigningKey, VerifyingKey};
144 use http::{HeaderMap, Method, Request, Uri};
145 use rand::rngs::OsRng;
146
147 #[test]
148 fn test_signature_validation() {
149 let mut request = Request::new(Some("test body".to_string()));
150 *request.method_mut() = Method::POST;
151 *request.uri_mut() = Uri::from_static("http://example.com");
152 request
153 .headers_mut()
154 .insert("Content-Type", "application/json".parse().unwrap());
155
156 let signing_key = SigningKey::generate(&mut OsRng);
157 let verifying_key = VerifyingKey::from(&signing_key);
158
159 let options = SignOptions::new(&request, &signing_key, "test-key".to_string());
160 let signature_headers = create_signature_headers(options).unwrap();
161
162 let mut headers = HeaderMap::new();
163 headers.insert("Signature", signature_headers.signature.parse().unwrap());
164 headers.insert(
165 "Signature-Input",
166 signature_headers.signature_input.parse().unwrap(),
167 );
168
169 let options = ValidationOptions::new(&request, &headers, &verifying_key);
170 assert!(validate_signature(options).is_ok());
171 }
172
173 #[test]
174 fn test_missing_signature_input_header() {
175 let mut request = Request::new(Some("body".to_string()));
176 *request.method_mut() = Method::POST;
177 *request.uri_mut() = Uri::from_static("http://example.com");
178
179 let signing_key = SigningKey::generate(&mut OsRng);
180 let verifying_key = VerifyingKey::from(&signing_key);
181
182 let headers = HeaderMap::new();
183 let options = ValidationOptions::new(&request, &headers, &verifying_key);
184 let err = validate_signature(options).unwrap_err();
185 match err {
186 HttpSignatureError::Validation(msg) => {
187 assert_eq!(msg, "Missing Signature-Input header");
188 }
189 _ => panic!("unexpected error type"),
190 }
191 }
192
193 #[test]
194 fn test_missing_signature_header() {
195 let mut request = Request::new(Some("body".to_string()));
196 *request.method_mut() = Method::POST;
197 *request.uri_mut() = Uri::from_static("http://example.com");
198
199 let signing_key = SigningKey::generate(&mut OsRng);
200 let options = SignOptions::new(&request, &signing_key, "k".to_string());
201 let sig = create_signature_headers(options).unwrap();
202
203 let mut headers = HeaderMap::new();
204 headers.insert("Signature-Input", sig.signature_input.parse().unwrap());
205
206 let verifying_key = VerifyingKey::from(&signing_key);
207 let options = ValidationOptions::new(&request, &headers, &verifying_key);
208 let err = validate_signature(options).unwrap_err();
209 match err {
210 HttpSignatureError::Validation(msg) => {
211 assert_eq!(msg, "Missing Signature header");
212 }
213 _ => panic!("unexpected error type"),
214 }
215 }
216
217 #[test]
218 fn test_base64_decode_failed() {
219 let mut request = Request::new(Some("body".to_string()));
220 *request.method_mut() = Method::POST;
221 *request.uri_mut() = Uri::from_static("http://example.com");
222
223 let signing_key = SigningKey::generate(&mut OsRng);
224 let options = SignOptions::new(&request, &signing_key, "k".to_string());
225 let sig = create_signature_headers(options).unwrap();
226
227 let mut headers = HeaderMap::new();
228 headers.insert("Signature-Input", sig.signature_input.parse().unwrap());
229 headers.insert("Signature", "%%%".parse().unwrap());
230
231 let verifying_key = VerifyingKey::from(&signing_key);
232 let options = ValidationOptions::new(&request, &headers, &verifying_key);
233 let err = validate_signature(options).unwrap_err();
234 match err {
235 HttpSignatureError::Validation(msg) => {
236 assert_eq!(msg, "Base64 decode failed");
237 }
238 _ => panic!("unexpected error type"),
239 }
240 }
241
242 #[test]
243 fn test_invalid_signature_length() {
244 let mut request = Request::new(Some("body".to_string()));
245 *request.method_mut() = Method::POST;
246 *request.uri_mut() = Uri::from_static("http://example.com");
247
248 let signing_key = SigningKey::generate(&mut OsRng);
249 let options = SignOptions::new(&request, &signing_key, "k".to_string());
250 let sig = create_signature_headers(options).unwrap();
251
252 let mut headers = HeaderMap::new();
253 headers.insert("Signature-Input", sig.signature_input.parse().unwrap());
254 headers.insert("Signature", "aGVsbG8=".parse().unwrap()); let verifying_key = VerifyingKey::from(&signing_key);
257 let options = ValidationOptions::new(&request, &headers, &verifying_key);
258 let err = validate_signature(options).unwrap_err();
259 match err {
260 HttpSignatureError::Validation(msg) => {
261 assert_eq!(msg, "Invalid signature length");
262 }
263 _ => panic!("unexpected error type"),
264 }
265 }
266
267 #[test]
268 fn test_signature_verification_failed() {
269 let mut request = Request::new(Some("body".to_string()));
270 *request.method_mut() = Method::POST;
271 *request.uri_mut() = Uri::from_static("http://example.com");
272 request
273 .headers_mut()
274 .insert("Content-Type", "application/json".parse().unwrap());
275
276 let signing_key = SigningKey::generate(&mut OsRng);
277 let verifying_key = VerifyingKey::from(&signing_key);
278
279 let options = SignOptions::new(&request, &signing_key, "k".to_string());
280 let sig = create_signature_headers(options).unwrap();
281
282 *request.uri_mut() = Uri::from_static("http://example.com/changed");
284
285 let mut headers = HeaderMap::new();
286 headers.insert("Signature-Input", sig.signature_input.parse().unwrap());
287 headers.insert("Signature", sig.signature.parse().unwrap());
288
289 let options = ValidationOptions::new(&request, &headers, &verifying_key);
290 let err = validate_signature(options).unwrap_err();
291 match err {
292 HttpSignatureError::Validation(msg) => {
293 assert_eq!(msg, "Signature verification failed");
294 }
295 _ => panic!("unexpected error type"),
296 }
297 }
298}