use std::fmt;
use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
mod binding;
mod grant;
pub mod request;
pub use binding::{
BindState, BindingRecord, SignedBinding, sign_binding, verify_binding_signature,
};
pub use grant::{
CheckoutGrant, GrantArtifact, LicenceRef, MAX_GRANT_ARTIFACTS, SignedGrant, sign_grant,
verify_grant, verify_grant_signature,
};
pub fn hex_encode(bytes: &[u8]) -> String {
const DIGITS: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push(DIGITS[(b >> 4) as usize] as char);
s.push(DIGITS[(b & 0x0f) as usize] as char);
}
s
}
pub fn hex_decode_exact<const N: usize>(s: &str) -> Option<[u8; N]> {
if s.len() != N * 2 {
return None;
}
let mut out = [0u8; N];
let bytes = s.as_bytes();
for (i, slot) in out.iter_mut().enumerate() {
let hi = hex_val(bytes[2 * i])?;
let lo = hex_val(bytes[2 * i + 1])?;
*slot = (hi << 4) | lo;
}
Some(out)
}
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
_ => None,
}
}
pub const MESH_ID_DOMAIN: &str = "weft-licence-v1/mesh-id";
pub const BINDING_DOMAIN: &str = "weft-licence-v1/binding";
pub const GRANT_DOMAIN: &str = "weft-licence-v1/grant";
pub const APPROVAL_DOMAIN: &str = "weft-licence-v1/approval";
pub const MAX_PAYLOAD_BYTES: usize = 16 * 1024;
pub const MAX_STORE_BYTES: u64 = 8 * 1024 * 1024;
pub const GRANT_SKEW_SECS: u64 = 300;
pub const MAX_GRANT_TTL_SECS: u64 = 7 * 24 * 3600;
pub const FAR_FUTURE_CLAMP_SECS: u64 = 30 * 24 * 3600;
pub const MAX_UNIX_TIME: u64 = 4_102_444_800;
pub const MAX_ARTIFACT_BYTES: u64 = 1 << 30;
pub const MAX_FLOORS: usize = 8;
pub const MAX_GRANT_SLOTS: usize = 512;
pub const MAX_APPROVALS: usize = 4096;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LicenceError {
#[error("malformed licence record: {0}")]
Malformed(String),
#[error("licence record too large")]
TooLarge,
#[error("licence signature does not verify")]
BadSignature,
#[error("licence signer is not trusted for this record")]
UntrustedKey,
#[error("grant key is revoked")]
KeyRevoked,
#[error("record is for another mesh")]
WrongMesh,
#[error("no local mesh id")]
NoLocalMesh,
#[error("no binding")]
NoBinding,
#[error("binding is unbound")]
Unbound,
#[error("binding is orphaned")]
Orphaned,
#[error("conflicting records at seq {0}")]
Conflict(u64),
#[error("newer grant drops arch {0}")]
DropsArch(String),
#[error("newer grant changes artifact {0}")]
ChangesArtifact(String),
#[error("grant is not yet valid")]
NotYetValid,
#[error("grant lifetime exceeds the maximum")]
TtlTooLong,
#[error("binding refused: {0}")]
BindingRefused(&'static str),
#[error("binding check failed: {0}")]
CheckFailed(String),
#[error("licence store is poisoned: {0}")]
Poisoned(String),
#[error("licence store persist failed: {0}")]
Persist(String),
#[error("licence store is full")]
Full,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct MeshId([u8; 32]);
impl MeshId {
pub fn derive(genesis_pin: &[u8; 32], mesh_nonce: &[u8; 32]) -> Self {
let mut h = Sha256::new();
h.update(MESH_ID_DOMAIN.as_bytes());
h.update(b"\n");
h.update(genesis_pin);
h.update(mesh_nonce);
Self(h.finalize().into())
}
pub fn from_hex(s: &str) -> Option<Self> {
hex_decode_exact::<32>(s).map(Self)
}
pub fn to_hex(&self) -> String {
hex_encode(&self.0)
}
}
impl fmt::Debug for MeshId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MeshId({})", &self.to_hex()[..12])
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SignedEnvelope {
pub payload: String,
pub public_key: String,
pub signature: String,
}
pub fn signed_bytes(domain: &str, payload: &str) -> Vec<u8> {
let mut v = Vec::with_capacity(domain.len() + 1 + payload.len());
v.extend_from_slice(domain.as_bytes());
v.push(b'\n');
v.extend_from_slice(payload.as_bytes());
v
}
pub fn sign_envelope<T: Serialize>(
domain: &str,
record: &T,
key: &SigningKey,
) -> Result<SignedEnvelope, LicenceError> {
let payload =
serde_json::to_string(record).map_err(|e| LicenceError::Malformed(e.to_string()))?;
let sig = key.sign(&signed_bytes(domain, &payload));
Ok(SignedEnvelope {
payload,
public_key: hex_encode(&key.verifying_key().to_bytes()),
signature: hex_encode(&sig.to_bytes()),
})
}
pub fn envelope_key(env: &SignedEnvelope) -> Result<[u8; 32], LicenceError> {
if env.payload.len() > MAX_PAYLOAD_BYTES {
return Err(LicenceError::TooLarge);
}
hex_decode_exact::<32>(&env.public_key)
.ok_or_else(|| LicenceError::Malformed("public key".into()))
}
pub fn verify_envelope(
domain: &str,
env: &SignedEnvelope,
pk: &[u8; 32],
) -> Result<(), LicenceError> {
let sig = hex_decode_exact::<64>(&env.signature)
.ok_or_else(|| LicenceError::Malformed("signature".into()))?;
let vk = VerifyingKey::from_bytes(pk).map_err(|_| LicenceError::BadSignature)?;
vk.verify_strict(
&signed_bytes(domain, &env.payload),
&Signature::from_bytes(&sig),
)
.map_err(|_| LicenceError::BadSignature)
}
pub fn parse_canonical<T: Serialize + DeserializeOwned>(
payload: &str,
) -> Result<T, LicenceError> {
let v: T =
serde_json::from_str(payload).map_err(|e| LicenceError::Malformed(e.to_string()))?;
let again = serde_json::to_string(&v).map_err(|e| LicenceError::Malformed(e.to_string()))?;
if again != payload {
return Err(LicenceError::Malformed("payload is not canonical".into()));
}
Ok(v)
}
pub fn sha256_hex(bytes: &[u8]) -> String {
hex_encode(&Sha256::digest(bytes))
}
pub fn key_id(pk: &[u8; 32]) -> String {
format!("ed25519:{}", &sha256_hex(pk)[..16])
}
pub fn valid_token(s: &str) -> bool {
!s.is_empty()
&& s.len() <= 128
&& s.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-' | '+' | '@' | '/'))
}
pub fn valid_hex32(s: &str) -> bool {
hex_decode_exact::<32>(s).is_some()
}