use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use monetize_product::EntitlementFact;
use serde_json::Value;
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct Snapshot {
pub issued_unix_ms: u64,
pub facts: Vec<EntitlementFact>,
pub signature: Vec<u8>,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum SignatureError {
#[error("signature is not 64 bytes")]
Malformed,
#[error("signature does not verify for tenant {0}")]
Fact(String),
#[error("snapshot envelope signature does not verify")]
Envelope,
}
pub fn canonical_json(value: &Value, out: &mut String) {
match value {
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
out.push('{');
for (i, k) in keys.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push_str(&serde_json::to_string(k).expect("string"));
out.push(':');
canonical_json(&map[*k], out);
}
out.push('}');
}
Value::Array(items) => {
out.push('[');
for (i, v) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
canonical_json(v, out);
}
out.push(']');
}
other => out.push_str(&other.to_string()),
}
}
pub fn fact_message(fact: &EntitlementFact) -> Vec<u8> {
let mut v = serde_json::to_value(fact).expect("fact serializes");
v.as_object_mut().expect("fact is an object").remove("signature");
let mut s = String::new();
canonical_json(&v, &mut s);
s.into_bytes()
}
pub fn snapshot_message(issued_unix_ms: u64, facts: &[EntitlementFact]) -> Vec<u8> {
let v = serde_json::json!({ "issued_unix_ms": issued_unix_ms, "facts": facts });
let mut s = String::new();
canonical_json(&v, &mut s);
s.into_bytes()
}
fn check(key: &VerifyingKey, msg: &[u8], sig: &[u8]) -> Result<bool, SignatureError> {
let sig = Signature::from_slice(sig).map_err(|_| SignatureError::Malformed)?;
Ok(key.verify(msg, &sig).is_ok())
}
pub fn verify_fact(fact: &EntitlementFact, key: &VerifyingKey) -> Result<(), SignatureError> {
if check(key, &fact_message(fact), &fact.signature)? {
Ok(())
} else {
Err(SignatureError::Fact(fact.tenant.0.clone()))
}
}
pub fn verify_snapshot(snap: &Snapshot, key: &VerifyingKey) -> Result<(), SignatureError> {
if !check(key, &snapshot_message(snap.issued_unix_ms, &snap.facts), &snap.signature)? {
return Err(SignatureError::Envelope);
}
snap.facts.iter().try_for_each(|f| verify_fact(f, key))
}