use alloc::string::{String, ToString};
use alloc::vec::Vec;
use chio_core_types::capability::CapabilityToken;
use chio_core_types::crypto::PublicKey;
use crate::capability_verify::{verify_capability, CapabilityError, VerifiedCapability};
use crate::clock::Clock;
use crate::guard::{Guard, GuardContext, PortableToolCallRequest};
use crate::normalized::{NormalizationError, NormalizedEvaluationVerdict};
use crate::scope::{resolve_matching_grants, MatchedGrant};
use crate::Verdict;
pub struct EvaluateInput<'a> {
pub request: &'a PortableToolCallRequest,
pub capability: &'a CapabilityToken,
pub trusted_issuers: &'a [PublicKey],
pub clock: &'a dyn Clock,
pub guards: &'a [&'a dyn Guard],
pub session_filesystem_roots: Option<&'a [String]>,
}
#[derive(Debug, Clone)]
pub struct EvaluationVerdict {
pub verdict: Verdict,
pub reason: Option<String>,
pub matched_grant_index: Option<usize>,
pub verified: Option<VerifiedCapability>,
}
impl EvaluationVerdict {
#[must_use]
pub fn is_allow(&self) -> bool {
self.verdict == Verdict::Allow
}
#[must_use]
pub fn is_deny(&self) -> bool {
self.verdict == Verdict::Deny
}
pub fn normalized(
&self,
request: &PortableToolCallRequest,
) -> Result<NormalizedEvaluationVerdict, NormalizationError> {
NormalizedEvaluationVerdict::try_from_evaluation(request, self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KernelCoreError {
InvalidCapability(CapabilityError),
SubjectMismatch { expected: String, actual: String },
OutOfScope { tool: String, server: String },
ConstraintError { reason: String },
GuardError { guard: String, reason: String },
GuardDenied { guard: String },
}
impl KernelCoreError {
#[must_use]
pub fn deny_reason(&self) -> String {
match self {
KernelCoreError::InvalidCapability(error) => match error {
CapabilityError::UntrustedIssuer => {
"capability issuer is not a trusted CA".to_string()
}
CapabilityError::InvalidSignature => "capability signature is invalid".to_string(),
CapabilityError::NotYetValid => "capability not yet valid".to_string(),
CapabilityError::Expired => "capability has expired".to_string(),
CapabilityError::Internal(msg) => {
let mut out = String::from("capability verification failed: ");
out.push_str(msg);
out
}
},
KernelCoreError::SubjectMismatch { expected, actual } => {
let mut out = String::from("request agent ");
out.push_str(actual);
out.push_str(" does not match capability subject ");
out.push_str(expected);
out
}
KernelCoreError::OutOfScope { tool, server } => {
let mut out = String::from("requested tool ");
out.push_str(tool);
out.push_str(" on server ");
out.push_str(server);
out.push_str(" is not in capability scope");
out
}
KernelCoreError::ConstraintError { reason } => {
let mut out = String::from("constraint evaluation failed: ");
out.push_str(reason);
out
}
KernelCoreError::GuardError { guard, reason } => {
let mut out = String::from("guard \"");
out.push_str(guard);
out.push_str("\" error (fail-closed): ");
out.push_str(reason);
out
}
KernelCoreError::GuardDenied { guard } => {
let mut out = String::from("guard \"");
out.push_str(guard);
out.push_str("\" denied the request");
out
}
}
}
}
pub fn evaluate(input: EvaluateInput<'_>) -> EvaluationVerdict {
let verified = match verify_capability(input.capability, input.trusted_issuers, input.clock) {
Ok(verified) => verified,
Err(error) => {
let core_err = KernelCoreError::InvalidCapability(error);
return deny(core_err, None, None);
}
};
if verified.subject_hex != input.request.agent_id {
let core_err = KernelCoreError::SubjectMismatch {
expected: verified.subject_hex.clone(),
actual: input.request.agent_id.clone(),
};
return deny(core_err, None, Some(verified));
}
let matches: Vec<MatchedGrant<'_>> = match resolve_matching_grants(
&verified.scope,
&input.request.tool_name,
&input.request.server_id,
&input.request.arguments,
) {
Ok(matches) if matches.is_empty() => {
let core_err = KernelCoreError::OutOfScope {
tool: input.request.tool_name.clone(),
server: input.request.server_id.clone(),
};
return deny(core_err, None, Some(verified));
}
Ok(matches) => matches,
Err(crate::ScopeMatchError::OutOfScope) => {
let core_err = KernelCoreError::OutOfScope {
tool: input.request.tool_name.clone(),
server: input.request.server_id.clone(),
};
return deny(core_err, None, Some(verified));
}
Err(crate::ScopeMatchError::ConstraintError(reason)) => {
return deny(
KernelCoreError::ConstraintError { reason },
None,
Some(verified),
);
}
};
let matched_grant_index = matches[0].index;
let ctx = GuardContext {
request: input.request,
scope: &verified.scope,
agent_id: &input.request.agent_id,
server_id: &input.request.server_id,
session_filesystem_roots: input.session_filesystem_roots,
matched_grant_index: Some(matched_grant_index),
};
for guard in input.guards {
match guard.evaluate(&ctx) {
Ok(Verdict::Allow) => {}
Ok(Verdict::Deny) | Ok(Verdict::PendingApproval) => {
let core_err = KernelCoreError::GuardDenied {
guard: guard.name().to_string(),
};
return deny(core_err, Some(matched_grant_index), Some(verified));
}
Err(error) => {
let core_err = KernelCoreError::GuardError {
guard: guard.name().to_string(),
reason: error.deny_reason(),
};
return deny(core_err, Some(matched_grant_index), Some(verified));
}
}
}
EvaluationVerdict {
verdict: Verdict::Allow,
reason: None,
matched_grant_index: Some(matched_grant_index),
verified: Some(verified),
}
}
fn deny(
error: KernelCoreError,
matched_grant_index: Option<usize>,
verified: Option<VerifiedCapability>,
) -> EvaluationVerdict {
EvaluationVerdict {
verdict: Verdict::Deny,
reason: Some(error.deny_reason()),
matched_grant_index,
verified,
}
}