1use std::fmt;
13
14use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
15use serde::de::DeserializeOwned;
16use serde::{Deserialize, Serialize};
17use sha2::{Digest, Sha256};
18
19mod binding;
20mod grant;
21pub mod request;
22pub use binding::{
23 BindState, BindingRecord, SignedBinding, sign_binding, verify_binding_signature,
24};
25pub use grant::{
26 CheckoutGrant, GrantArtifact, LicenceRef, MAX_GRANT_ARTIFACTS, SignedGrant, sign_grant,
27 verify_grant, verify_grant_signature,
28};
29
30pub fn hex_encode(bytes: &[u8]) -> String {
32 const DIGITS: &[u8; 16] = b"0123456789abcdef";
33 let mut s = String::with_capacity(bytes.len() * 2);
34 for b in bytes {
35 s.push(DIGITS[(b >> 4) as usize] as char);
36 s.push(DIGITS[(b & 0x0f) as usize] as char);
37 }
38 s
39}
40
41pub fn hex_decode_exact<const N: usize>(s: &str) -> Option<[u8; N]> {
44 if s.len() != N * 2 {
45 return None;
46 }
47 let mut out = [0u8; N];
48 let bytes = s.as_bytes();
49 for (i, slot) in out.iter_mut().enumerate() {
50 let hi = hex_val(bytes[2 * i])?;
51 let lo = hex_val(bytes[2 * i + 1])?;
52 *slot = (hi << 4) | lo;
53 }
54 Some(out)
55}
56
57fn hex_val(b: u8) -> Option<u8> {
58 match b {
59 b'0'..=b'9' => Some(b - b'0'),
60 b'a'..=b'f' => Some(b - b'a' + 10),
61 _ => None,
62 }
63}
64
65pub const MESH_ID_DOMAIN: &str = "weft-licence-v1/mesh-id";
67pub const BINDING_DOMAIN: &str = "weft-licence-v1/binding";
69pub const GRANT_DOMAIN: &str = "weft-licence-v1/grant";
71pub const APPROVAL_DOMAIN: &str = "weft-licence-v1/approval";
73
74pub const MAX_PAYLOAD_BYTES: usize = 16 * 1024;
76pub const MAX_STORE_BYTES: u64 = 8 * 1024 * 1024;
78pub const GRANT_SKEW_SECS: u64 = 300;
80pub const MAX_GRANT_TTL_SECS: u64 = 7 * 24 * 3600;
82pub const FAR_FUTURE_CLAMP_SECS: u64 = 30 * 24 * 3600;
84pub const MAX_UNIX_TIME: u64 = 4_102_444_800;
86pub const MAX_ARTIFACT_BYTES: u64 = 1 << 30;
88pub const MAX_FLOORS: usize = 8;
90pub const MAX_GRANT_SLOTS: usize = 512;
92pub const MAX_APPROVALS: usize = 4096;
94
95#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
97pub enum LicenceError {
98 #[error("malformed licence record: {0}")]
100 Malformed(String),
101 #[error("licence record too large")]
103 TooLarge,
104 #[error("licence signature does not verify")]
106 BadSignature,
107 #[error("licence signer is not trusted for this record")]
110 UntrustedKey,
111 #[error("grant key is revoked")]
113 KeyRevoked,
114 #[error("record is for another mesh")]
116 WrongMesh,
117 #[error("no local mesh id")]
119 NoLocalMesh,
120 #[error("no binding")]
122 NoBinding,
123 #[error("binding is unbound")]
125 Unbound,
126 #[error("binding is orphaned")]
128 Orphaned,
129 #[error("conflicting records at seq {0}")]
131 Conflict(u64),
132 #[error("newer grant drops arch {0}")]
134 DropsArch(String),
135 #[error("newer grant changes artifact {0}")]
137 ChangesArtifact(String),
138 #[error("grant is not yet valid")]
140 NotYetValid,
141 #[error("grant lifetime exceeds the maximum")]
143 TtlTooLong,
144 #[error("binding refused: {0}")]
146 BindingRefused(&'static str),
147 #[error("binding check failed: {0}")]
149 CheckFailed(String),
150 #[error("licence store is poisoned: {0}")]
152 Poisoned(String),
153 #[error("licence store persist failed: {0}")]
155 Persist(String),
156 #[error("licence store is full")]
158 Full,
159}
160
161#[derive(Clone, Copy, PartialEq, Eq, Hash)]
164pub struct MeshId([u8; 32]);
165
166impl MeshId {
167 pub fn derive(genesis_pin: &[u8; 32], mesh_nonce: &[u8; 32]) -> Self {
169 let mut h = Sha256::new();
170 h.update(MESH_ID_DOMAIN.as_bytes());
171 h.update(b"\n");
172 h.update(genesis_pin);
173 h.update(mesh_nonce);
174 Self(h.finalize().into())
175 }
176
177 pub fn from_hex(s: &str) -> Option<Self> {
179 hex_decode_exact::<32>(s).map(Self)
180 }
181
182 pub fn to_hex(&self) -> String {
184 hex_encode(&self.0)
185 }
186}
187
188impl fmt::Debug for MeshId {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 write!(f, "MeshId({})", &self.to_hex()[..12])
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(deny_unknown_fields)]
198pub struct SignedEnvelope {
199 pub payload: String,
201 pub public_key: String,
203 pub signature: String,
205}
206
207pub fn signed_bytes(domain: &str, payload: &str) -> Vec<u8> {
208 let mut v = Vec::with_capacity(domain.len() + 1 + payload.len());
209 v.extend_from_slice(domain.as_bytes());
210 v.push(b'\n');
211 v.extend_from_slice(payload.as_bytes());
212 v
213}
214
215pub fn sign_envelope<T: Serialize>(
217 domain: &str,
218 record: &T,
219 key: &SigningKey,
220) -> Result<SignedEnvelope, LicenceError> {
221 let payload =
222 serde_json::to_string(record).map_err(|e| LicenceError::Malformed(e.to_string()))?;
223 let sig = key.sign(&signed_bytes(domain, &payload));
224 Ok(SignedEnvelope {
225 payload,
226 public_key: hex_encode(&key.verifying_key().to_bytes()),
227 signature: hex_encode(&sig.to_bytes()),
228 })
229}
230
231pub fn envelope_key(env: &SignedEnvelope) -> Result<[u8; 32], LicenceError> {
233 if env.payload.len() > MAX_PAYLOAD_BYTES {
234 return Err(LicenceError::TooLarge);
235 }
236 hex_decode_exact::<32>(&env.public_key)
237 .ok_or_else(|| LicenceError::Malformed("public key".into()))
238}
239
240pub fn verify_envelope(
242 domain: &str,
243 env: &SignedEnvelope,
244 pk: &[u8; 32],
245) -> Result<(), LicenceError> {
246 let sig = hex_decode_exact::<64>(&env.signature)
247 .ok_or_else(|| LicenceError::Malformed("signature".into()))?;
248 let vk = VerifyingKey::from_bytes(pk).map_err(|_| LicenceError::BadSignature)?;
249 vk.verify_strict(
250 &signed_bytes(domain, &env.payload),
251 &Signature::from_bytes(&sig),
252 )
253 .map_err(|_| LicenceError::BadSignature)
254}
255
256pub fn parse_canonical<T: Serialize + DeserializeOwned>(
259 payload: &str,
260) -> Result<T, LicenceError> {
261 let v: T =
262 serde_json::from_str(payload).map_err(|e| LicenceError::Malformed(e.to_string()))?;
263 let again = serde_json::to_string(&v).map_err(|e| LicenceError::Malformed(e.to_string()))?;
264 if again != payload {
265 return Err(LicenceError::Malformed("payload is not canonical".into()));
266 }
267 Ok(v)
268}
269
270pub fn sha256_hex(bytes: &[u8]) -> String {
272 hex_encode(&Sha256::digest(bytes))
273}
274
275pub fn key_id(pk: &[u8; 32]) -> String {
278 format!("ed25519:{}", &sha256_hex(pk)[..16])
279}
280
281pub fn valid_token(s: &str) -> bool {
283 !s.is_empty()
284 && s.len() <= 128
285 && s.chars()
286 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-' | '+' | '@' | '/'))
287}
288
289pub fn valid_hex32(s: &str) -> bool {
291 hex_decode_exact::<32>(s).is_some()
292}