Skip to main content

macula_rust/
signed_object.rs

1//! Signed objects, as macula_signed_object and macula-go sign and verify them:
2//! a record, a request, a reply, a relay error, a publication, or a stream
3//! frame. The fields gain `alg`, the signer's profile algorithm, and are
4//! encoded as tbs in the deterministic form; the signature covers the label, a
5//! zero byte, the SHA-384 of the signer's key as carried, and tbs. An
6//! [`Object`] carries its key; a [`HeldObject`] leaves it out for a verifier
7//! that already holds it, and still signs its hash.
8
9use std::fmt;
10
11use sha2::{Digest, Sha384};
12
13use crate::cbor::{self, Value};
14use crate::node_key::{carried_key_well_formed, verify, KeyError, NodeKey};
15use crate::profile::Profile;
16
17/// The refusals of a signed object, named as macula_signed_object names them,
18/// and the ones signing gives.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum ObjectError {
21    /// Not exactly the keys of its shape, each a byte string; a carried key
22    /// not in the verifier's profile's carried form; or a tbs the decoding
23    /// rule refuses, or that is not a map naming alg as text.
24    Malformed,
25    /// A signature that does not verify over the label, the key's hash and
26    /// the tbs as received.
27    SignatureInvalid,
28    /// An alg that names another profile's algorithm than the verifier's.
29    AlgMismatch,
30    /// An alg the verifier's allowlist does not hold (`alg_unsupported`,
31    /// WIRE_1_0.md section 7), refused before any signature work.
32    AlgUnsupported(String),
33    /// A field to sign whose key is not text.
34    FieldKeyNotText,
35    /// Two fields to sign with one key, named here.
36    DuplicateField(String),
37    /// The key could not sign.
38    Key(KeyError),
39}
40
41impl fmt::Display for ObjectError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            ObjectError::Malformed => f.write_str("malformed signed object"),
45            ObjectError::SignatureInvalid => {
46                f.write_str("the signed object's signature does not verify")
47            }
48            ObjectError::AlgMismatch => {
49                f.write_str("the signed object names another profile's algorithm")
50            }
51            ObjectError::AlgUnsupported(alg) => {
52                write!(f, "an alg the verifier does not take: {alg:?}")
53            }
54            ObjectError::FieldKeyNotText => f.write_str("a field key that is not text"),
55            ObjectError::DuplicateField(name) => write!(f, "two fields named {name:?}"),
56            ObjectError::Key(e) => write!(f, "{e}"),
57        }
58    }
59}
60
61impl std::error::Error for ObjectError {}
62
63/// A signed object that carries its signer's key as carried.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct Object {
66    pub key: Vec<u8>,
67    pub tbs: Vec<u8>,
68    pub signature: Vec<u8>,
69}
70
71/// A signed object whose verifier already holds the signer's key.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct HeldObject {
74    pub tbs: Vec<u8>,
75    pub signature: Vec<u8>,
76}
77
78/// A signed object that verified: the key it verified with, its tbs bytes as
79/// received, and the map they decode to.
80#[derive(Debug, Clone, PartialEq)]
81pub struct VerifiedObject {
82    pub key: Vec<u8>,
83    pub tbs: Vec<u8>,
84    pub fields: Value,
85}
86
87impl Object {
88    /// The object as the map `{key, tbs, signature}`.
89    pub fn to_value(&self) -> Value {
90        Value::Map(vec![
91            (Value::text("key"), Value::Bytes(self.key.clone())),
92            (Value::text("tbs"), Value::Bytes(self.tbs.clone())),
93            (
94                Value::text("signature"),
95                Value::Bytes(self.signature.clone()),
96            ),
97        ])
98    }
99
100    /// The object in `value`, a map of exactly `key`, `tbs` and `signature`,
101    /// each a byte string.
102    pub fn from_value(value: &Value) -> Result<Object, ObjectError> {
103        let [key, tbs, signature] = exact_byte_fields(value, ["key", "tbs", "signature"])?;
104        Ok(Object {
105            key,
106            tbs,
107            signature,
108        })
109    }
110}
111
112impl HeldObject {
113    /// The object as the map `{tbs, signature}`.
114    pub fn to_value(&self) -> Value {
115        Value::Map(vec![
116            (Value::text("tbs"), Value::Bytes(self.tbs.clone())),
117            (
118                Value::text("signature"),
119                Value::Bytes(self.signature.clone()),
120            ),
121        ])
122    }
123
124    /// The object in `value`, a map of exactly `tbs` and `signature`, each a
125    /// byte string.
126    pub fn from_value(value: &Value) -> Result<HeldObject, ObjectError> {
127        let [tbs, signature] = exact_byte_fields(value, ["tbs", "signature"])?;
128        Ok(HeldObject { tbs, signature })
129    }
130}
131
132/// Signs `fields` under `label` with `key`. The fields gain alg, replacing any
133/// alg they held; every field needs a text key of its own.
134pub fn sign_object(
135    label: &str,
136    fields: &[(Value, Value)],
137    key: &NodeKey,
138) -> Result<Object, ObjectError> {
139    let carried = key.public_key();
140    let tbs = object_tbs(fields, key.profile())?;
141    let signature = key
142        .sign(&object_signed_bytes(label, &carried, &tbs))
143        .map_err(ObjectError::Key)?;
144    Ok(Object {
145        key: carried,
146        tbs,
147        signature,
148    })
149}
150
151/// [`sign_object`] for a verifier that already holds `key`.
152pub fn sign_held_object(
153    label: &str,
154    fields: &[(Value, Value)],
155    key: &NodeKey,
156) -> Result<HeldObject, ObjectError> {
157    let object = sign_object(label, fields, key)?;
158    Ok(HeldObject {
159        tbs: object.tbs,
160        signature: object.signature,
161    })
162}
163
164/// Verifies an object that carries its key, under `label` and the verifier's
165/// `profile`, in macula's order: exactly key, tbs and signature, each a byte
166/// string; key in the profile's carried form; the signature over tbs as
167/// received; only then tbs under the decoding rule, a map whose alg names the
168/// profile's algorithm. alg is checked and never selects an algorithm.
169pub fn verify_object(
170    label: &str,
171    value: &Value,
172    profile: Profile,
173) -> Result<VerifiedObject, ObjectError> {
174    let object = Object::from_value(value)?;
175    if !carried_key_well_formed(&object.key, profile) {
176        return Err(ObjectError::Malformed);
177    }
178    verified(label, object.key, object.tbs, &object.signature, profile)
179}
180
181/// Verifies an object that carries its key, under `label`, with the verifier
182/// its alg selects from `profile`'s allowlist (WIRE_1_0.md sections 5.2 and
183/// 7), in this order: exactly key, tbs and signature, each a byte string; tbs
184/// under the decoding rule, a map holding alg as text, nothing else in it
185/// acted on before the signature; alg on the allowlist, which is the
186/// profile's own algorithm alone, so the other profile's is refused before
187/// any crypto ([`ObjectError::AlgUnsupported`]); key in that algorithm's
188/// carried form; the signature over tbs as received.
189pub fn verify_object_by_alg(
190    label: &str,
191    value: &Value,
192    profile: Profile,
193) -> Result<VerifiedObject, ObjectError> {
194    let object = Object::from_value(value)?;
195    let fields = cbor::decode(&object.tbs).map_err(|_| ObjectError::Malformed)?;
196    let alg = match (&fields, fields.get("alg")) {
197        (Value::Map(_), Some(Value::Text(alg))) => alg.clone(),
198        _ => return Err(ObjectError::Malformed),
199    };
200    if alg != profile.sig_alg() {
201        return Err(ObjectError::AlgUnsupported(alg));
202    }
203    if !carried_key_well_formed(&object.key, profile) {
204        return Err(ObjectError::Malformed);
205    }
206    let signed = object_signed_bytes(label, &object.key, &object.tbs);
207    if !verify(&signed, &object.signature, &object.key, profile) {
208        return Err(ObjectError::SignatureInvalid);
209    }
210    Ok(VerifiedObject {
211        key: object.key,
212        tbs: object.tbs,
213        fields,
214    })
215}
216
217/// Verifies an object whose key the verifier holds, as carried, under `label`
218/// and the verifier's `profile`.
219pub fn verify_held_object(
220    label: &str,
221    value: &Value,
222    key: &[u8],
223    profile: Profile,
224) -> Result<VerifiedObject, ObjectError> {
225    let held = HeldObject::from_value(value)?;
226    verified(label, key.to_vec(), held.tbs, &held.signature, profile)
227}
228
229fn verified(
230    label: &str,
231    key: Vec<u8>,
232    tbs: Vec<u8>,
233    signature: &[u8],
234    profile: Profile,
235) -> Result<VerifiedObject, ObjectError> {
236    if !verify(
237        &object_signed_bytes(label, &key, &tbs),
238        signature,
239        &key,
240        profile,
241    ) {
242        return Err(ObjectError::SignatureInvalid);
243    }
244    let fields = cbor::decode(&tbs).map_err(|_| ObjectError::Malformed)?;
245    let alg = match (&fields, fields.get("alg")) {
246        (Value::Map(_), Some(Value::Text(alg))) => alg.clone(),
247        _ => return Err(ObjectError::Malformed),
248    };
249    if alg != profile.sig_alg() {
250        return Err(ObjectError::AlgMismatch);
251    }
252    Ok(VerifiedObject { key, tbs, fields })
253}
254
255/// The fields with alg for `profile`, deterministically encoded.
256fn object_tbs(fields: &[(Value, Value)], profile: Profile) -> Result<Vec<u8>, ObjectError> {
257    let mut seen = std::collections::HashSet::with_capacity(fields.len());
258    let mut with_alg = Vec::with_capacity(fields.len() + 1);
259    for (key, value) in fields {
260        let Value::Text(name) = key else {
261            return Err(ObjectError::FieldKeyNotText);
262        };
263        if seen.contains(name.as_str()) {
264            return Err(ObjectError::DuplicateField(name.clone()));
265        }
266        if name == "alg" {
267            continue;
268        }
269        seen.insert(name.as_str());
270        with_alg.push((key.clone(), value.clone()));
271    }
272    with_alg.push((Value::text("alg"), Value::text(profile.sig_alg())));
273    cbor::encode(&Value::Map(with_alg)).map_err(|_| ObjectError::Malformed)
274}
275
276/// What a signed object's signature covers: label, a zero byte, the SHA-384
277/// of the key as carried, and tbs.
278fn object_signed_bytes(label: &str, key: &[u8], tbs: &[u8]) -> Vec<u8> {
279    let mut out = Vec::with_capacity(label.len() + 1 + 48 + tbs.len());
280    out.extend_from_slice(label.as_bytes());
281    out.push(0);
282    out.extend_from_slice(&Sha384::digest(key));
283    out.extend_from_slice(tbs);
284    out
285}
286
287/// The byte strings under `names` in `value`, when it is a map of exactly
288/// those text keys, each holding a byte string.
289fn exact_byte_fields<const N: usize>(
290    value: &Value,
291    names: [&str; N],
292) -> Result<[Vec<u8>; N], ObjectError> {
293    let Value::Map(pairs) = value else {
294        return Err(ObjectError::Malformed);
295    };
296    if pairs.len() != N {
297        return Err(ObjectError::Malformed);
298    }
299    let mut out: [Vec<u8>; N] = std::array::from_fn(|_| Vec::new());
300    for (slot, name) in out.iter_mut().zip(names) {
301        match value.get(name) {
302            Some(Value::Bytes(b)) => *slot = b.clone(),
303            _ => return Err(ObjectError::Malformed),
304        }
305    }
306    Ok(out)
307}
308
309#[cfg(test)]
310mod wire_1_0_tests {
311    //! The wire 1.0 tbs vectors (macula#97, tbs.json): every signed structure's
312    //! fields encode to exactly its canonical tbs, its signing input is the
313    //! label, a zero byte, the SHA-384 of the signer's key as carried when the
314    //! form is keyed, and the tbs, and its signature verifies under the
315    //! signer's key.
316
317    use super::*;
318    use crate::wire_vectors::{self, hex_of, typed};
319
320    #[test]
321    fn the_wire_1_0_tbs_vectors() {
322        let file = wire_vectors::load("tbs");
323        for c in &file.cases {
324            let profile = Profile::parse(&c.profile).unwrap();
325            let key = wire_vectors::key(&c.profile, c.text("signer"));
326            let tbs = cbor::encode(&typed(&c.input["fields"])).unwrap();
327            assert_eq!(hex::encode(&tbs), c.expect_text("tbs"), "{}: the tbs", c.id);
328            let input = match c.text("form") {
329                "keyed" => object_signed_bytes(c.text("label"), &key, &tbs),
330                _ => crate::binding::labelled(c.text("label"), &tbs),
331            };
332            assert_eq!(
333                hex::encode(&input),
334                c.expect_text("signing_input"),
335                "{}",
336                c.id
337            );
338            let signature = hex_of(&c.expect["signature"]);
339            assert!(
340                verify(&input, &signature, &key, profile),
341                "{}: the signature",
342                c.id
343            );
344        }
345        assert_eq!(file.cases.len(), 30);
346    }
347}