Skip to main content

kanoniv_agent_auth/
delegation.rs

1//! Cryptographic delegation with attenuated capabilities.
2//!
3//! Implements Macaroon-style delegation where an agent can grant another agent
4//! a subset of its authority, with constraints (caveats). Delegations chain:
5//! Agent A delegates to B, who delegates to C, each adding restrictions.
6//! Verification walks the chain back to the root, checking every signature
7//! and every caveat. No server calls required.
8//!
9//! # Concepts
10//!
11//! - **Delegation**: "I grant you power X with restrictions Y" (reusable)
12//! - **Invocation**: "I'm using power X, here's my proof" (single-use action)
13//! - **Caveat**: A constraint on what the delegated power can do
14//! - **Chain**: A linked list of delegations from invoker back to root authority
15
16use serde::{Deserialize, Serialize};
17
18use crate::identity::{AgentIdentity, AgentKeyPair};
19use crate::signing::SignedMessage;
20use crate::CryptoError;
21
22/// A constraint on delegated authority.
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24#[serde(tag = "type", content = "value")]
25pub enum Caveat {
26    /// Restrict to specific actions (e.g. ["resolve", "search"]).
27    #[serde(rename = "action_scope")]
28    ActionScope(Vec<String>),
29
30    /// Delegation expires at this RFC 3339 timestamp.
31    #[serde(rename = "expires_at")]
32    ExpiresAt(String),
33
34    /// Maximum cost ceiling for the delegated operation.
35    #[serde(rename = "max_cost")]
36    MaxCost(f64),
37
38    /// Resource pattern the delegation applies to (glob-style).
39    /// E.g. "entity:customer:*", "source:crm:*"
40    #[serde(rename = "resource")]
41    Resource(String),
42
43    /// Restrict to a specific context (e.g. task_id, session_id).
44    #[serde(rename = "context")]
45    Context { key: String, value: String },
46
47    /// Arbitrary user-defined caveat.
48    #[serde(rename = "custom")]
49    Custom {
50        key: String,
51        value: serde_json::Value,
52    },
53}
54
55/// A cryptographic delegation of authority from one agent to another.
56///
57/// Delegations form a chain: each delegation optionally references a parent
58/// delegation that granted the issuer their authority. The chain terminates
59/// at the root authority (who needs no parent delegation).
60/// Maximum delegation chain depth to prevent DoS via deeply nested chains.
61pub const MAX_CHAIN_DEPTH: usize = 32;
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Delegation {
65    /// DID of the agent granting authority
66    pub issuer_did: String,
67    /// DID of the agent receiving authority
68    pub delegate_did: String,
69    /// Issuer's public key bytes (for self-verifying chains without key resolver)
70    pub issuer_public_key: Vec<u8>,
71    /// Constraints on the delegated authority
72    pub caveats: Vec<Caveat>,
73    /// Parent delegation proving the issuer's authority (None for root)
74    pub parent_proof: Option<Box<Delegation>>,
75    /// Cryptographic proof (signed by issuer)
76    pub proof: SignedMessage,
77}
78
79impl Delegation {
80    /// Create and sign a new root delegation (no parent).
81    ///
82    /// The issuer is the root authority and does not need a parent delegation.
83    pub fn create_root(
84        issuer_keypair: &AgentKeyPair,
85        delegate_did: &str,
86        caveats: Vec<Caveat>,
87    ) -> Result<Self, CryptoError> {
88        let issuer_identity = issuer_keypair.identity();
89        Self::create_inner(
90            issuer_keypair,
91            &issuer_identity.did,
92            delegate_did,
93            caveats,
94            None,
95        )
96    }
97
98    /// Create and sign a delegated delegation (with parent chain).
99    ///
100    /// The issuer must have been granted authority via the parent delegation.
101    /// Additional caveats can only narrow the authority, never widen it.
102    pub fn delegate(
103        issuer_keypair: &AgentKeyPair,
104        delegate_did: &str,
105        additional_caveats: Vec<Caveat>,
106        parent: Delegation,
107    ) -> Result<Self, CryptoError> {
108        let issuer_identity = issuer_keypair.identity();
109
110        // Issuer must be the delegate of the parent delegation
111        if parent.delegate_did != issuer_identity.did {
112            return Err(CryptoError::DelegationChainBroken(
113                "issuer is not the delegate of parent delegation".into(),
114            ));
115        }
116
117        // Merge parent caveats with additional caveats (union of restrictions)
118        let mut all_caveats = parent.caveats.clone();
119        all_caveats.extend(additional_caveats);
120
121        Self::create_inner(
122            issuer_keypair,
123            &issuer_identity.did,
124            delegate_did,
125            all_caveats,
126            Some(Box::new(parent)),
127        )
128    }
129
130    fn create_inner(
131        issuer_keypair: &AgentKeyPair,
132        issuer_did: &str,
133        delegate_did: &str,
134        caveats: Vec<Caveat>,
135        parent: Option<Box<Delegation>>,
136    ) -> Result<Self, CryptoError> {
137        // Check chain depth limit
138        if let Some(ref p) = parent {
139            if p.depth() >= MAX_CHAIN_DEPTH {
140                return Err(CryptoError::DelegationChainBroken(format!(
141                    "chain depth exceeds maximum of {}",
142                    MAX_CHAIN_DEPTH
143                )));
144            }
145        }
146
147        let issuer_identity = issuer_keypair.identity();
148        let parent_hash = parent.as_ref().map(|p| p.proof.content_hash());
149
150        let payload = serde_json::json!({
151            "issuer_did": issuer_did,
152            "delegate_did": delegate_did,
153            "caveats": caveats,
154            "parent_hash": parent_hash,
155        });
156
157        let proof = SignedMessage::sign(issuer_keypair, payload)?;
158
159        Ok(Self {
160            issuer_did: issuer_did.to_string(),
161            delegate_did: delegate_did.to_string(),
162            issuer_public_key: issuer_identity.public_key_bytes.clone(),
163            caveats,
164            parent_proof: parent,
165            proof,
166        })
167    }
168
169    /// Get the chain depth (0 for root, 1 for first delegation, etc.)
170    pub fn depth(&self) -> usize {
171        let mut depth = 0;
172        let mut current = self;
173        while let Some(ref parent) = current.parent_proof {
174            depth += 1;
175            current = parent;
176        }
177        depth
178    }
179}
180
181/// An invocation: an agent exercising delegated authority.
182///
183/// Combines the action being performed with the delegation chain
184/// that proves the agent has authority to perform it.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct Invocation {
187    /// DID of the agent performing the action
188    pub invoker_did: String,
189    /// The action being performed
190    pub action: String,
191    /// Action arguments / context
192    pub args: serde_json::Value,
193    /// The delegation chain proving authority
194    pub delegation: Delegation,
195    /// Cryptographic proof (signed by invoker)
196    pub proof: SignedMessage,
197}
198
199impl Invocation {
200    /// Create and sign an invocation.
201    ///
202    /// The invoker must be the delegate of the delegation.
203    pub fn create(
204        invoker_keypair: &AgentKeyPair,
205        action: &str,
206        args: serde_json::Value,
207        delegation: Delegation,
208    ) -> Result<Self, CryptoError> {
209        let invoker_identity = invoker_keypair.identity();
210
211        if delegation.delegate_did != invoker_identity.did {
212            return Err(CryptoError::DelegationChainBroken(
213                "invoker is not the delegate of the delegation".into(),
214            ));
215        }
216
217        let payload = serde_json::json!({
218            "invoker_did": invoker_identity.did,
219            "action": action,
220            "args": args,
221            "delegation_hash": delegation.proof.content_hash(),
222        });
223
224        let proof = SignedMessage::sign(invoker_keypair, payload)?;
225
226        Ok(Self {
227            invoker_did: invoker_identity.did,
228            action: action.to_string(),
229            args,
230            delegation,
231            proof,
232        })
233    }
234}
235
236/// Result of a successful verification, containing the full authority chain.
237#[derive(Debug)]
238pub struct VerificationResult {
239    /// The invoker's DID
240    pub invoker_did: String,
241    /// The root authority's DID
242    pub root_did: String,
243    /// The chain of DIDs from invoker back to root
244    pub chain: Vec<String>,
245    /// The chain depth
246    pub depth: usize,
247}
248
249/// Verify an invocation's entire authority chain (no revocation check).
250///
251/// For revocation support, use `verify_invocation_with_revocation` instead.
252pub fn verify_invocation(
253    invocation: &Invocation,
254    invoker_identity: &AgentIdentity,
255    root_identity: &AgentIdentity,
256) -> Result<VerificationResult, CryptoError> {
257    verify_invocation_with_revocation(invocation, invoker_identity, root_identity, |_| false)
258}
259
260/// Verify an invocation's entire authority chain with optional revocation check.
261///
262/// Checks:
263/// 1. Invocation signature is valid for the invoker
264/// 2. Invoker is the delegate of the delegation
265/// 3. **Every** delegation signature is verified (using embedded public keys)
266/// 4. Each delegation's issuer is the delegate of its parent
267/// 5. Embedded public keys match their DIDs
268/// 6. No delegation in the chain has been revoked
269/// 7. The chain terminates at the expected root authority
270/// 8. All caveats are satisfied for the invoked action
271///
272/// The `is_revoked` callback receives a delegation's content hash and returns
273/// `true` if that delegation has been revoked. Use `|_| false` to skip
274/// revocation checks, or provide a lookup against your revocation service.
275pub fn verify_invocation_with_revocation(
276    invocation: &Invocation,
277    invoker_identity: &AgentIdentity,
278    root_identity: &AgentIdentity,
279    is_revoked: impl Fn(&str) -> bool,
280) -> Result<VerificationResult, CryptoError> {
281    // 1. Verify invocation signature
282    invocation.proof.verify(invoker_identity)?;
283
284    // 2. Verify invoker matches delegation delegate
285    if invocation.invoker_did != invocation.delegation.delegate_did {
286        return Err(CryptoError::DelegationChainBroken(
287            "invoker is not the delegate of the delegation".into(),
288        ));
289    }
290
291    // 3. Walk and verify the full delegation chain
292    let mut chain = vec![invocation.invoker_did.clone()];
293    let mut current = &invocation.delegation;
294    let mut all_caveats: Vec<Caveat> = Vec::new();
295    let mut steps = 0usize;
296
297    loop {
298        steps += 1;
299        if steps > MAX_CHAIN_DEPTH {
300            return Err(CryptoError::DelegationChainBroken(format!(
301                "chain depth exceeds maximum of {}",
302                MAX_CHAIN_DEPTH
303            )));
304        }
305
306        chain.push(current.issuer_did.clone());
307
308        // Reconstruct issuer identity from embedded public key
309        let issuer_identity =
310            AgentIdentity::from_bytes(&current.issuer_public_key).map_err(|_| {
311                CryptoError::DelegationChainBroken(format!(
312                    "invalid embedded public key for '{}'",
313                    current.issuer_did
314                ))
315            })?;
316
317        // Verify the embedded public key matches the claimed DID
318        if issuer_identity.did != current.issuer_did {
319            return Err(CryptoError::DelegationChainBroken(format!(
320                "embedded public key produces DID '{}' but delegation claims '{}'",
321                issuer_identity.did, current.issuer_did
322            )));
323        }
324
325        // Verify this delegation's signature using the embedded public key
326        current.proof.verify(&issuer_identity)?;
327
328        // Check if this delegation has been revoked
329        let delegation_hash = current.proof.content_hash();
330        if is_revoked(&delegation_hash) {
331            return Err(CryptoError::DelegationRevoked(delegation_hash));
332        }
333
334        // Extract caveats from the SIGNED PAYLOAD (not outer fields) to prevent tampering
335        if let Some(signed_caveats) = current.proof.payload.get("caveats") {
336            if let Ok(caveats) = serde_json::from_value::<Vec<Caveat>>(signed_caveats.clone()) {
337                all_caveats.extend(caveats);
338            }
339        }
340
341        // Check chain linkage
342        if current.issuer_did == root_identity.did {
343            // Reached root - verify it matches the expected root identity
344            if issuer_identity.public_key_bytes != root_identity.public_key_bytes {
345                return Err(CryptoError::DelegationChainBroken(
346                    "root public key mismatch".into(),
347                ));
348            }
349            break;
350        }
351
352        // Not root - must have a parent proof
353        match &current.parent_proof {
354            Some(parent) => {
355                if parent.delegate_did != current.issuer_did {
356                    return Err(CryptoError::DelegationChainBroken(format!(
357                        "delegation issuer '{}' is not the delegate of parent delegation '{}'",
358                        current.issuer_did, parent.delegate_did
359                    )));
360                }
361                current = parent;
362            }
363            None => {
364                return Err(CryptoError::DelegationChainBroken(format!(
365                    "chain terminates at '{}', expected root '{}'",
366                    current.issuer_did, root_identity.did
367                )));
368            }
369        }
370    }
371
372    // 4. Check all caveats (from signed payloads) against the invocation
373    let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
374    for caveat in &all_caveats {
375        check_caveat(caveat, &invocation.action, &invocation.args, &now)?;
376    }
377
378    let depth = chain.len() - 1;
379    Ok(VerificationResult {
380        invoker_did: invocation.invoker_did.clone(),
381        root_did: root_identity.did.clone(),
382        chain,
383        depth,
384    })
385}
386
387/// Verify a delegation chain without an invocation (no revocation check).
388pub fn verify_delegation_chain(
389    delegation: &Delegation,
390    root_identity: &AgentIdentity,
391) -> Result<Vec<String>, CryptoError> {
392    verify_delegation_chain_with_revocation(delegation, root_identity, |_| false)
393}
394
395/// Verify a delegation chain with optional revocation check.
396///
397/// Verifies every signature in the chain using embedded public keys.
398pub fn verify_delegation_chain_with_revocation(
399    delegation: &Delegation,
400    root_identity: &AgentIdentity,
401    is_revoked: impl Fn(&str) -> bool,
402) -> Result<Vec<String>, CryptoError> {
403    let mut chain = Vec::new();
404    let mut current = delegation;
405    let mut steps = 0usize;
406
407    loop {
408        steps += 1;
409        if steps > MAX_CHAIN_DEPTH {
410            return Err(CryptoError::DelegationChainBroken(format!(
411                "chain depth exceeds maximum of {}",
412                MAX_CHAIN_DEPTH
413            )));
414        }
415
416        chain.push(current.delegate_did.clone());
417        chain.push(current.issuer_did.clone());
418
419        // Verify signature using embedded public key
420        let issuer_identity =
421            AgentIdentity::from_bytes(&current.issuer_public_key).map_err(|_| {
422                CryptoError::DelegationChainBroken(format!(
423                    "invalid embedded public key for '{}'",
424                    current.issuer_did
425                ))
426            })?;
427
428        if issuer_identity.did != current.issuer_did {
429            return Err(CryptoError::DelegationChainBroken(format!(
430                "embedded public key produces DID '{}' but delegation claims '{}'",
431                issuer_identity.did, current.issuer_did
432            )));
433        }
434
435        current.proof.verify(&issuer_identity)?;
436
437        let delegation_hash = current.proof.content_hash();
438        if is_revoked(&delegation_hash) {
439            return Err(CryptoError::DelegationRevoked(delegation_hash));
440        }
441
442        if current.issuer_did == root_identity.did {
443            if issuer_identity.public_key_bytes != root_identity.public_key_bytes {
444                return Err(CryptoError::DelegationChainBroken(
445                    "root public key mismatch".into(),
446                ));
447            }
448            break;
449        }
450
451        match &current.parent_proof {
452            Some(parent) => {
453                if parent.delegate_did != current.issuer_did {
454                    return Err(CryptoError::DelegationChainBroken(
455                        "chain linkage broken: issuer not delegate of parent".into(),
456                    ));
457                }
458                current = parent;
459            }
460            None => {
461                return Err(CryptoError::DelegationChainBroken(format!(
462                    "chain terminates at '{}', expected root '{}'",
463                    current.issuer_did, root_identity.did
464                )));
465            }
466        }
467    }
468
469    chain.dedup();
470    Ok(chain)
471}
472
473fn check_caveat(
474    caveat: &Caveat,
475    action: &str,
476    args: &serde_json::Value,
477    now: &str,
478) -> Result<(), CryptoError> {
479    match caveat {
480        Caveat::ActionScope(allowed) => {
481            if !allowed.iter().any(|a| a == action) {
482                return Err(CryptoError::CaveatViolation(format!(
483                    "action '{}' not in allowed scope {:?}",
484                    action, allowed
485                )));
486            }
487        }
488        Caveat::ExpiresAt(expiry) => {
489            if now > expiry.as_str() {
490                return Err(CryptoError::CaveatViolation(format!(
491                    "delegation expired at {}",
492                    expiry
493                )));
494            }
495        }
496        Caveat::MaxCost(max) => match args.get("cost").and_then(|v| v.as_f64()) {
497            Some(cost) if cost > *max => {
498                return Err(CryptoError::CaveatViolation(format!(
499                    "cost {} exceeds max {}",
500                    cost, max
501                )));
502            }
503            None => {
504                return Err(CryptoError::CaveatViolation(
505                    "max_cost caveat requires 'cost' field in args".into(),
506                ));
507            }
508            _ => {}
509        },
510        Caveat::Resource(pattern) => match args.get("resource").and_then(|v| v.as_str()) {
511            Some(resource) if !matches_glob(pattern, resource) => {
512                return Err(CryptoError::CaveatViolation(format!(
513                    "resource '{}' does not match pattern '{}'",
514                    resource, pattern
515                )));
516            }
517            None => {
518                return Err(CryptoError::CaveatViolation(
519                    "resource caveat requires 'resource' field in args".into(),
520                ));
521            }
522            _ => {}
523        },
524        Caveat::Context { key, value } => {
525            let actual = args.get(key).and_then(|v| v.as_str());
526            if actual != Some(value.as_str()) {
527                return Err(CryptoError::CaveatViolation(format!(
528                    "context '{}' expected '{}', got '{}'",
529                    key,
530                    value,
531                    actual.unwrap_or("<missing>")
532                )));
533            }
534        }
535        Caveat::Custom { key, value } => {
536            let actual = args.get(key);
537            if actual != Some(value) {
538                return Err(CryptoError::CaveatViolation(format!(
539                    "custom caveat '{}' not satisfied",
540                    key
541                )));
542            }
543        }
544    }
545    Ok(())
546}
547
548/// Simple glob matching: supports trailing * only.
549/// E.g. "entity:customer:*" matches "entity:customer:123"
550fn matches_glob(pattern: &str, value: &str) -> bool {
551    if let Some(prefix) = pattern.strip_suffix('*') {
552        value.starts_with(prefix)
553    } else {
554        pattern == value
555    }
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561
562    fn keypair() -> AgentKeyPair {
563        AgentKeyPair::generate()
564    }
565
566    // --- Delegation creation ---
567
568    #[test]
569    fn test_root_delegation() {
570        let root = keypair();
571        let agent_b = keypair();
572
573        let delegation = Delegation::create_root(
574            &root,
575            &agent_b.identity().did,
576            vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
577        )
578        .unwrap();
579
580        assert_eq!(delegation.issuer_did, root.identity().did);
581        assert_eq!(delegation.delegate_did, agent_b.identity().did);
582        assert_eq!(delegation.depth(), 0);
583        assert!(delegation.parent_proof.is_none());
584    }
585
586    #[test]
587    fn test_chained_delegation() {
588        let root = keypair();
589        let agent_b = keypair();
590        let agent_c = keypair();
591
592        let d1 = Delegation::create_root(
593            &root,
594            &agent_b.identity().did,
595            vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
596        )
597        .unwrap();
598
599        let d2 = Delegation::delegate(
600            &agent_b,
601            &agent_c.identity().did,
602            vec![], // no additional restrictions
603            d1,
604        )
605        .unwrap();
606
607        assert_eq!(d2.issuer_did, agent_b.identity().did);
608        assert_eq!(d2.delegate_did, agent_c.identity().did);
609        assert_eq!(d2.depth(), 1);
610        assert!(d2.parent_proof.is_some());
611    }
612
613    #[test]
614    fn test_delegate_must_be_parent_delegate() {
615        let root = keypair();
616        let agent_b = keypair();
617        let agent_c = keypair();
618        let unrelated = keypair();
619
620        let d1 = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
621
622        // Agent C tries to delegate using Agent B's delegation (but C is not B)
623        let result = Delegation::delegate(&unrelated, &agent_c.identity().did, vec![], d1);
624        assert!(result.is_err());
625    }
626
627    // --- Invocation ---
628
629    #[test]
630    fn test_invocation_basic() {
631        let root = keypair();
632        let agent_b = keypair();
633
634        let delegation = Delegation::create_root(
635            &root,
636            &agent_b.identity().did,
637            vec![Caveat::ActionScope(vec!["resolve".into()])],
638        )
639        .unwrap();
640
641        let invocation = Invocation::create(
642            &agent_b,
643            "resolve",
644            serde_json::json!({"entity_id": "123"}),
645            delegation,
646        )
647        .unwrap();
648
649        assert_eq!(invocation.invoker_did, agent_b.identity().did);
650        assert_eq!(invocation.action, "resolve");
651    }
652
653    #[test]
654    fn test_invocation_must_be_delegation_delegate() {
655        let root = keypair();
656        let agent_b = keypair();
657        let agent_c = keypair();
658
659        let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
660
661        // Agent C tries to invoke using Agent B's delegation
662        let result = Invocation::create(&agent_c, "resolve", serde_json::json!({}), delegation);
663        assert!(result.is_err());
664    }
665
666    // --- Verification ---
667
668    #[test]
669    fn test_verify_root_invocation() {
670        let root = keypair();
671        let agent_b = keypair();
672
673        let delegation = Delegation::create_root(
674            &root,
675            &agent_b.identity().did,
676            vec![Caveat::ActionScope(vec!["resolve".into()])],
677        )
678        .unwrap();
679
680        let invocation =
681            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
682
683        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity()).unwrap();
684
685        assert_eq!(result.invoker_did, agent_b.identity().did);
686        assert_eq!(result.root_did, root.identity().did);
687        assert_eq!(result.depth, 1); // invoker -> root
688    }
689
690    #[test]
691    fn test_verify_chained_invocation() {
692        let root = keypair();
693        let agent_b = keypair();
694        let agent_c = keypair();
695
696        let d1 = Delegation::create_root(
697            &root,
698            &agent_b.identity().did,
699            vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
700        )
701        .unwrap();
702
703        let d2 = Delegation::delegate(
704            &agent_b,
705            &agent_c.identity().did,
706            vec![], // inherit parent's caveats
707            d1,
708        )
709        .unwrap();
710
711        let invocation =
712            Invocation::create(&agent_c, "resolve", serde_json::json!({}), d2).unwrap();
713
714        let result = verify_invocation(&invocation, &agent_c.identity(), &root.identity()).unwrap();
715
716        assert_eq!(result.invoker_did, agent_c.identity().did);
717        assert_eq!(result.root_did, root.identity().did);
718        assert_eq!(result.depth, 2); // C -> B -> root
719    }
720
721    #[test]
722    fn test_verify_wrong_root_fails() {
723        let root = keypair();
724        let agent_b = keypair();
725        let fake_root = keypair();
726
727        let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
728
729        let invocation =
730            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
731
732        let result = verify_invocation(&invocation, &agent_b.identity(), &fake_root.identity());
733        assert!(result.is_err());
734    }
735
736    // --- Caveat enforcement ---
737
738    #[test]
739    fn test_action_scope_caveat_passes() {
740        let root = keypair();
741        let agent_b = keypair();
742
743        let delegation = Delegation::create_root(
744            &root,
745            &agent_b.identity().did,
746            vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
747        )
748        .unwrap();
749
750        let invocation =
751            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
752
753        assert!(verify_invocation(&invocation, &agent_b.identity(), &root.identity()).is_ok());
754    }
755
756    #[test]
757    fn test_action_scope_caveat_blocks() {
758        let root = keypair();
759        let agent_b = keypair();
760
761        let delegation = Delegation::create_root(
762            &root,
763            &agent_b.identity().did,
764            vec![Caveat::ActionScope(vec!["resolve".into()])],
765        )
766        .unwrap();
767
768        let invocation = Invocation::create(
769            &agent_b,
770            "merge", // not in allowed scope
771            serde_json::json!({}),
772            delegation,
773        )
774        .unwrap();
775
776        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
777        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
778    }
779
780    #[test]
781    fn test_expires_at_caveat_blocks() {
782        let root = keypair();
783        let agent_b = keypair();
784
785        let delegation = Delegation::create_root(
786            &root,
787            &agent_b.identity().did,
788            vec![Caveat::ExpiresAt("2020-01-01T00:00:00.000Z".into())],
789        )
790        .unwrap();
791
792        let invocation =
793            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
794
795        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
796        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
797    }
798
799    #[test]
800    fn test_max_cost_caveat_passes() {
801        let root = keypair();
802        let agent_b = keypair();
803
804        let delegation =
805            Delegation::create_root(&root, &agent_b.identity().did, vec![Caveat::MaxCost(5.0)])
806                .unwrap();
807
808        let invocation = Invocation::create(
809            &agent_b,
810            "resolve",
811            serde_json::json!({"cost": 3.50}),
812            delegation,
813        )
814        .unwrap();
815
816        assert!(verify_invocation(&invocation, &agent_b.identity(), &root.identity()).is_ok());
817    }
818
819    #[test]
820    fn test_max_cost_caveat_blocks() {
821        let root = keypair();
822        let agent_b = keypair();
823
824        let delegation =
825            Delegation::create_root(&root, &agent_b.identity().did, vec![Caveat::MaxCost(5.0)])
826                .unwrap();
827
828        let invocation = Invocation::create(
829            &agent_b,
830            "resolve",
831            serde_json::json!({"cost": 10.0}),
832            delegation,
833        )
834        .unwrap();
835
836        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
837        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
838    }
839
840    #[test]
841    fn test_resource_caveat_glob() {
842        let root = keypair();
843        let agent_b = keypair();
844
845        let delegation = Delegation::create_root(
846            &root,
847            &agent_b.identity().did,
848            vec![Caveat::Resource("entity:customer:*".into())],
849        )
850        .unwrap();
851
852        // Matching resource
853        let inv_ok = Invocation::create(
854            &agent_b,
855            "resolve",
856            serde_json::json!({"resource": "entity:customer:123"}),
857            delegation.clone(),
858        )
859        .unwrap();
860        assert!(verify_invocation(&inv_ok, &agent_b.identity(), &root.identity()).is_ok());
861
862        // Non-matching resource
863        let inv_bad = Invocation::create(
864            &agent_b,
865            "resolve",
866            serde_json::json!({"resource": "entity:order:456"}),
867            delegation,
868        )
869        .unwrap();
870        let result = verify_invocation(&inv_bad, &agent_b.identity(), &root.identity());
871        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
872    }
873
874    #[test]
875    fn test_context_caveat() {
876        let root = keypair();
877        let agent_b = keypair();
878
879        let delegation = Delegation::create_root(
880            &root,
881            &agent_b.identity().did,
882            vec![Caveat::Context {
883                key: "task_id".into(),
884                value: "task-abc".into(),
885            }],
886        )
887        .unwrap();
888
889        // Correct context
890        let inv_ok = Invocation::create(
891            &agent_b,
892            "resolve",
893            serde_json::json!({"task_id": "task-abc"}),
894            delegation.clone(),
895        )
896        .unwrap();
897        assert!(verify_invocation(&inv_ok, &agent_b.identity(), &root.identity()).is_ok());
898
899        // Wrong context
900        let inv_bad = Invocation::create(
901            &agent_b,
902            "resolve",
903            serde_json::json!({"task_id": "task-xyz"}),
904            delegation,
905        )
906        .unwrap();
907        assert!(matches!(
908            verify_invocation(&inv_bad, &agent_b.identity(), &root.identity()),
909            Err(CryptoError::CaveatViolation(_))
910        ));
911    }
912
913    #[test]
914    fn test_attenuation_narrows_not_widens() {
915        let root = keypair();
916        let agent_b = keypair();
917        let agent_c = keypair();
918
919        // Root gives B: resolve + search
920        let d1 = Delegation::create_root(
921            &root,
922            &agent_b.identity().did,
923            vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
924        )
925        .unwrap();
926
927        // B gives C: only resolve (narrower)
928        let d2 = Delegation::delegate(
929            &agent_b,
930            &agent_c.identity().did,
931            vec![Caveat::ActionScope(vec!["resolve".into()])],
932            d1,
933        )
934        .unwrap();
935
936        // C tries to search - blocked by C's caveat
937        let inv = Invocation::create(&agent_c, "search", serde_json::json!({}), d2).unwrap();
938
939        let result = verify_invocation(&inv, &agent_c.identity(), &root.identity());
940        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
941    }
942
943    #[test]
944    fn test_three_level_chain() {
945        let root = keypair();
946        let agent_b = keypair();
947        let agent_c = keypair();
948        let agent_d = keypair();
949
950        let d1 = Delegation::create_root(
951            &root,
952            &agent_b.identity().did,
953            vec![Caveat::ActionScope(vec!["resolve".into()])],
954        )
955        .unwrap();
956
957        let d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
958
959        let d3 = Delegation::delegate(&agent_c, &agent_d.identity().did, vec![], d2).unwrap();
960
961        let inv = Invocation::create(&agent_d, "resolve", serde_json::json!({}), d3).unwrap();
962
963        let result = verify_invocation(&inv, &agent_d.identity(), &root.identity()).unwrap();
964        assert_eq!(result.depth, 3); // D -> C -> B -> root
965    }
966
967    #[test]
968    fn test_verify_delegation_chain() {
969        let root = keypair();
970        let agent_b = keypair();
971        let agent_c = keypair();
972
973        let d1 = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
974
975        let d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
976
977        let chain = verify_delegation_chain(&d2, &root.identity()).unwrap();
978        assert!(chain.contains(&root.identity().did));
979        assert!(chain.contains(&agent_b.identity().did));
980        assert!(chain.contains(&agent_c.identity().did));
981    }
982
983    #[test]
984    fn test_delegation_serialization_roundtrip() {
985        let root = keypair();
986        let agent_b = keypair();
987
988        let delegation = Delegation::create_root(
989            &root,
990            &agent_b.identity().did,
991            vec![
992                Caveat::ActionScope(vec!["resolve".into()]),
993                Caveat::ExpiresAt("2030-01-01T00:00:00.000Z".into()),
994                Caveat::MaxCost(10.0),
995            ],
996        )
997        .unwrap();
998
999        let json = serde_json::to_string(&delegation).unwrap();
1000        let restored: Delegation = serde_json::from_str(&json).unwrap();
1001        assert_eq!(restored.issuer_did, delegation.issuer_did);
1002        assert_eq!(restored.delegate_did, delegation.delegate_did);
1003        assert_eq!(restored.caveats.len(), 3);
1004    }
1005
1006    #[test]
1007    fn test_caveat_serialization_roundtrip() {
1008        let caveats = vec![
1009            Caveat::ActionScope(vec!["resolve".into(), "search".into()]),
1010            Caveat::ExpiresAt("2030-01-01T00:00:00.000Z".into()),
1011            Caveat::MaxCost(5.0),
1012            Caveat::Resource("entity:*".into()),
1013            Caveat::Context {
1014                key: "task_id".into(),
1015                value: "t1".into(),
1016            },
1017            Caveat::Custom {
1018                key: "org".into(),
1019                value: serde_json::json!("acme"),
1020            },
1021        ];
1022
1023        for caveat in &caveats {
1024            let json = serde_json::to_string(caveat).unwrap();
1025            let restored: Caveat = serde_json::from_str(&json).unwrap();
1026            assert_eq!(&restored, caveat, "Roundtrip failed for {:?}", caveat);
1027        }
1028    }
1029
1030    #[test]
1031    fn test_glob_matching() {
1032        assert!(matches_glob("entity:*", "entity:customer:123"));
1033        assert!(matches_glob("entity:customer:*", "entity:customer:123"));
1034        assert!(!matches_glob("entity:customer:*", "entity:order:456"));
1035        assert!(matches_glob("exact", "exact"));
1036        assert!(!matches_glob("exact", "other"));
1037        assert!(matches_glob("*", "anything"));
1038    }
1039
1040    #[test]
1041    fn test_max_cost_missing_field_fails() {
1042        let root = keypair();
1043        let agent_b = keypair();
1044
1045        let delegation =
1046            Delegation::create_root(&root, &agent_b.identity().did, vec![Caveat::MaxCost(5.0)])
1047                .unwrap();
1048
1049        // No cost field in args - should fail (not silently pass)
1050        let invocation =
1051            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1052
1053        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
1054        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
1055    }
1056
1057    #[test]
1058    fn test_resource_missing_field_fails() {
1059        let root = keypair();
1060        let agent_b = keypair();
1061
1062        let delegation = Delegation::create_root(
1063            &root,
1064            &agent_b.identity().did,
1065            vec![Caveat::Resource("entity:*".into())],
1066        )
1067        .unwrap();
1068
1069        // No resource field in args - should fail
1070        let invocation =
1071            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1072
1073        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
1074        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
1075    }
1076
1077    #[test]
1078    fn test_embedded_public_key_present() {
1079        let root = keypair();
1080        let agent_b = keypair();
1081
1082        let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1083
1084        assert_eq!(
1085            delegation.issuer_public_key,
1086            root.identity().public_key_bytes
1087        );
1088    }
1089
1090    #[test]
1091    fn test_tampered_delegation_caveats_detected() {
1092        let root = keypair();
1093        let agent_b = keypair();
1094
1095        let mut delegation = Delegation::create_root(
1096            &root,
1097            &agent_b.identity().did,
1098            vec![Caveat::ActionScope(vec!["resolve".into()])],
1099        )
1100        .unwrap();
1101
1102        // Tamper with outer caveats to widen scope
1103        delegation.caveats = vec![Caveat::ActionScope(vec!["resolve".into(), "merge".into()])];
1104
1105        let invocation =
1106            Invocation::create(&agent_b, "merge", serde_json::json!({}), delegation).unwrap();
1107
1108        // Should fail because verification reads caveats from signed payload,
1109        // not from the tampered outer field
1110        let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
1111        assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
1112    }
1113
1114    #[test]
1115    fn test_intermediate_signature_verified() {
1116        let root = keypair();
1117        let agent_b = keypair();
1118        let agent_c = keypair();
1119
1120        let d1 = Delegation::create_root(
1121            &root,
1122            &agent_b.identity().did,
1123            vec![Caveat::ActionScope(vec!["resolve".into()])],
1124        )
1125        .unwrap();
1126
1127        let mut d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
1128
1129        // Tamper with d2's proof signature (corrupt it)
1130        d2.proof.signature = "00".repeat(64);
1131
1132        let invocation =
1133            Invocation::create(&agent_c, "resolve", serde_json::json!({}), d2).unwrap();
1134
1135        // Should fail because B's delegation signature is now verified
1136        let result = verify_invocation(&invocation, &agent_c.identity(), &root.identity());
1137        assert!(result.is_err());
1138    }
1139
1140    // --- Revocation ---
1141
1142    #[test]
1143    fn test_revocation_blocks_invocation() {
1144        let root = keypair();
1145        let agent_b = keypair();
1146
1147        let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1148
1149        let revoked_hash = delegation.proof.content_hash();
1150
1151        let invocation =
1152            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1153
1154        // Without revocation - passes
1155        assert!(verify_invocation(&invocation, &agent_b.identity(), &root.identity()).is_ok());
1156
1157        // With revocation - fails
1158        let result = verify_invocation_with_revocation(
1159            &invocation,
1160            &agent_b.identity(),
1161            &root.identity(),
1162            |hash| hash == revoked_hash,
1163        );
1164        assert!(matches!(result, Err(CryptoError::DelegationRevoked(_))));
1165    }
1166
1167    #[test]
1168    fn test_revocation_in_chain_blocks() {
1169        let root = keypair();
1170        let agent_b = keypair();
1171        let agent_c = keypair();
1172
1173        let d1 = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1174
1175        let revoked_hash = d1.proof.content_hash();
1176
1177        let d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
1178
1179        let invocation =
1180            Invocation::create(&agent_c, "resolve", serde_json::json!({}), d2).unwrap();
1181
1182        // Revoking the root delegation blocks the entire chain
1183        let result = verify_invocation_with_revocation(
1184            &invocation,
1185            &agent_c.identity(),
1186            &root.identity(),
1187            |hash| hash == revoked_hash,
1188        );
1189        assert!(matches!(result, Err(CryptoError::DelegationRevoked(_))));
1190    }
1191
1192    #[test]
1193    fn test_no_revocation_callback_passes() {
1194        let root = keypair();
1195        let agent_b = keypair();
1196
1197        let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1198
1199        let invocation =
1200            Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1201
1202        // Default (no revocation) always passes
1203        let result = verify_invocation_with_revocation(
1204            &invocation,
1205            &agent_b.identity(),
1206            &root.identity(),
1207            |_| false,
1208        );
1209        assert!(result.is_ok());
1210    }
1211}