Skip to main content

monetize_embed/
signing.rs

1//! **The one canonical form** an entitlement is signed over, and the verify side.
2//!
3//! Signing lives in `monetize` (it has the key); verifying lives here because a product
4//! must check a fact without linking the core (redb, vendors). Core depends on this
5//! crate for [`fact_message`] so there is exactly one canonical form.
6//!
7//! Canonical JSON: the fact's fields minus `signature`, objects with keys sorted
8//! bytewise, no whitespace. `serde_json`'s map is sorted by default but that is a cargo
9//! feature (`preserve_order`) any crate in the build could flip, so the sort is done
10//! here, explicitly, and does not depend on it.
11
12use ed25519_dalek::{Signature, Verifier, VerifyingKey};
13use monetize_product::EntitlementFact;
14use serde_json::Value;
15
16/// A signed set of facts, the file monetize hands a product so it can start (or
17/// restart) with the full picture before any push arrives.
18#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
19pub struct Snapshot {
20    /// Monotonic: a cache refuses a snapshot older than the one it holds (replay).
21    pub issued_unix_ms: u64,
22    pub facts: Vec<EntitlementFact>,
23    /// Ed25519 over [`snapshot_message`], by monetize's key.
24    pub signature: Vec<u8>,
25}
26
27#[derive(Debug, thiserror::Error, PartialEq, Eq)]
28pub enum SignatureError {
29    #[error("signature is not 64 bytes")]
30    Malformed,
31    #[error("signature does not verify for tenant {0}")]
32    Fact(String),
33    #[error("snapshot envelope signature does not verify")]
34    Envelope,
35}
36
37/// Write `value` as canonical JSON: keys sorted, no whitespace.
38pub fn canonical_json(value: &Value, out: &mut String) {
39    match value {
40        Value::Object(map) => {
41            let mut keys: Vec<&String> = map.keys().collect();
42            keys.sort();
43            out.push('{');
44            for (i, k) in keys.iter().enumerate() {
45                if i > 0 {
46                    out.push(',');
47                }
48                out.push_str(&serde_json::to_string(k).expect("string"));
49                out.push(':');
50                canonical_json(&map[*k], out);
51            }
52            out.push('}');
53        }
54        Value::Array(items) => {
55            out.push('[');
56            for (i, v) in items.iter().enumerate() {
57                if i > 0 {
58                    out.push(',');
59                }
60                canonical_json(v, out);
61            }
62            out.push(']');
63        }
64        other => out.push_str(&other.to_string()),
65    }
66}
67
68/// The bytes a fact's signature covers: every field except `signature`.
69pub fn fact_message(fact: &EntitlementFact) -> Vec<u8> {
70    let mut v = serde_json::to_value(fact).expect("fact serializes");
71    v.as_object_mut().expect("fact is an object").remove("signature");
72    let mut s = String::new();
73    canonical_json(&v, &mut s);
74    s.into_bytes()
75}
76
77/// The bytes a snapshot's envelope signature covers. The facts inside keep their own
78/// signatures (they are part of the message), so a snapshot vouches for the *set*.
79pub fn snapshot_message(issued_unix_ms: u64, facts: &[EntitlementFact]) -> Vec<u8> {
80    let v = serde_json::json!({ "issued_unix_ms": issued_unix_ms, "facts": facts });
81    let mut s = String::new();
82    canonical_json(&v, &mut s);
83    s.into_bytes()
84}
85
86fn check(key: &VerifyingKey, msg: &[u8], sig: &[u8]) -> Result<bool, SignatureError> {
87    let sig = Signature::from_slice(sig).map_err(|_| SignatureError::Malformed)?;
88    Ok(key.verify(msg, &sig).is_ok())
89}
90
91pub fn verify_fact(fact: &EntitlementFact, key: &VerifyingKey) -> Result<(), SignatureError> {
92    if check(key, &fact_message(fact), &fact.signature)? {
93        Ok(())
94    } else {
95        Err(SignatureError::Fact(fact.tenant.0.clone()))
96    }
97}
98
99/// Envelope first, then every fact: one forged fact rejects the whole snapshot.
100pub fn verify_snapshot(snap: &Snapshot, key: &VerifyingKey) -> Result<(), SignatureError> {
101    if !check(key, &snapshot_message(snap.issued_unix_ms, &snap.facts), &snap.signature)? {
102        return Err(SignatureError::Envelope);
103    }
104    snap.facts.iter().try_for_each(|f| verify_fact(f, key))
105}