clone

one developer profile for links and private tcp services, reached peer to peer over iroh with no open ports

main
devsite / crates / devsite-proto / src / access_plan.rs
access_plan.rs 4.2K
1//! Server-signed approval tokens for endpoint-bound service grants.23use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};4use serde::{Deserialize, Serialize};56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]7pub struct ServiceGrantPlanClaims {8 pub schema_version: u32,9 pub issuer_credential_id: String,10 pub request_id: String,11 pub service: String,12 pub resource_id: String,13 pub requester_endpoint_id: String,14 pub request_expires_at: u64,15 pub grant_expires_at: u64,16}1718#[derive(Debug, Clone, Serialize, Deserialize)]19pub struct SignedServiceGrantPlan {20 claims: Vec<u8>,21 signature: Vec<u8>,22}2324#[derive(Debug, thiserror::Error, PartialEq, Eq)]25pub enum ServiceGrantPlanError {26 #[error("service grant plan signature does not verify")]27 BadSignature,28 #[error("service grant plan is malformed")]29 Malformed,30}3132impl SignedServiceGrantPlan {33 pub fn sign(34 claims: &ServiceGrantPlanClaims,35 key: &SigningKey,36 ) -> Result<Self, ServiceGrantPlanError> {37 let mut bytes = b"dev.site approved service grant plan v1\0".to_vec();38 bytes.extend(postcard::to_allocvec(claims).map_err(|_| ServiceGrantPlanError::Malformed)?);39 Ok(Self {40 signature: key.sign(&bytes).to_bytes().to_vec(),41 claims: bytes,42 })43 }4445 pub fn verify(46 &self,47 key: &VerifyingKey,48 ) -> Result<ServiceGrantPlanClaims, ServiceGrantPlanError> {49 let signature: [u8; 64] = self50 .signature51 .as_slice()52 .try_into()53 .map_err(|_| ServiceGrantPlanError::Malformed)?;54 key.verify(&self.claims, &Signature::from_bytes(&signature))55 .map_err(|_| ServiceGrantPlanError::BadSignature)?;56 let payload = self57 .claims58 .strip_prefix(b"dev.site approved service grant plan v1\0")59 .ok_or(ServiceGrantPlanError::Malformed)?;60 postcard::from_bytes(payload).map_err(|_| ServiceGrantPlanError::Malformed)61 }6263 pub fn to_token(&self) -> String {64 let bytes = postcard::to_allocvec(self).expect("a signed service grant plan serializes");65 format!("dsp_{}", data_encoding::BASE64URL_NOPAD.encode(&bytes))66 }6768 pub fn from_token(token: &str) -> Result<Self, ServiceGrantPlanError> {69 let encoded = token70 .strip_prefix("dsp_")71 .ok_or(ServiceGrantPlanError::Malformed)?;72 let bytes = data_encoding::BASE64URL_NOPAD73 .decode(encoded.as_bytes())74 .map_err(|_| ServiceGrantPlanError::Malformed)?;75 postcard::from_bytes(&bytes).map_err(|_| ServiceGrantPlanError::Malformed)76 }7778 /// Decode untrusted claims for clients that need to reconstruct the exact79 /// request. The server must still verify the signature before authorizing it.80 pub fn unverified_claims(&self) -> Result<ServiceGrantPlanClaims, ServiceGrantPlanError> {81 let payload = self82 .claims83 .strip_prefix(b"dev.site approved service grant plan v1\0")84 .ok_or(ServiceGrantPlanError::Malformed)?;85 postcard::from_bytes(payload).map_err(|_| ServiceGrantPlanError::Malformed)86 }87}8889#[cfg(test)]90mod tests {91 use super::*;9293 fn claims() -> ServiceGrantPlanClaims {94 ServiceGrantPlanClaims {95 schema_version: 1,96 issuer_credential_id: "machine_one".into(),97 request_id: "agr_one".into(),98 service: "postgres".into(),99 resource_id: "res_one".into(),100 requester_endpoint_id: "endpoint".into(),101 request_expires_at: 200,102 grant_expires_at: 300,103 }104 }105106 #[test]107 fn signed_plan_token_round_trips_and_rejects_another_issuer() {108 let issuer = SigningKey::from_bytes(&[1; 32]);109 let other = SigningKey::from_bytes(&[2; 32]);110 let token = SignedServiceGrantPlan::sign(&claims(), &issuer)111 .unwrap()112 .to_token();113 let restored = SignedServiceGrantPlan::from_token(&token).unwrap();114 assert_eq!(restored.verify(&issuer.verifying_key()).unwrap(), claims());115 assert_eq!(116 restored.verify(&other.verifying_key()),117 Err(ServiceGrantPlanError::BadSignature)118 );119 }120}