1use crate::{
2 crypto::SigningKey,
3 error::{HttpSigError, HttpSigResult},
4 message_component::HttpMessageComponent,
5 prelude::{message_component::HttpMessageComponentId, VerifyingKey},
6 signature_params::HttpSignatureParams,
7};
8use base64::{engine::general_purpose, Engine as _};
9use fxhash::FxBuildHasher;
10use indexmap::IndexMap;
11use sfv::{BareItem, Item, ListEntry, Parser};
12
13pub type HttpSignatureHeadersMap = IndexMap<String, HttpSignatureHeaders, FxBuildHasher>;
15
16const DEFAULT_SIGNATURE_NAME: &str = "sig";
18
19#[derive(Debug, Clone)]
20pub struct HttpSignatureHeaders {
22 signature_name: String,
24 signature: HttpSignature,
26 signature_params: HttpSignatureParams,
28}
29
30impl HttpSignatureHeaders {
31 pub fn try_parse(signature_header: &str, signature_input_header: &str) -> HttpSigResult<HttpSignatureHeadersMap> {
33 let signature_input =
34 Parser::parse_dictionary(signature_input_header.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
35 let signature =
36 Parser::parse_dictionary(signature_header.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
37
38 if signature.len() != signature_input.len() {
39 return Err(HttpSigError::BuildSignatureHeaderError(
40 "The number of signature and signature-input headers are not the same".to_string(),
41 ));
42 }
43
44 if !signature.keys().all(|k| signature_input.contains_key(k)) {
45 return Err(HttpSigError::BuildSignatureHeaderError(
46 "The signature and signature-input headers are not the same".to_string(),
47 ));
48 }
49 if !signature.values().all(|v| {
50 matches!(
51 v,
52 ListEntry::Item(Item {
53 bare_item: BareItem::ByteSeq(_),
54 ..
55 })
56 )
57 }) {
58 return Err(HttpSigError::BuildSignatureHeaderError(
59 "The signature header is not a dictionary".to_string(),
60 ));
61 }
62 if !signature_input.values().all(|v| matches!(v, ListEntry::InnerList(_))) {
63 return Err(HttpSigError::BuildSignatureHeaderError(
64 "The signature-input header is not a dictionary".to_string(),
65 ));
66 }
67
68 let res = signature_input
69 .iter()
70 .map(|(k, v)| {
71 let signature_name = k.to_string();
72 let signature_params = HttpSignatureParams::try_from(v)?;
73
74 let signature_bytes = match signature.get(k) {
75 Some(ListEntry::Item(Item {
76 bare_item: BareItem::ByteSeq(v),
77 ..
78 })) => v,
79 _ => unreachable!(),
80 };
81 let signature = HttpSignature(signature_bytes.to_vec());
82
83 Ok((
84 signature_name.clone(),
85 Self {
86 signature_name,
87 signature,
88 signature_params,
89 },
90 )) as HttpSigResult<(String, Self)>
91 })
92 .collect::<Result<HttpSignatureHeadersMap, _>>()?;
93 Ok(res)
94 }
95
96 pub fn signature_name(&self) -> &str {
98 &self.signature_name
99 }
100
101 pub fn signature(&self) -> &HttpSignature {
103 &self.signature
104 }
105
106 pub fn signature_params(&self) -> &HttpSignatureParams {
108 &self.signature_params
109 }
110
111 pub fn signature_header_value(&self) -> String {
113 format!("{}=:{}:", self.signature_name, self.signature)
114 }
115 pub fn signature_input_header_value(&self) -> String {
117 format!("{}={}", self.signature_name, self.signature_params)
118 }
119}
120
121#[derive(Debug, Clone)]
122pub struct HttpSignature(Vec<u8>);
124impl std::fmt::Display for HttpSignature {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 let signature_value = general_purpose::STANDARD.encode(&self.0);
127 write!(f, "{}", signature_value)
128 }
129}
130
131pub struct HttpSignatureBase {
134 component_lines: Vec<HttpMessageComponent>,
136 signature_params: HttpSignatureParams,
138}
139
140impl HttpSignatureBase {
141 pub fn try_new(component_lines: &[HttpMessageComponent], signature_params: &HttpSignatureParams) -> HttpSigResult<Self> {
146 if component_lines.len() != signature_params.covered_components.len() {
148 return Err(HttpSigError::BuildSignatureBaseError(
149 "The number of component lines is not the same as the number of covered message component ids".to_string(),
150 ));
151 }
152
153 let assertion = component_lines
154 .iter()
155 .zip(signature_params.covered_components.iter())
156 .all(|(component_line, covered_component_id)| component_line.id == *covered_component_id);
157 if !assertion {
158 return Err(HttpSigError::BuildSignatureBaseError(
159 "The order of component lines is not the same as the order of covered message component ids".to_string(),
160 ));
161 }
162
163 Ok(Self {
164 component_lines: component_lines.to_vec(),
165 signature_params: signature_params.clone(),
166 })
167 }
168
169 pub fn as_bytes(&self) -> Vec<u8> {
171 let string = self.to_string();
172 string.as_bytes().to_vec()
173 }
174
175 pub fn build_raw_signature(&self, signing_key: &impl SigningKey) -> HttpSigResult<Vec<u8>> {
177 let bytes = self.as_bytes();
178 signing_key.sign(&bytes)
179 }
180
181 pub fn build_signature_headers(
183 &self,
184 signing_key: &impl SigningKey,
185 signature_name: Option<&str>,
186 ) -> HttpSigResult<HttpSignatureHeaders> {
187 let signature = self.build_raw_signature(signing_key)?;
188 Ok(HttpSignatureHeaders {
189 signature_name: signature_name.unwrap_or(DEFAULT_SIGNATURE_NAME).to_string(),
190 signature: HttpSignature(signature),
191 signature_params: self.signature_params.clone(),
192 })
193 }
194
195 pub fn verify_signature_headers(
197 &self,
198 verifying_key: &impl VerifyingKey,
199 signature_headers: &HttpSignatureHeaders,
200 ) -> HttpSigResult<()> {
201 if signature_headers.signature_params().is_expired() {
202 return Err(HttpSigError::ExpiredSignatureParams(
203 "Signature params is expired".to_string(),
204 ));
205 }
206 let signature_bytes = signature_headers.signature.0.as_slice();
207 verifying_key.verify(&self.as_bytes(), signature_bytes)
208 }
209
210 pub fn keyid(&self) -> Option<&str> {
212 self.signature_params.keyid.as_deref()
213 }
214
215 pub fn alg(&self) -> Option<&str> {
217 self.signature_params.alg.as_deref()
218 }
219
220 pub fn nonce(&self) -> Option<&str> {
222 self.signature_params.nonce.as_deref()
223 }
224
225 pub fn covered_components(&self) -> &[HttpMessageComponentId] {
227 &self.signature_params.covered_components
228 }
229}
230
231impl std::fmt::Display for HttpSignatureBase {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 let mut signature_base = String::new();
234 for component_line in &self.component_lines {
235 signature_base.push_str(&component_line.to_string());
236 signature_base.push('\n');
237 }
238 signature_base.push_str(&format!("\"@signature-params\": {}", self.signature_params));
239 write!(f, "{}", signature_base)
240 }
241}
242
243#[cfg(test)]
244mod test {
245 use super::*;
246 use crate::signature_params::HttpSignatureParams;
247
248 const COMPONENT_LINES: &[&str] = &[
249 r##""@method": GET"##,
250 r##""@path": /"##,
251 r##""date": Tue, 07 Jun 2014 20:51:35 GMT"##,
252 r##""content-digest": sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:"##,
253 ];
254
255 #[test]
257 fn test_signature_base_directly_instantiated() {
258 const SIGPARA: &str = r##";created=1704972031;alg="ed25519";keyid="gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is=""##;
259 let values = (r##""@method" "@path" "date" "content-digest""##, SIGPARA);
260 let signature_params = HttpSignatureParams::try_from(format!("({}){}", values.0, values.1).as_str()).unwrap();
261
262 let component_lines = COMPONENT_LINES
263 .iter()
264 .map(|&s| HttpMessageComponent::try_from(s))
265 .collect::<Result<Vec<_>, _>>()
266 .unwrap();
267 let signature_base = HttpSignatureBase::try_new(&component_lines, &signature_params).unwrap();
268 let test_string = r##""@method": GET
269"@path": /
270"date": Tue, 07 Jun 2014 20:51:35 GMT
271"content-digest": sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
272"@signature-params": "##;
273 assert_eq!(
274 signature_base.to_string(),
275 format!("{}({}){}", test_string, values.0, values.1)
276 );
277 }
278
279 #[test]
280 fn test_signature_values() {
281 const SIGNATURE_INPUT: &str = r##"sig-b26=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519", sig-b27=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519-alt""##;
282 const SIGNATURE: &str = r##"sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:, sig-b27=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:"##;
283
284 let header_map = HttpSignatureHeaders::try_parse(SIGNATURE, SIGNATURE_INPUT).unwrap();
285 assert!(header_map.len() == 2);
286 let http_signature_headers = header_map.get("sig-b26").unwrap();
287 assert_eq!(
288 http_signature_headers.signature_header_value(),
289 SIGNATURE.split(',').next().unwrap()
290 );
291 assert_eq!(
292 http_signature_headers.signature_input_header_value(),
293 SIGNATURE_INPUT.split(',').next().unwrap()
294 );
295 }
296}