Skip to main content

attested_request/
signature.rs

1//! The `Signature-Input` and `Signature` structured fields.
2//!
3//! RFC 9421 §3.2 step 7 builds `@signature-params` by serializing the *parsed* signature
4//! parameters, never by copying the field's wire bytes. [`SignatureParams::serialize`] is that
5//! serialization, and signers use it to write `Signature-Input`, so a conforming client produces
6//! the same bytes on the wire and in the signature base. Whitespace that Structured Fields allows
7//! on the wire therefore cannot change the signature base, and neither can any other difference
8//! between what was parsed and what was signed.
9
10use base64::{Engine as _, engine::general_purpose::STANDARD};
11use sfv::{
12    BareItem, DictSerializer, Dictionary, Integer, KeyRef, ListEntry, ListSerializer, Parser,
13    StringRef, key_ref,
14};
15
16use crate::profile::{Component, SIGNATURE_LABEL};
17
18/// Decoded length a request nonce must reach.
19pub const MIN_NONCE_BYTES: usize = 16;
20
21const CREATED: &KeyRef = key_ref("created");
22const NONCE: &KeyRef = key_ref("nonce");
23const ALG: &KeyRef = key_ref("alg");
24
25/// A signature parameter of the profile. The profile accepts exactly these three.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Param {
28    /// `created`: Unix seconds at signing time.
29    Created,
30    /// `nonce`: a fresh random value per request.
31    Nonce,
32    /// `alg`: the platform signature encoding.
33    Alg,
34}
35
36impl Param {
37    /// The order signers emit parameters in.
38    pub const SIGNING_ORDER: [Self; 3] = [Self::Created, Self::Nonce, Self::Alg];
39
40    const fn name(self) -> &'static str {
41        match self {
42            Self::Created => "created",
43            Self::Nonce => "nonce",
44            Self::Alg => "alg",
45        }
46    }
47}
48
49/// The parsed value of the `integrity` member of `Signature-Input`.
50///
51/// Component and parameter order are preserved, because both are part of the signed bytes.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct SignatureParams {
54    components: Vec<Component>,
55    created: Integer,
56    nonce: sfv::String,
57    alg: sfv::String,
58    order: [Param; 3],
59}
60
61/// Why a `Signature-Input` field was refused.
62#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
63pub enum SignatureInputError {
64    /// The field is not a valid Structured Fields dictionary.
65    #[error("signature-input is not a structured field dictionary")]
66    Syntax,
67    /// The dictionary does not hold exactly one member labelled `integrity`.
68    #[error("signature-input must hold exactly one member labelled `integrity`")]
69    Label,
70    /// The member is not a non-empty inner list.
71    #[error("signature-input member is not a non-empty inner list")]
72    NotInnerList,
73    /// A covered component is not a string, or carries parameters.
74    #[error("covered components must be plain strings")]
75    ComponentForm,
76    /// A covered component appears twice.
77    #[error("covered component `{0}` appears twice")]
78    DuplicateComponent(Component),
79    /// A covered component is outside the profile, so the verifier cannot resolve it.
80    #[error("covered component is not part of the profile")]
81    UnsupportedComponent,
82    /// A signature parameter other than `created`, `nonce` and `alg`, such as `keyid` or `tag`.
83    #[error("unexpected signature parameter")]
84    UnexpectedParameter,
85    /// A required signature parameter is missing.
86    #[error("missing signature parameter `{}`", .0.name())]
87    MissingParameter(Param),
88    /// A signature parameter has the wrong type or an empty value.
89    #[error("invalid signature parameter `{}`", .0.name())]
90    InvalidParameter(Param),
91}
92
93impl SignatureParams {
94    /// Parameters for a new signature, covering every profile component in signing order.
95    ///
96    /// # Errors
97    ///
98    /// Returns [`SignatureInputError::InvalidParameter`] when `created` is nonpositive or outside
99    /// the Structured Fields integer range, or `nonce` or `alg` is empty or not printable ASCII.
100    pub fn new(created: i64, nonce: &str, alg: &str) -> Result<Self, SignatureInputError> {
101        let invalid = SignatureInputError::InvalidParameter;
102        if created <= 0 {
103            return Err(invalid(Param::Created));
104        }
105        if nonce.is_empty() {
106            return Err(invalid(Param::Nonce));
107        }
108        if alg.is_empty() {
109            return Err(invalid(Param::Alg));
110        }
111
112        Ok(Self {
113            components: Component::ALL.to_vec(),
114            created: Integer::try_from(created).map_err(|_| invalid(Param::Created))?,
115            nonce: sfv::String::from_string(nonce.to_owned()).map_err(|_| invalid(Param::Nonce))?,
116            alg: sfv::String::from_string(alg.to_owned()).map_err(|_| invalid(Param::Alg))?,
117            order: Param::SIGNING_ORDER,
118        })
119    }
120
121    /// Parses a `Signature-Input` field value.
122    ///
123    /// # Errors
124    ///
125    /// Returns a [`SignatureInputError`] when the field violates Structured Fields or the profile.
126    pub fn parse(field: &str) -> Result<Self, SignatureInputError> {
127        let dictionary: Dictionary = Parser::new(field)
128            .parse()
129            .map_err(|_| SignatureInputError::Syntax)?;
130        let member = only_integrity_member(&dictionary).ok_or(SignatureInputError::Label)?;
131        let ListEntry::InnerList(inner_list) = member else {
132            return Err(SignatureInputError::NotInnerList);
133        };
134        if inner_list.items.is_empty() {
135            return Err(SignatureInputError::NotInnerList);
136        }
137
138        let mut components = Vec::with_capacity(inner_list.items.len());
139        for item in &inner_list.items {
140            let BareItem::String(identifier) = &item.bare_item else {
141                return Err(SignatureInputError::ComponentForm);
142            };
143            if !item.params.is_empty() {
144                return Err(SignatureInputError::ComponentForm);
145            }
146            let component = Component::from_identifier(identifier.as_str())
147                .ok_or(SignatureInputError::UnsupportedComponent)?;
148            if components.contains(&component) {
149                return Err(SignatureInputError::DuplicateComponent(component));
150            }
151            components.push(component);
152        }
153
154        let mut created = None;
155        let mut nonce = None;
156        let mut alg = None;
157        let mut order = Vec::with_capacity(3);
158        for (key, value) in &inner_list.params {
159            let param = match key.as_str() {
160                "created" => Param::Created,
161                "nonce" => Param::Nonce,
162                "alg" => Param::Alg,
163                _ => return Err(SignatureInputError::UnexpectedParameter),
164            };
165            let invalid = SignatureInputError::InvalidParameter(param);
166            match (param, value) {
167                (Param::Created, BareItem::Integer(value)) if i64::from(*value) > 0 => {
168                    created = Some(*value);
169                }
170                (Param::Nonce, BareItem::String(value)) if !value.as_str().is_empty() => {
171                    nonce = Some(value.clone());
172                }
173                (Param::Alg, BareItem::String(value)) if !value.as_str().is_empty() => {
174                    alg = Some(value.clone());
175                }
176                _ => return Err(invalid),
177            }
178            order.push(param);
179        }
180
181        let missing = SignatureInputError::MissingParameter;
182        Ok(Self {
183            components,
184            created: created.ok_or(missing(Param::Created))?,
185            nonce: nonce.ok_or(missing(Param::Nonce))?,
186            alg: alg.ok_or(missing(Param::Alg))?,
187            // Parameters keys are unique, so three accepted keys are each present exactly once.
188            order: order
189                .try_into()
190                .expect("created, nonce and alg are each present once"),
191        })
192    }
193
194    /// The covered components, in signed order.
195    #[must_use]
196    pub fn components(&self) -> &[Component] {
197        &self.components
198    }
199
200    /// The `created` parameter, in Unix seconds.
201    #[must_use]
202    pub fn created(&self) -> i64 {
203        i64::from(self.created)
204    }
205
206    /// The `nonce` parameter.
207    #[must_use]
208    pub fn nonce(&self) -> &str {
209        self.nonce.as_str()
210    }
211
212    /// The `alg` parameter.
213    #[must_use]
214    pub fn alg(&self) -> &str {
215        self.alg.as_str()
216    }
217
218    /// The canonical Structured Fields serialization of these parameters (RFC 9421 §2.3).
219    ///
220    /// This is the value of the `@signature-params` component and of the `integrity` member of
221    /// `Signature-Input`.
222    #[must_use]
223    pub fn serialize(&self) -> String {
224        let mut list = ListSerializer::new();
225        {
226            let mut inner = list.inner_list();
227            for component in &self.components {
228                let identifier = StringRef::from_str(component.identifier())
229                    .expect("component identifiers are printable ASCII");
230                let _ = inner.bare_item(identifier);
231            }
232            let mut params = inner.finish();
233            for param in self.order {
234                params = match param {
235                    Param::Created => params.parameter(CREATED, self.created),
236                    Param::Nonce => params.parameter(NONCE, &*self.nonce),
237                    Param::Alg => params.parameter(ALG, &*self.alg),
238                };
239            }
240        }
241        list.finish().expect("a list with one member serializes")
242    }
243
244    /// The `Signature-Input` field value carrying these parameters.
245    #[must_use]
246    pub fn to_field(&self) -> String {
247        format!("{SIGNATURE_LABEL}={}", self.serialize())
248    }
249}
250
251/// Why a `Signature` field was refused.
252#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
253pub enum SignatureFieldError {
254    /// The field is not a valid Structured Fields dictionary.
255    #[error("signature is not a structured field dictionary")]
256    Syntax,
257    /// The dictionary does not hold exactly one member labelled `integrity`.
258    #[error("signature must hold exactly one member labelled `integrity`")]
259    Label,
260    /// The member is not a non-empty byte sequence without parameters.
261    #[error("signature member is not a non-empty byte sequence")]
262    NotByteSequence,
263}
264
265/// Parses a `Signature` field value into the signature bytes.
266///
267/// # Errors
268///
269/// Returns a [`SignatureFieldError`] when the field violates Structured Fields or the profile.
270pub fn parse_signature(field: &str) -> Result<Vec<u8>, SignatureFieldError> {
271    let dictionary: Dictionary = Parser::new(field)
272        .parse()
273        .map_err(|_| SignatureFieldError::Syntax)?;
274    let member = only_integrity_member(&dictionary).ok_or(SignatureFieldError::Label)?;
275    match member {
276        ListEntry::Item(item) if item.params.is_empty() => match &item.bare_item {
277            BareItem::ByteSequence(bytes) if !bytes.is_empty() => Ok(bytes.clone()),
278            _ => Err(SignatureFieldError::NotByteSequence),
279        },
280        _ => Err(SignatureFieldError::NotByteSequence),
281    }
282}
283
284/// The `Signature` field value carrying `signature`.
285#[must_use]
286pub fn signature_field(signature: &[u8]) -> String {
287    let mut dictionary = DictSerializer::new();
288    let _ = dictionary.bare_item(key_ref(SIGNATURE_LABEL), signature);
289    dictionary
290        .finish()
291        .expect("a dictionary with one member serializes")
292}
293
294// Structured Fields keeps only the last of duplicate dictionary keys. That is harmless here:
295// the signature base is serialized from the parsed value, so a discarded duplicate is neither
296// checked nor signed.
297fn only_integrity_member(dictionary: &Dictionary) -> Option<&ListEntry> {
298    match dictionary.iter().collect::<Vec<_>>().as_slice() {
299        [(label, member)] if label.as_str() == SIGNATURE_LABEL => Some(member),
300        _ => None,
301    }
302}
303
304/// Why a request nonce was refused.
305#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
306pub enum NonceError {
307    /// The nonce is not canonical padded base64.
308    #[error("nonce is not canonical padded base64")]
309    NotCanonicalBase64,
310    /// The nonce decodes to fewer than [`MIN_NONCE_BYTES`] bytes.
311    #[error("nonce decodes to fewer than {MIN_NONCE_BYTES} bytes")]
312    TooShort,
313}
314
315/// Checks a request nonce: canonical padded base64 decoding to at least [`MIN_NONCE_BYTES`].
316///
317/// A verifier cannot measure entropy, so this is the whole of what it can check.
318///
319/// # Errors
320///
321/// Returns a [`NonceError`] describing the first rule the nonce breaks.
322pub fn validate_nonce(nonce: &str) -> Result<(), NonceError> {
323    let decoded = STANDARD
324        .decode(nonce)
325        .map_err(|_| NonceError::NotCanonicalBase64)?;
326    if STANDARD.encode(&decoded) != nonce {
327        return Err(NonceError::NotCanonicalBase64);
328    }
329    if decoded.len() < MIN_NONCE_BYTES {
330        return Err(NonceError::TooShort);
331    }
332    Ok(())
333}
334
335/// A fresh request nonce: [`MIN_NONCE_BYTES`] bytes from the OS CSPRNG, padded base64.
336///
337/// # Errors
338///
339/// Returns an error when the operating system cannot supply randomness.
340pub fn generate_nonce() -> Result<String, getrandom::Error> {
341    let mut bytes = [0u8; MIN_NONCE_BYTES];
342    getrandom::fill(&mut bytes)?;
343    Ok(STANDARD.encode(bytes))
344}