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