Skip to main content

kipuka_est/
lib.rs

1//! EST (RFC 7030) protocol types with ML-DSA (FIPS 204) and ML-KEM (FIPS 203) support.
2//!
3//! This crate implements the wire protocol types for Enrollment over Secure Transport,
4//! with comprehensive support for NIST FIPS 204 (ML-DSA) digital signatures and
5//! FIPS 203 (ML-KEM) key encapsulation mechanisms.
6//!
7//! # Supported Operations
8//!
9//! - `/cacerts` - Retrieve CA certificate chain
10//! - `/simpleenroll` - Certificate enrollment with PKCS#10 CSR
11//! - `/simplereenroll` - Certificate re-enrollment with mTLS
12//! - `/fullcmc` - Full CMC protocol support
13//! - `/serverkeygen` - Server-side key generation with ML-KEM KRA support
14//! - `/csrattrs` - CSR attribute hints including PQC algorithm OIDs
15//!
16//! # Post-Quantum Cryptography
17//!
18//! All enrollment operations support:
19//! - ML-DSA-44, ML-DSA-65, ML-DSA-87 (FIPS 204 digital signatures)
20//! - ML-KEM-512, ML-KEM-768, ML-KEM-1024 (FIPS 203 key encapsulation)
21//! - Composite algorithms (ML-DSA + traditional) per OID arc 2.16.840.1.114027.80.5.2
22
23pub mod cacerts;
24pub mod content_type;
25pub mod csrattrs;
26pub mod enroll;
27pub mod fullcmc;
28pub mod reenroll;
29pub mod serverkeygen;
30
31use thiserror::Error;
32
33/// EST protocol errors.
34#[derive(Debug, Error, Clone)]
35pub enum EstError {
36    /// Invalid base64 encoding.
37    #[error("Invalid base64 encoding: {0}")]
38    InvalidBase64(String),
39
40    /// Invalid DER encoding.
41    #[error("Invalid DER encoding: {0}")]
42    InvalidDer(String),
43
44    /// Invalid PKCS#7 structure.
45    #[error("Invalid PKCS#7 structure: {0}")]
46    InvalidPkcs7(String),
47
48    /// Invalid PKCS#10 CSR.
49    #[error("Invalid PKCS#10 CSR: {0}")]
50    InvalidPkcs10(String),
51
52    /// Invalid PKCS#8 private key.
53    #[error("Invalid PKCS#8 private key: {0}")]
54    InvalidPkcs8(String),
55
56    /// Invalid CMC request.
57    #[error("Invalid CMC request: {0}")]
58    InvalidCmc(String),
59
60    /// Missing required field.
61    #[error("Missing required field: {0}")]
62    MissingField(String),
63
64    /// Unsupported algorithm.
65    #[error("Unsupported algorithm: OID {0}")]
66    UnsupportedAlgorithm(String),
67
68    /// Invalid multipart MIME structure.
69    #[error("Invalid multipart MIME: {0}")]
70    InvalidMultipart(String),
71
72    /// Subject mismatch in re-enrollment.
73    #[error("Subject mismatch: expected {expected}, got {actual}")]
74    SubjectMismatch { expected: String, actual: String },
75
76    /// Invalid proof of possession.
77    #[error("Invalid proof of possession: {0}")]
78    InvalidPop(String),
79
80    /// Invalid EKU for CMC RA.
81    #[error("Invalid EKU: expected id-kp-cmcRA")]
82    InvalidEku,
83
84    /// ML-KEM level mismatch.
85    #[error("ML-KEM level mismatch: requested {requested}, server only supports {supported}")]
86    MlKemLevelMismatch { requested: u16, supported: u16 },
87
88    /// Generic protocol error.
89    #[error("EST protocol error: {0}")]
90    Protocol(String),
91}
92
93/// Result type for EST operations.
94pub type EstResult<T> = Result<T, EstError>;
95
96/// EST protocol operations per RFC 7030.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98pub enum EstOperation {
99    /// Retrieve CA certificates (§4.1).
100    CaCerts,
101
102    /// Simple enrollment (§4.2).
103    SimpleEnroll,
104
105    /// Simple re-enrollment (§4.2.2).
106    SimpleReenroll,
107
108    /// Full CMC (§4.3).
109    FullCmc,
110
111    /// Server-side key generation (§4.4) with ML-KEM KRA support.
112    ServerKeygen,
113
114    /// CSR attributes (§4.5).
115    CsrAttrs,
116}
117
118impl EstOperation {
119    /// Returns the URL path segment for this operation.
120    pub fn path(&self) -> &'static str {
121        match self {
122            Self::CaCerts => "cacerts",
123            Self::SimpleEnroll => "simpleenroll",
124            Self::SimpleReenroll => "simplereenroll",
125            Self::FullCmc => "fullcmc",
126            Self::ServerKeygen => "serverkeygen",
127            Self::CsrAttrs => "csrattrs",
128        }
129    }
130
131    /// Returns whether this operation requires mTLS client authentication.
132    pub fn requires_mtls(&self) -> bool {
133        match self {
134            Self::CaCerts | Self::CsrAttrs => false,
135            Self::SimpleEnroll | Self::SimpleReenroll | Self::FullCmc | Self::ServerKeygen => true,
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_operation_paths() {
146        assert_eq!(EstOperation::CaCerts.path(), "cacerts");
147        assert_eq!(EstOperation::SimpleEnroll.path(), "simpleenroll");
148        assert_eq!(EstOperation::SimpleReenroll.path(), "simplereenroll");
149        assert_eq!(EstOperation::FullCmc.path(), "fullcmc");
150        assert_eq!(EstOperation::ServerKeygen.path(), "serverkeygen");
151        assert_eq!(EstOperation::CsrAttrs.path(), "csrattrs");
152    }
153
154    #[test]
155    fn test_mtls_requirements() {
156        assert!(!EstOperation::CaCerts.requires_mtls());
157        assert!(!EstOperation::CsrAttrs.requires_mtls());
158        assert!(EstOperation::SimpleEnroll.requires_mtls());
159        assert!(EstOperation::SimpleReenroll.requires_mtls());
160        assert!(EstOperation::FullCmc.requires_mtls());
161        assert!(EstOperation::ServerKeygen.requires_mtls());
162    }
163}