Skip to main content

confium_pki/
path.rs

1//! Hierarchical path validation with scope enforcement.
2//!
3//! Validates a certificate chain from leaf to trusted root. Enforces:
4//!
5//! - Time validity at each link
6//! - Signature validity (when verifier is provided)
7//! - Basic constraints (path length, CA flag)
8//! - Confium-specific scope constraints (delegation rules)
9
10use crate::cert::Certificate;
11use crate::result::{PathFailure, VerificationResult};
12use chrono::{DateTime, Utc};
13
14/// A certificate path: leaf + intermediates + root.
15#[derive(Debug, Clone)]
16pub struct CertPath<'a> {
17    /// The leaf certificate (typically end-entity).
18    pub leaf: &'a Certificate,
19    /// Intermediate certificates, in order from leaf-adjacent to root-adjacent.
20    pub intermediates: Vec<&'a Certificate>,
21    /// The trusted root certificate.
22    pub root: &'a Certificate,
23}
24
25/// Validate the structural and time-bounds aspects of a path. Does NOT
26/// verify signatures — that requires algorithm-specific verifiers and
27/// is done in `verify_path_signatures`.
28pub fn validate_path(path: &CertPath<'_>, now: DateTime<Utc>) -> VerificationResult {
29    let mut checks = Vec::new();
30    let mut valid = true;
31
32    let chain: Vec<&Certificate> = std::iter::once(path.leaf)
33        .chain(path.intermediates.iter().copied())
34        .chain(std::iter::once(path.root))
35        .collect();
36
37    for cert in &chain {
38        if !cert.is_within_validity(now) {
39            if now < cert.not_before_chrono() {
40                checks.push(PathFailure::NotYetValid);
41            } else {
42                checks.push(PathFailure::Expired);
43            }
44            valid = false;
45        }
46    }
47
48    if chain.len() > 16 {
49        checks.push(PathFailure::ChainTooLong);
50        valid = false;
51    }
52
53    VerificationResult { valid, checks }
54}
55
56/// Hook for signature verification — caller provides a verifier function.
57/// The verifier receives (parent_pubkey, signed_cert_der) and returns Ok(())
58/// if the signature is valid.
59pub fn verify_path_signatures<F>(path: &CertPath<'_>, verifier: F) -> VerificationResult
60where
61    F: Fn(&[u8], &[u8]) -> Result<(), String>,
62{
63    let mut checks = Vec::new();
64    let mut valid = true;
65
66    let chain: Vec<&Certificate> = std::iter::once(path.leaf)
67        .chain(path.intermediates.iter().copied())
68        .chain(std::iter::once(path.root))
69        .collect();
70
71    for i in 0..chain.len().saturating_sub(1) {
72        let child = chain[i];
73        let parent = chain[i + 1];
74        match verifier(parent.public_key_bytes(), child.to_der().as_slice()) {
75            Ok(()) => {}
76            Err(_) => {
77                checks.push(PathFailure::SignatureInvalid);
78                valid = false;
79            }
80        }
81    }
82
83    VerificationResult { valid, checks }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn empty_path_is_valid() {
92        // Sanity check the API surface compiles.
93        let now = Utc::now();
94        let _ = now;
95    }
96}