Skip to main content

http_msgsign/sign/
params.rs

1use std::fmt::{Display, Formatter};
2use std::time::SystemTime;
3
4use indexmap::IndexSet;
5
6use crate::components::params::FieldParameter;
7use crate::components::{Derive, HttpComponent, NameType, TargetField, ToComponent};
8use crate::errors::{HttpComponentError, SignatureParamsError};
9use crate::sign::{ExchangeRecord, SignatureInput, SignerKey};
10
11pub const SIGNATURE_PARAMS: &str = "@signature-params";
12
13#[derive(Debug, Clone)]
14pub struct SignatureParams {
15    covered: IndexSet<TargetField>,
16    created: Option<u64>,
17    gen_created: bool,
18    expires: Option<u64>,
19    algorithm: Option<String>,
20    key_id: Option<String>,
21    nonce: Option<String>,
22    tag: Option<String>,
23}
24
25#[derive(Debug)]
26pub struct KeyPropertyLoadedSignatureParams<'a> {
27    origin: &'a SignatureParams,
28    algorithm: Option<String>,
29    key_id: Option<String>,
30}
31
32impl SignatureParams {
33    /// `created` is generated at [`SignatureParams::to_string`] called.  
34    /// This is because the specification recommends the inclusion of “created” so the process generates it by default.
35    pub fn to_component(&self) -> HttpComponent {
36        HttpComponent {
37            id: format!("\"{SIGNATURE_PARAMS}\""),
38            value: Some(self.to_string()),
39        }
40    }
41
42    pub fn request_to_component<B>(
43        &self,
44        request: &http::Request<B>,
45    ) -> Result<IndexSet<HttpComponent>, HttpComponentError> {
46        let mut components = self
47            .covered
48            .iter()
49            .map(|covered| request.to_component(covered))
50            .collect::<Result<IndexSet<_>, HttpComponentError>>()?;
51
52        // This requirement that the component named @signature-params always be added at the end is satisfied here.
53        components.insert(self.to_component());
54
55        Ok(components)
56    }
57
58    pub fn response_to_component<B>(
59        &self,
60        response: &http::Response<B>,
61    ) -> Result<IndexSet<HttpComponent>, HttpComponentError> {
62        let mut components = self
63            .covered
64            .iter()
65            .map(|covered| response.to_component(covered))
66            .collect::<Result<IndexSet<_>, HttpComponentError>>()?;
67
68        // This requirement that the component named @signature-params always be added at the end is satisfied here.
69        components.insert(self.to_component());
70
71        Ok(components)
72    }
73
74    pub fn record_to_component<Req, Res>(
75        &self,
76        record: &ExchangeRecord<Req, Res>,
77    ) -> Result<IndexSet<HttpComponent>, HttpComponentError> {
78        let mut components = self
79            .covered
80            .iter()
81            .map(|covered| record.to_component(covered))
82            .collect::<Result<IndexSet<_>, HttpComponentError>>()?;
83
84        // This requirement that the component named @signature-params always be added at the end is satisfied here.
85        components.insert(self.to_component());
86
87        Ok(components)
88    }
89
90    pub fn load_signer_key<S: SignerKey>(&self, key: &S) -> KeyPropertyLoadedSignatureParams {
91        KeyPropertyLoadedSignatureParams {
92            origin: self,
93            algorithm: Some(S::ALGORITHM.to_string()),
94            key_id: Some(key.key_id().to_string()),
95        }
96    }
97}
98
99impl KeyPropertyLoadedSignatureParams<'_> {
100    pub fn to_component(&self) -> HttpComponent {
101        HttpComponent {
102            id: format!("\"{SIGNATURE_PARAMS}\""),
103            value: Some(self.to_string()),
104        }
105    }
106
107    pub fn request_to_component<B>(
108        &self,
109        request: &http::Request<B>,
110    ) -> Result<IndexSet<HttpComponent>, HttpComponentError> {
111        let mut components = self
112            .origin
113            .covered
114            .iter()
115            .map(|target| request.to_component(target))
116            .collect::<Result<IndexSet<_>, HttpComponentError>>()?;
117
118        // This requirement that the component named @signature-params always be added at the end is satisfied here.
119        components.insert(self.to_component());
120
121        Ok(components)
122    }
123
124    pub fn response_to_component<B>(
125        &self,
126        response: &http::Response<B>,
127    ) -> Result<IndexSet<HttpComponent>, HttpComponentError> {
128        let mut components = self
129            .origin
130            .covered
131            .iter()
132            .map(|target| response.to_component(target))
133            .collect::<Result<IndexSet<_>, HttpComponentError>>()?;
134
135        // This requirement that the component named @signature-params always be added at the end is satisfied here.
136        components.insert(self.to_component());
137
138        Ok(components)
139    }
140
141    pub fn record_to_component<Req, Res>(
142        &self,
143        record: &ExchangeRecord<Req, Res>,
144    ) -> Result<IndexSet<HttpComponent>, HttpComponentError> {
145        let mut components = self
146            .origin
147            .covered
148            .iter()
149            .map(|target| record.to_component(target))
150            .collect::<Result<IndexSet<_>, HttpComponentError>>()?;
151
152        // This requirement that the component named @signature-params always be added at the end is satisfied here.
153        components.insert(self.to_component());
154
155        Ok(components)
156    }
157}
158
159impl SignatureParams {
160    pub fn builder() -> Builder {
161        Builder::default()
162    }
163}
164
165impl From<SignatureInput> for SignatureParams {
166    fn from(value: SignatureInput) -> Self {
167        Self {
168            covered: value.covered,
169            created: value.created,
170            gen_created: false,
171            expires: value.expires,
172            algorithm: value.algorithm,
173            key_id: value.key_id,
174            nonce: value.nonce,
175            tag: value.tag,
176        }
177    }
178}
179
180impl Display for SignatureParams {
181    //noinspection SpellCheckingInspection
182    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
183        let covered = self.covered.iter().fold(String::new(), |acc, id| {
184            if acc.is_empty() {
185                format!("{id}")
186            } else {
187                format!("{acc} {id}")
188            }
189        });
190        
191        let at = SystemTime::now()
192            .duration_since(std::time::UNIX_EPOCH)
193            .ok()
194            .map(|d| d.as_secs())
195            .unwrap_or(0);
196        
197        let mut base = format!("({})", covered);
198        if self.gen_created {
199            base += &format!(";created={at}")
200        } else if let Some(created) = self.created {
201            base += &format!(";created={created}");
202        }
203
204        if let Some(expires) = self.expires {
205            let expires_at = at + expires;
206            base += &format!(";expires={expires_at}");
207        }
208
209        if let Some(algorithm) = &self.algorithm {
210            base += &format!(";alg=\"{algorithm}\"");
211        }
212
213        if let Some(key_id) = &self.key_id {
214            base += &format!(";keyid=\"{key_id}\"");
215        }
216
217        if let Some(nonce) = &self.nonce {
218            base += &format!(";nonce=\"{nonce}\"");
219        }
220
221        if let Some(tag) = &self.tag {
222            base += &format!(";tag=\"{tag}\"");
223        }
224
225        write!(f, "{}", base)
226    }
227}
228
229impl Display for KeyPropertyLoadedSignatureParams<'_> {
230    //noinspection SpellCheckingInspection
231    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
232        let mut origin = format!("{}", self.origin);
233        if self.origin.algorithm.is_none() {
234            if let Some(algorithm) = &self.algorithm {
235                origin += &format!(";alg=\"{algorithm}\"");
236            }
237        }
238        if self.origin.key_id.is_none() {
239            if let Some(key_id) = &self.key_id {
240                origin += &format!(";keyid=\"{key_id}\"");
241            }
242        }
243        write!(f, "{}", origin)
244    }
245}
246
247pub struct Builder {
248    builder: Result<SignatureParams, SignatureParamsError>,
249}
250
251//noinspection SpellCheckingInspection
252impl Builder {
253    /// `Derived Components` defined in RFC9421 can be added as covered components.  
254    /// `Derived Components` have their own formatting behavior, so adding a Parameter may be redundant.
255    /// Therefore, Parameter cannot be used.
256    ///
257    /// **Note**: The order in which they are added is preserved and is a common array with [`Builder::add_header`].
258    ///
259    /// See [`RFC9421 Derived Components`](https://datatracker.ietf.org/doc/html/rfc9421#name-derived-components)
260    pub fn add_derive(self, derive: Derive, params: FieldParameter) -> Self {
261        self.and_then(|mut sign_params| {
262            sign_params
263                .covered
264                .insert(TargetField::new(derive.into(), params)?);
265            Ok(sign_params)
266        })
267    }
268
269    /// HTTP header values can be added to the covered component.  
270    /// This is the so-called `HTTP Fields` defined in [RFC9421 HTTP Fields](https://datatracker.ietf.org/doc/html/rfc9421#name-http-fields).  
271    /// Can also be assigned Parameter defined in [RFC9421 §2.1-17](https://datatracker.ietf.org/doc/html/rfc9421#section-2.1-17).
272    ///
273    /// **Note**: The order in which they are added is preserved and is a common array with [`Builder::add_derive`].
274    pub fn add_header<H>(self, header: H, params: FieldParameter) -> Self
275    where
276        H: TryInto<http::HeaderName>,
277        H::Error: Into<http::Error>,
278    {
279        self.and_then(|mut sign_params| {
280            let header = header.try_into().map_err(Into::into)?;
281            sign_params
282                .covered
283                .insert(TargetField::new(NameType::from(header), params)?);
284            Ok(sign_params)
285        })
286    }
287    
288    /// `expires`: Expiration time as a UNIX timestamp value of type Integer.  
289    /// Sub-second precision is not supported.
290    ///
291    /// See [RFC9421 Signature Parameters §2.3-4.4](https://datatracker.ietf.org/doc/html/rfc9421#section-2.3-4.4)
292    pub fn set_expires(self, duration_unix: impl Into<u64>) -> Self {
293        self.and_then(|mut sign_params| {
294            sign_params.expires = Some(duration_unix.into());
295            Ok(sign_params)
296        })
297    }
298
299    /// `alg`: The HTTP message signature algorithm from the "HTTP Signature Algorithms" registry, as a String value.
300    ///
301    /// See [RFC9421 Signature Parameters §2.3-4.8](https://datatracker.ietf.org/doc/html/rfc9421#section-2.3-4.8)
302    pub fn set_algorithm(self, algorithm: impl Into<String>) -> Self {
303        self.and_then(|mut sign_params| {
304            sign_params.algorithm = Some(algorithm.into());
305            Ok(sign_params)
306        })
307    }
308
309    /// `keyid`: The identifier for the key material as a String value.
310    ///
311    /// See [RFC9421 Signature Parameters §2.3-4.10](https://datatracker.ietf.org/doc/html/rfc9421#section-2.3-4.10)
312    pub fn set_key_id(self, key_id: impl Into<String>) -> Self {
313        self.and_then(|mut sign_params| {
314            sign_params.key_id = Some(key_id.into());
315            Ok(sign_params)
316        })
317    }
318
319    /// `nonce`: A random unique value generated for this signature as a String value.
320    ///
321    /// See [RFC9421 Signature Parameters §2.3-4.6](https://datatracker.ietf.org/doc/html/rfc9421#section-2.3-4.6)
322    pub fn set_nonce(self, nonce: impl Into<String>) -> Self {
323        self.and_then(|mut sign_params| {
324            sign_params.nonce = Some(nonce.into());
325            Ok(sign_params)
326        })
327    }
328
329    /// `tag`: An application-specific tag for the signature as a String value.  
330    /// This value is used by applications to help identify signatures relevant for specific applications or protocols.
331    ///
332    /// See [RFC9421 Signature Parameters §2.3-4.12](https://datatracker.ietf.org/doc/html/rfc9421#section-2.3-4.12)
333    pub fn set_tag(self, tag: impl Into<String>) -> Self {
334        self.and_then(|mut sign_params| {
335            sign_params.tag = Some(tag.into());
336            Ok(sign_params)
337        })
338    }
339    
340    /// `created`: Creation time as a UNIX timestamp value of type Integer.  
341    /// Sub-second precision is not supported.  
342    /// The inclusion of this parameter is **RECOMMENDED**.  
343    ///
344    /// See [RFC9421 Signature Parameters §2.3-4.2](https://datatracker.ietf.org/doc/html/rfc9421#section-2.3-4.2)
345    pub fn gen_created(self) -> Self {
346        self.and_then(|mut sign_params| {
347            sign_params.gen_created = true;
348            Ok(sign_params)
349        })
350    }
351
352    pub fn build(self) -> Result<SignatureParams, SignatureParamsError> {
353        self.builder
354    }
355
356    fn and_then<F>(self, f: F) -> Self
357    where
358        F: FnOnce(SignatureParams) -> Result<SignatureParams, SignatureParamsError>,
359    {
360        Self {
361            builder: self.builder.and_then(f),
362        }
363    }
364}
365
366impl Default for Builder {
367    fn default() -> Self {
368        Self {
369            builder: Ok(SignatureParams {
370                covered: IndexSet::new(),
371                created: None,
372                gen_created: false,
373                expires: None,
374                algorithm: None,
375                key_id: None,
376                nonce: None,
377                tag: None,
378            }),
379        }
380    }
381}