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    /// A field to sign whose key is not text.
31    FieldKeyNotText,
32    /// Two fields to sign with one key, named here.
33    DuplicateField(String),
34    /// The key could not sign.
35    Key(KeyError),
36}
37
38impl fmt::Display for ObjectError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            ObjectError::Malformed => f.write_str("malformed signed object"),
42            ObjectError::SignatureInvalid => {
43                f.write_str("the signed object's signature does not verify")
44            }
45            ObjectError::AlgMismatch => {
46                f.write_str("the signed object names another profile's algorithm")
47            }
48            ObjectError::FieldKeyNotText => f.write_str("a field key that is not text"),
49            ObjectError::DuplicateField(name) => write!(f, "two fields named {name:?}"),
50            ObjectError::Key(e) => write!(f, "{e}"),
51        }
52    }
53}
54
55impl std::error::Error for ObjectError {}
56
57/// A signed object that carries its signer's key as carried.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct Object {
60    pub key: Vec<u8>,
61    pub tbs: Vec<u8>,
62    pub signature: Vec<u8>,
63}
64
65/// A signed object whose verifier already holds the signer's key.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct HeldObject {
68    pub tbs: Vec<u8>,
69    pub signature: Vec<u8>,
70}
71
72/// A signed object that verified: the key it verified with, its tbs bytes as
73/// received, and the map they decode to.
74#[derive(Debug, Clone, PartialEq)]
75pub struct VerifiedObject {
76    pub key: Vec<u8>,
77    pub tbs: Vec<u8>,
78    pub fields: Value,
79}
80
81impl Object {
82    /// The object as the map `{key, tbs, signature}`.
83    pub fn to_value(&self) -> Value {
84        Value::Map(vec![
85            (Value::text("key"), Value::Bytes(self.key.clone())),
86            (Value::text("tbs"), Value::Bytes(self.tbs.clone())),
87            (
88                Value::text("signature"),
89                Value::Bytes(self.signature.clone()),
90            ),
91        ])
92    }
93
94    /// The object in `value`, a map of exactly `key`, `tbs` and `signature`,
95    /// each a byte string.
96    pub fn from_value(value: &Value) -> Result<Object, ObjectError> {
97        let [key, tbs, signature] = exact_byte_fields(value, ["key", "tbs", "signature"])?;
98        Ok(Object {
99            key,
100            tbs,
101            signature,
102        })
103    }
104}
105
106impl HeldObject {
107    /// The object as the map `{tbs, signature}`.
108    pub fn to_value(&self) -> Value {
109        Value::Map(vec![
110            (Value::text("tbs"), Value::Bytes(self.tbs.clone())),
111            (
112                Value::text("signature"),
113                Value::Bytes(self.signature.clone()),
114            ),
115        ])
116    }
117
118    /// The object in `value`, a map of exactly `tbs` and `signature`, each a
119    /// byte string.
120    pub fn from_value(value: &Value) -> Result<HeldObject, ObjectError> {
121        let [tbs, signature] = exact_byte_fields(value, ["tbs", "signature"])?;
122        Ok(HeldObject { tbs, signature })
123    }
124}
125
126/// Signs `fields` under `label` with `key`. The fields gain alg, replacing any
127/// alg they held; every field needs a text key of its own.
128pub fn sign_object(
129    label: &str,
130    fields: &[(Value, Value)],
131    key: &NodeKey,
132) -> Result<Object, ObjectError> {
133    let carried = key.public_key();
134    let tbs = object_tbs(fields, key.profile())?;
135    let signature = key
136        .sign(&object_signed_bytes(label, &carried, &tbs))
137        .map_err(ObjectError::Key)?;
138    Ok(Object {
139        key: carried,
140        tbs,
141        signature,
142    })
143}
144
145/// [`sign_object`] for a verifier that already holds `key`.
146pub fn sign_held_object(
147    label: &str,
148    fields: &[(Value, Value)],
149    key: &NodeKey,
150) -> Result<HeldObject, ObjectError> {
151    let object = sign_object(label, fields, key)?;
152    Ok(HeldObject {
153        tbs: object.tbs,
154        signature: object.signature,
155    })
156}
157
158/// Verifies an object that carries its key, under `label` and the verifier's
159/// `profile`, in macula's order: exactly key, tbs and signature, each a byte
160/// string; key in the profile's carried form; the signature over tbs as
161/// received; only then tbs under the decoding rule, a map whose alg names the
162/// profile's algorithm. alg is checked and never selects an algorithm.
163pub fn verify_object(
164    label: &str,
165    value: &Value,
166    profile: Profile,
167) -> Result<VerifiedObject, ObjectError> {
168    let object = Object::from_value(value)?;
169    if !carried_key_well_formed(&object.key, profile) {
170        return Err(ObjectError::Malformed);
171    }
172    verified(label, object.key, object.tbs, &object.signature, profile)
173}
174
175/// Verifies an object whose key the verifier holds, as carried, under `label`
176/// and the verifier's `profile`.
177pub fn verify_held_object(
178    label: &str,
179    value: &Value,
180    key: &[u8],
181    profile: Profile,
182) -> Result<VerifiedObject, ObjectError> {
183    let held = HeldObject::from_value(value)?;
184    verified(label, key.to_vec(), held.tbs, &held.signature, profile)
185}
186
187fn verified(
188    label: &str,
189    key: Vec<u8>,
190    tbs: Vec<u8>,
191    signature: &[u8],
192    profile: Profile,
193) -> Result<VerifiedObject, ObjectError> {
194    if !verify(
195        &object_signed_bytes(label, &key, &tbs),
196        signature,
197        &key,
198        profile,
199    ) {
200        return Err(ObjectError::SignatureInvalid);
201    }
202    let fields = cbor::decode(&tbs).map_err(|_| ObjectError::Malformed)?;
203    let alg = match (&fields, fields.get("alg")) {
204        (Value::Map(_), Some(Value::Text(alg))) => alg.clone(),
205        _ => return Err(ObjectError::Malformed),
206    };
207    if alg != profile.sig_alg() {
208        return Err(ObjectError::AlgMismatch);
209    }
210    Ok(VerifiedObject { key, tbs, fields })
211}
212
213/// The fields with alg for `profile`, deterministically encoded.
214fn object_tbs(fields: &[(Value, Value)], profile: Profile) -> Result<Vec<u8>, ObjectError> {
215    let mut seen = std::collections::HashSet::with_capacity(fields.len());
216    let mut with_alg = Vec::with_capacity(fields.len() + 1);
217    for (key, value) in fields {
218        let Value::Text(name) = key else {
219            return Err(ObjectError::FieldKeyNotText);
220        };
221        if seen.contains(name.as_str()) {
222            return Err(ObjectError::DuplicateField(name.clone()));
223        }
224        if name == "alg" {
225            continue;
226        }
227        seen.insert(name.as_str());
228        with_alg.push((key.clone(), value.clone()));
229    }
230    with_alg.push((Value::text("alg"), Value::text(profile.sig_alg())));
231    cbor::encode(&Value::Map(with_alg)).map_err(|_| ObjectError::Malformed)
232}
233
234/// What a signed object's signature covers: label, a zero byte, the SHA-384
235/// of the key as carried, and tbs.
236fn object_signed_bytes(label: &str, key: &[u8], tbs: &[u8]) -> Vec<u8> {
237    let mut out = Vec::with_capacity(label.len() + 1 + 48 + tbs.len());
238    out.extend_from_slice(label.as_bytes());
239    out.push(0);
240    out.extend_from_slice(&Sha384::digest(key));
241    out.extend_from_slice(tbs);
242    out
243}
244
245/// The byte strings under `names` in `value`, when it is a map of exactly
246/// those text keys, each holding a byte string.
247fn exact_byte_fields<const N: usize>(
248    value: &Value,
249    names: [&str; N],
250) -> Result<[Vec<u8>; N], ObjectError> {
251    let Value::Map(pairs) = value else {
252        return Err(ObjectError::Malformed);
253    };
254    if pairs.len() != N {
255        return Err(ObjectError::Malformed);
256    }
257    let mut out: [Vec<u8>; N] = std::array::from_fn(|_| Vec::new());
258    for (slot, name) in out.iter_mut().zip(names) {
259        match value.get(name) {
260            Some(Value::Bytes(b)) => *slot = b.clone(),
261            _ => return Err(ObjectError::Malformed),
262        }
263    }
264    Ok(out)
265}