coreason-urn-authority 0.45.1

Epistemic Ledger & OCI Trust Anchor for CoReason URNs.
Documentation
// Copyright (c) 2026 CoReason, Inc.
// All rights reserved.

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, // Fail-closed
    };

    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; // Fail-closed
    }

    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;
    }

    // Decode URL-safe base64 payload (parts[1])
    let payload_b64 = parts[1];
    let payload_bytes = match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(payload_b64) {
        Ok(b) => b,
        Err(_) => {
            // Try standard engine just in case
            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,
    };

    // Validate exp
    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;
    }

    // Validate tenant claim matches
    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> {
    // Step 0: Validate URN format using regex
    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
        ));
    }

    // Step 1: Load ledger and resolve 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
        )
    })?;

    // Step 2: License validation via JWT
    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
            ));
        }
    }

    // Check if the signer's DID is authorized
    if !is_authorized_tenant_did(&entry.authorized_signer_did) {
        return Err(format!(
            "Unauthorized Signer: The DID '{}' is not authorized.",
            entry.authorized_signer_did
        ));
    }

    // Step 3: Extract public key from DID
    let public_key_pem = crypto::extract_public_key_pem_from_did(&entry.authorized_signer_did)?;

    // Step 4: Verify OCI signature via Cosign
    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(),
    })
}