1use crate::{
2 crypto::{AlgorithmName, SigningKey},
3 error::{HttpSigError, HttpSigResult},
4 message_component::HttpMessageComponentId,
5 trace::*,
6 util::has_unique_elements,
7};
8use base64::{engine::general_purpose, Engine as _};
9use rand::Rng;
10use sfv::{ListEntry, Parser, SerializeValue};
11use std::time::{SystemTime, UNIX_EPOCH};
12
13const DEFAULT_DURATION: u64 = 300;
14
15#[derive(Debug, Clone, Default)]
17pub struct HttpSignatureParams {
20 pub created: Option<u64>,
22 pub expires: Option<u64>,
24 pub nonce: Option<String>,
26 pub alg: Option<String>,
28 pub keyid: Option<String>,
30 pub tag: Option<String>,
32 pub covered_components: Vec<HttpMessageComponentId>,
34}
35
36impl HttpSignatureParams {
37 pub fn try_new(covered_components: &[HttpMessageComponentId]) -> HttpSigResult<Self> {
39 let created = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
40 if !has_unique_elements(covered_components.iter()) {
41 return Err(HttpSigError::InvalidSignatureParams(
42 "duplicate covered component ids".to_string(),
43 ));
44 }
45
46 Ok(Self {
47 created: Some(created),
48 covered_components: covered_components.to_vec(),
49 ..Default::default()
50 })
51 }
52
53 pub fn set_created(&mut self, created: u64) -> &mut Self {
55 self.created = Some(created);
56 self
57 }
58
59 pub fn set_expires(&mut self, expires: u64) -> &mut Self {
61 self.expires = Some(expires);
62 self
63 }
64
65 pub fn set_nonce(&mut self, nonce: &str) -> &mut Self {
67 self.nonce = Some(nonce.to_string());
68 self
69 }
70
71 pub fn set_alg(&mut self, alg: &AlgorithmName) -> &mut Self {
73 self.alg = Some(alg.to_string());
74 self
75 }
76
77 pub fn set_keyid(&mut self, keyid: &str) -> &mut Self {
79 self.keyid = Some(keyid.to_string());
80 self
81 }
82
83 pub fn set_tag(&mut self, tag: &str) -> &mut Self {
85 self.tag = Some(tag.to_string());
86 self
87 }
88
89 pub fn set_key_info(&mut self, key: &impl SigningKey) -> &mut Self {
91 self.keyid = Some(key.key_id().to_string());
92 self.alg = Some(key.alg().to_string());
93 self
94 }
95
96 pub fn set_random_nonce(&mut self) -> &mut Self {
98 let mut rng = rand::rng();
99 let nonce = rng.random::<[u8; 32]>();
100 self.nonce = Some(general_purpose::STANDARD.encode(nonce));
101 self
102 }
103
104 pub fn set_expires_with_duration(&mut self, duration_secs: Option<u64>) -> &mut Self {
106 assert!(self.created.is_some(), "created timestamp is not set");
107 let duration_secs = duration_secs.unwrap_or(DEFAULT_DURATION);
108 self.expires = Some(self.created.unwrap() + duration_secs);
109 self
110 }
111
112 pub fn is_expired(&self) -> bool {
115 if let Some(exp) = self.expires {
116 exp < SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
117 } else {
118 false
119 }
120 }
121}
122
123impl std::fmt::Display for HttpSignatureParams {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 let joined = self.covered_components.iter().fold("".to_string(), |acc, v| {
126 if acc.is_empty() {
127 v.to_string()
128 } else {
129 format!("{acc} {v}")
130 }
131 });
132 let mut s: String = format!("({})", joined);
133 if self.created.is_some() {
134 s.push_str(&format!(";created={}", self.created.unwrap()));
135 }
136 if self.expires.is_some() {
137 s.push_str(&format!(";expires={}", self.expires.unwrap()));
138 }
139 if self.nonce.is_some() {
140 s.push_str(&format!(";nonce=\"{}\"", self.nonce.as_ref().unwrap()));
141 }
142 if self.alg.is_some() {
143 s.push_str(&format!(";alg=\"{}\"", self.alg.as_ref().unwrap()));
144 }
145 if self.keyid.is_some() {
146 s.push_str(&format!(";keyid=\"{}\"", self.keyid.as_ref().unwrap()));
147 }
148 if self.tag.is_some() {
149 s.push_str(&format!(";tag=\"{}\"", self.tag.as_ref().unwrap()));
150 }
151 write!(f, "{}", s)
152 }
153}
154
155impl TryFrom<&ListEntry> for HttpSignatureParams {
156 type Error = HttpSigError;
157 fn try_from(value: &ListEntry) -> HttpSigResult<Self> {
159 if !matches!(value, ListEntry::InnerList(_)) {
160 return Err(HttpSigError::InvalidSignatureParams("Invalid signature params".to_string()));
161 }
162 let inner_list_with_params = match value {
163 ListEntry::InnerList(v) => v,
164 _ => unreachable!(),
165 };
166 let covered_components = inner_list_with_params
167 .items
168 .iter()
169 .map(|v| {
170 v.serialize_value()
171 .map_err(|e| HttpSigError::ParseSFVError(e.to_string()))
172 .and_then(|v| HttpMessageComponentId::try_from(v.as_str()))
173 })
174 .collect::<Result<Vec<_>, _>>()?;
175
176 if !has_unique_elements(covered_components.iter()) {
177 return Err(HttpSigError::InvalidSignatureParams(
178 "duplicate covered component ids".to_string(),
179 ));
180 }
181
182 let mut params = Self {
183 created: None,
184 expires: None,
185 nonce: None,
186 alg: None,
187 keyid: None,
188 tag: None,
189 covered_components,
190 };
191
192 inner_list_with_params
193 .params
194 .iter()
195 .for_each(|(key, bare_item)| match key.as_str() {
196 "created" => params.created = bare_item.as_int().map(|v| v as u64),
197 "expires" => params.expires = bare_item.as_int().map(|v| v as u64),
198 "nonce" => params.nonce = bare_item.as_str().map(|v| v.to_string()),
199 "alg" => params.alg = bare_item.as_str().map(|v| v.to_string()),
200 "keyid" => params.keyid = bare_item.as_str().map(|v| v.to_string()),
201 "tag" => params.tag = bare_item.as_str().map(|v| v.to_string()),
202 _ => {
203 error!("Ignore invalid signature parameter: {}", key)
204 }
205 });
206 Ok(params)
207 }
208}
209
210impl TryFrom<&str> for HttpSignatureParams {
211 type Error = HttpSigError;
212 fn try_from(value: &str) -> HttpSigResult<Self> {
214 let sfv_parsed = Parser::parse_list(value.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
215 if sfv_parsed.len() != 1 || !matches!(sfv_parsed[0], ListEntry::InnerList(_)) {
216 return Err(HttpSigError::InvalidSignatureParams("Invalid signature params".to_string()));
217 }
218 HttpSignatureParams::try_from(&sfv_parsed[0])
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::crypto::SecretKey;
226 const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
227MC4CAQAwBQYDK2VwBCIEIDSHAE++q1BP7T8tk+mJtS+hLf81B0o6CFyWgucDFN/C
228-----END PRIVATE KEY-----
229"##;
230 const _EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
231MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
232-----END PUBLIC KEY-----
233"##;
234 const EDDSA_KEY_ID: &str = "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is=";
235
236 fn build_covered_components() -> Vec<HttpMessageComponentId> {
237 vec![
238 HttpMessageComponentId::try_from("@method").unwrap(),
239 HttpMessageComponentId::try_from("@path").unwrap(),
240 HttpMessageComponentId::try_from("@scheme").unwrap(),
241 HttpMessageComponentId::try_from("@authority").unwrap(),
242 HttpMessageComponentId::try_from("content-type").unwrap(),
243 HttpMessageComponentId::try_from("date").unwrap(),
244 HttpMessageComponentId::try_from("content-length").unwrap(),
245 ]
246 }
247
248 #[test]
249 fn test_try_new() {
250 let params = HttpSignatureParams::try_new(&build_covered_components());
251 assert!(params.is_ok());
252 let params = params.unwrap();
253 assert!(params.created.is_some());
254 assert!(params.expires.is_none());
255 assert!(params.nonce.is_none());
256 assert!(params.alg.is_none());
257 assert!(params.keyid.is_none());
258 assert!(params.tag.is_none());
259 assert_eq!(params.covered_components.len(), 7);
260 }
261
262 #[test]
263 fn test_set_key_info() {
264 let mut params = HttpSignatureParams::try_new(&build_covered_components()).unwrap();
265 params.set_key_info(&SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap());
266 assert_eq!(params.keyid, Some(EDDSA_KEY_ID.to_string()));
267 assert_eq!(params.alg, Some("ed25519".to_string()));
268 }
269
270 #[test]
271 fn test_set_duration() {
272 let mut params = HttpSignatureParams::try_new(&build_covered_components()).unwrap();
273 params.set_expires_with_duration(Some(100));
274 assert!(params.expires.is_some());
275 assert_eq!(params.expires.unwrap(), params.created.unwrap() + 100);
276 assert!(!params.is_expired());
277
278 let created = params.created.unwrap();
279 params.set_expires(created - 1);
280 assert!(params.is_expired());
281 }
282
283 #[test]
284 fn test_from_string_signature_params_without_param() {
285 let value = r##"("@method" "@path" "@scheme" "@authority" "content-type" "date" "content-length")"##;
286 let params = HttpSignatureParams::try_from(value);
287 assert!(params.is_ok());
288 let params = params.unwrap();
289 assert!(params.created.is_none());
290 assert!(params.expires.is_none());
291 assert!(params.nonce.is_none());
292 assert!(params.alg.is_none());
293 assert!(params.keyid.is_none());
294 assert!(params.tag.is_none());
295 assert_eq!(params.covered_components.len(), 7);
296 }
297
298 #[test]
299 fn test_from_string_signature_params() {
300 const SIGPARA: &str = r##";created=1704972031;alg="ed25519";keyid="gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is=""##;
301 let values = vec![
302 (
303 r##""@method" "@path" "@scheme";req "@authority" "content-type";bs "date" "content-length""##,
304 SIGPARA,
305 ),
306 (r##""##, SIGPARA),
307 ];
308 for (covered, sigpara) in values {
309 let value = format!("({}){}", covered, sigpara);
310 let params = HttpSignatureParams::try_from(value.as_str());
311 assert!(params.is_ok());
312 let params = params.unwrap();
313
314 assert_eq!(params.created, Some(1704972031));
315 assert_eq!(params.expires, None);
316 assert_eq!(params.nonce, None);
317 assert_eq!(params.alg, Some("ed25519".to_string()));
318 assert_eq!(params.keyid, Some(EDDSA_KEY_ID.to_string()));
319 assert_eq!(params.tag, None);
320 let covered_components = covered
321 .split(' ')
322 .filter(|v| !v.is_empty())
323 .map(|v| HttpMessageComponentId::try_from(v).unwrap())
324 .collect::<Vec<_>>();
325 assert_eq!(params.covered_components, covered_components);
326 assert_eq!(params.to_string(), value);
327 }
328 }
329}