use ed25519_dalek::SigningKey;
use serde::{Deserialize, Serialize};
use super::binding::require_operator;
use super::{
APPROVAL_DOMAIN, LicenceError, MeshId, SignedEnvelope, envelope_key, parse_canonical,
sha256_hex, sign_envelope, valid_hex32, valid_token, verify_envelope,
};
use crate::workload_pkg::TrustAnchors;
pub const MAX_APPROVAL_HASHES: usize = 32;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Approval {
pub v: u32,
pub mesh_id: String,
pub cog_id: String,
pub version: String,
pub sha256: Vec<String>,
pub approved_at: u64,
}
impl Approval {
pub fn content_key(&self) -> String {
let mut s = format!(
"weft-licence-v1/approval-key\n{}\n{}\n{}",
self.mesh_id, self.cog_id, self.version
);
for h in &self.sha256 {
s.push('\n');
s.push_str(h);
}
sha256_hex(s.as_bytes())
}
pub fn covers(&self, sha256: &str) -> bool {
self.sha256.iter().any(|h| h == sha256)
}
}
pub type SignedApproval = SignedEnvelope;
pub fn sign_approval(
approval: &Approval,
operator: &SigningKey,
) -> Result<SignedApproval, LicenceError> {
let mut a = approval.clone();
a.sha256.sort();
a.sha256.dedup();
sign_envelope(APPROVAL_DOMAIN, &a, operator)
}
pub fn verify_approval(
signed: &SignedApproval,
anchors: &TrustAnchors,
local: &MeshId,
) -> Result<Approval, LicenceError> {
let a = verify_approval_signature(signed, anchors)?;
if a.mesh_id != local.to_hex() {
return Err(LicenceError::WrongMesh);
}
Ok(a)
}
pub fn verify_approval_signature(
signed: &SignedApproval,
anchors: &TrustAnchors,
) -> Result<Approval, LicenceError> {
let pk = envelope_key(signed)?;
require_operator(anchors, &pk)?;
verify_envelope(APPROVAL_DOMAIN, signed, &pk)?;
let a: Approval = parse_canonical(&signed.payload)?;
let sorted = a.sha256.windows(2).all(|w| w[0] < w[1]);
if a.v != 1
|| !valid_hex32(&a.mesh_id)
|| !valid_token(&a.cog_id)
|| !valid_token(&a.version)
|| a.sha256.is_empty()
|| a.sha256.len() > MAX_APPROVAL_HASHES
|| !sorted
|| !a.sha256.iter().all(|h| valid_hex32(h))
{
return Err(LicenceError::Malformed("approval field".into()));
}
Ok(a)
}