use base64::Engine;
use serde::Serialize;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::crypto;
use crate::ledger::{self, LedgerEntry};
#[derive(Serialize, Debug)]
pub struct VerifiedCapabilityReceipt {
pub urn: String,
pub oci_uri: String,
pub authorized_signer_did: String,
pub license_tier: String,
}
pub fn is_authorized_tenant_did(did: &str) -> bool {
let auth_dids_env = match std::env::var("COREASON_AUTHORIZED_DIDS") {
Ok(val) => val,
Err(_) => return false, };
let authorized_dids: Vec<&str> = auth_dids_env
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect();
if authorized_dids.is_empty() {
return false; }
if authorized_dids.contains(&"*") {
return true;
}
authorized_dids.contains(&did)
}
fn verify_tenant_license_jwt(tenant_cid: &str) -> bool {
let mut jwt_token = std::env::var(format!("COREASON_LICENSE_JWT_{}", tenant_cid))
.or_else(|_| std::env::var("COREASON_LICENSE_JWT"))
.ok();
if jwt_token.is_none() {
let license_path_env = std::env::var("COREASON_LICENSE_PATH")
.unwrap_or_else(|_| format!("/var/run/coreason/licenses/{}.jwt", tenant_cid));
let path = Path::new(&license_path_env);
if path.exists() {
jwt_token = fs::read_to_string(path).ok().map(|s| s.trim().to_string());
}
}
let token = match jwt_token {
Some(t) => t,
None => return false,
};
let parts: Vec<&str> = token.split('.').collect();
if parts.len() < 2 {
return false;
}
let payload_b64 = parts[1];
let payload_bytes = match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(payload_b64) {
Ok(b) => b,
Err(_) => {
match base64::engine::general_purpose::STANDARD.decode(payload_b64) {
Ok(b) => b,
Err(_) => return false,
}
}
};
let claims: serde_json::Value = match serde_json::from_slice(&payload_bytes) {
Ok(c) => c,
Err(_) => return false,
};
if let Some(exp_val) = claims.get("exp") {
let exp = match exp_val.as_i64() {
Some(e) => e,
None => return false,
};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
if exp <= now {
return false;
}
} else {
return false;
}
let claim_tenant = claims
.get("tenant_cid")
.or_else(|| claims.get("sub"))
.and_then(|v| v.as_str())
.unwrap_or("");
if !claim_tenant.is_empty() && claim_tenant != tenant_cid {
return false;
}
true
}
pub fn resolve_capability(
urn: &str,
ledger_path: Option<PathBuf>,
) -> Result<VerifiedCapabilityReceipt, String> {
let re =
regex::Regex::new(r"^urn:coreason:[a-z][a-z0-9_]*:[a-zA-Z0-9][a-zA-Z0-9_.-]*(?::v\d+)?$")
.unwrap();
if !re.is_match(urn) {
return Err(format!(
"Invalid URN format: '{}'. Expected pattern: urn:coreason:<namespace>:<name>[:v<version>]",
urn
));
}
let path = ledger_path.unwrap_or_else(ledger::default_ledger_path);
let ledger = ledger::load_ledger(Some(path))?;
let entry: &LedgerEntry = ledger.iter().find(|e| e.urn == urn).ok_or_else(|| {
let available: Vec<String> = ledger.iter().map(|e| e.urn.clone()).collect();
format!(
"URN '{}' not found in OCI Ledger. Available URNs: {:?}",
urn, available
)
})?;
let ast_guillotine_active = std::env::var("COREASON_AST_GUILLOTINE")
.map(|v| v.to_lowercase() == "true")
.unwrap_or(false);
if ast_guillotine_active {
let license_valid = verify_tenant_license_jwt(&entry.tenant_cid);
if !license_valid {
return Err(format!(
"License verification failed: AST Guillotine is active and no valid license found for tenant '{}'.",
entry.tenant_cid
));
}
}
if !is_authorized_tenant_did(&entry.authorized_signer_did) {
return Err(format!(
"Unauthorized Signer: The DID '{}' is not authorized.",
entry.authorized_signer_did
));
}
let public_key_pem = crypto::extract_public_key_pem_from_did(&entry.authorized_signer_did)?;
crypto::verify_oci_signature(&entry.oci_uri, &public_key_pem)?;
Ok(VerifiedCapabilityReceipt {
urn: entry.urn.clone(),
oci_uri: entry.oci_uri.clone(),
authorized_signer_did: entry.authorized_signer_did.clone(),
license_tier: entry.license_tier.clone(),
})
}