clawft_kernel/licence/
approval.rs1use ed25519_dalek::SigningKey;
9use serde::{Deserialize, Serialize};
10
11use super::binding::require_operator;
12use super::{
13 APPROVAL_DOMAIN, LicenceError, MeshId, SignedEnvelope, envelope_key, parse_canonical,
14 sha256_hex, sign_envelope, valid_hex32, valid_token, verify_envelope,
15};
16use crate::workload_pkg::TrustAnchors;
17
18pub const MAX_APPROVAL_HASHES: usize = 32;
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(deny_unknown_fields)]
24pub struct Approval {
25 pub v: u32,
27 pub mesh_id: String,
29 pub cog_id: String,
31 pub version: String,
33 pub sha256: Vec<String>,
35 pub approved_at: u64,
37}
38
39impl Approval {
40 pub fn content_key(&self) -> String {
44 let mut s = format!(
45 "weft-licence-v1/approval-key\n{}\n{}\n{}",
46 self.mesh_id, self.cog_id, self.version
47 );
48 for h in &self.sha256 {
49 s.push('\n');
50 s.push_str(h);
51 }
52 sha256_hex(s.as_bytes())
53 }
54
55 pub fn covers(&self, sha256: &str) -> bool {
57 self.sha256.iter().any(|h| h == sha256)
58 }
59}
60
61pub type SignedApproval = SignedEnvelope;
63
64pub fn sign_approval(
67 approval: &Approval,
68 operator: &SigningKey,
69) -> Result<SignedApproval, LicenceError> {
70 let mut a = approval.clone();
71 a.sha256.sort();
72 a.sha256.dedup();
73 sign_envelope(APPROVAL_DOMAIN, &a, operator)
74}
75
76pub fn verify_approval(
79 signed: &SignedApproval,
80 anchors: &TrustAnchors,
81 local: &MeshId,
82) -> Result<Approval, LicenceError> {
83 let a = verify_approval_signature(signed, anchors)?;
84 if a.mesh_id != local.to_hex() {
85 return Err(LicenceError::WrongMesh);
86 }
87 Ok(a)
88}
89
90pub fn verify_approval_signature(
93 signed: &SignedApproval,
94 anchors: &TrustAnchors,
95) -> Result<Approval, LicenceError> {
96 let pk = envelope_key(signed)?;
97 require_operator(anchors, &pk)?;
98 verify_envelope(APPROVAL_DOMAIN, signed, &pk)?;
99 let a: Approval = parse_canonical(&signed.payload)?;
100 let sorted = a.sha256.windows(2).all(|w| w[0] < w[1]);
101 if a.v != 1
102 || !valid_hex32(&a.mesh_id)
103 || !valid_token(&a.cog_id)
104 || !valid_token(&a.version)
105 || a.sha256.is_empty()
106 || a.sha256.len() > MAX_APPROVAL_HASHES
107 || !sorted
108 || !a.sha256.iter().all(|h| valid_hex32(h))
109 {
110 return Err(LicenceError::Malformed("approval field".into()));
111 }
112 Ok(a)
113}