Skip to main content

open_payments/http_signature/
validation.rs

1use 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    // Remove the sig1= prefix if present
80    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()); // "hello"
255
256        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        // Tamper with request after signing to force verification failure
283        *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}