1use crate::enroll::{CertificationRequest, EnrollRequest, EnrollResponse};
10use crate::{EstError, EstResult};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct ReenrollRequest {
23 #[serde(flatten)]
25 inner: EnrollRequest,
26}
27
28impl ReenrollRequest {
29 pub fn new(csr_der: Vec<u8>) -> Self {
31 Self {
32 inner: EnrollRequest::new(csr_der),
33 }
34 }
35
36 pub fn from_enroll_request(inner: EnrollRequest) -> Self {
38 Self { inner }
39 }
40
41 pub fn inner(&self) -> &EnrollRequest {
43 &self.inner
44 }
45
46 pub fn into_inner(self) -> EnrollRequest {
48 self.inner
49 }
50
51 pub fn csr_der(&self) -> &[u8] {
53 self.inner.csr_der()
54 }
55
56 pub fn into_csr_der(self) -> Vec<u8> {
58 self.inner.into_csr_der()
59 }
60
61 pub fn to_base64(&self) -> String {
63 self.inner.to_base64()
64 }
65
66 pub fn from_base64(base64_data: &str) -> EstResult<Self> {
68 let inner = EnrollRequest::from_base64(base64_data)?;
69 Ok(Self { inner })
70 }
71
72 pub fn validate(&self) -> EstResult<()> {
74 self.inner.validate()
75 }
76
77 pub fn validate_subject_match(&self, mtls_subject: &str, csr_subject: &str) -> EstResult<()> {
93 if mtls_subject != csr_subject {
94 return Err(EstError::SubjectMismatch {
95 expected: mtls_subject.to_string(),
96 actual: csr_subject.to_string(),
97 });
98 }
99 Ok(())
100 }
101
102 pub fn contains_ml_dsa(&self) -> bool {
104 self.inner.contains_ml_dsa()
105 }
106
107 pub fn contains_ml_kem(&self) -> bool {
109 self.inner.contains_ml_kem()
110 }
111
112 pub fn to_certification_request(&self) -> CertificationRequest {
117 self.inner.to_certification_request()
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub struct ReenrollResponse {
127 #[serde(flatten)]
129 inner: EnrollResponse,
130}
131
132impl ReenrollResponse {
133 pub fn new(pkcs7_der: Vec<u8>) -> Self {
135 Self {
136 inner: EnrollResponse::new(pkcs7_der),
137 }
138 }
139
140 pub fn from_enroll_response(inner: EnrollResponse) -> Self {
142 Self { inner }
143 }
144
145 pub fn inner(&self) -> &EnrollResponse {
147 &self.inner
148 }
149
150 pub fn into_inner(self) -> EnrollResponse {
152 self.inner
153 }
154
155 pub fn pkcs7_der(&self) -> &[u8] {
157 self.inner.pkcs7_der()
158 }
159
160 pub fn into_pkcs7_der(self) -> Vec<u8> {
162 self.inner.into_pkcs7_der()
163 }
164
165 pub fn to_base64(&self) -> String {
167 self.inner.to_base64()
168 }
169
170 pub fn from_base64(base64_data: &str) -> EstResult<Self> {
172 let inner = EnrollResponse::from_base64(base64_data)?;
173 Ok(Self { inner })
174 }
175
176 pub fn validate(&self) -> EstResult<()> {
178 self.inner.validate()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_reenroll_request_roundtrip() {
188 let mut der = vec![0x30, 0x82, 0x01, 0x00];
189 der.extend(vec![0x00; 252]);
190
191 let request = ReenrollRequest::new(der.clone());
192 assert_eq!(request.csr_der(), &der);
193
194 let base64 = request.to_base64();
195 let decoded = ReenrollRequest::from_base64(&base64).unwrap();
196 assert_eq!(decoded.csr_der(), &der);
197 }
198
199 #[test]
200 fn test_reenroll_response_roundtrip() {
201 let mut der = vec![0x30, 0x82, 0x01, 0x00];
202 der.extend(vec![0x00; 252]);
203
204 let response = ReenrollResponse::new(der.clone());
205 assert_eq!(response.pkcs7_der(), &der);
206
207 let base64 = response.to_base64();
208 let decoded = ReenrollResponse::from_base64(&base64).unwrap();
209 assert_eq!(decoded.pkcs7_der(), &der);
210 }
211
212 #[test]
213 fn test_subject_match_success() {
214 let mut der = vec![0x30, 0x82, 0x01, 0x00];
215 der.extend(vec![0x00; 252]);
216
217 let request = ReenrollRequest::new(der);
218 let mtls_subject = "CN=client.example.com,O=Example,C=US";
219 let csr_subject = "CN=client.example.com,O=Example,C=US";
220
221 assert!(
222 request
223 .validate_subject_match(mtls_subject, csr_subject)
224 .is_ok()
225 );
226 }
227
228 #[test]
229 fn test_subject_match_failure() {
230 let mut der = vec![0x30, 0x82, 0x01, 0x00];
231 der.extend(vec![0x00; 252]);
232
233 let request = ReenrollRequest::new(der);
234 let mtls_subject = "CN=client.example.com,O=Example,C=US";
235 let csr_subject = "CN=attacker.evil.com,O=Evil,C=XX";
236
237 let result = request.validate_subject_match(mtls_subject, csr_subject);
238 assert!(matches!(result, Err(EstError::SubjectMismatch { .. })));
239
240 if let Err(EstError::SubjectMismatch { expected, actual }) = result {
241 assert_eq!(expected, mtls_subject);
242 assert_eq!(actual, csr_subject);
243 }
244 }
245
246 #[test]
247 fn test_from_enroll_request() {
248 let mut der = vec![0x30, 0x82, 0x01, 0x00];
249 der.extend(vec![0x00; 252]);
250
251 let enroll_req = EnrollRequest::new(der.clone());
252 let reenroll_req = ReenrollRequest::from_enroll_request(enroll_req);
253
254 assert_eq!(reenroll_req.csr_der(), &der);
255 }
256
257 #[test]
258 fn test_from_enroll_response() {
259 let mut der = vec![0x30, 0x82, 0x01, 0x00];
260 der.extend(vec![0x00; 252]);
261
262 let enroll_resp = EnrollResponse::new(der.clone());
263 let reenroll_resp = ReenrollResponse::from_enroll_response(enroll_resp);
264
265 assert_eq!(reenroll_resp.pkcs7_der(), &der);
266 }
267
268 #[test]
269 fn test_validate() {
270 let mut der = vec![0x30, 0x82, 0x01, 0x00];
271 der.extend(vec![0x00; 252]);
272
273 let request = ReenrollRequest::new(der);
274 assert!(request.validate().is_ok());
275 }
276
277 #[test]
278 fn test_ml_dsa_detection() {
279 let mut der = vec![0x30, 0x82, 0x01, 0x00];
280 der.extend_from_slice(b"\x06\x0b\x60\x86\x48\x01\x65\x03\x04\x03\x11");
281 der.extend(vec![0x00; 240]);
282
283 let request = ReenrollRequest::new(der);
284 assert!(request.contains_ml_dsa());
285 assert!(!request.contains_ml_kem());
286 }
287
288 #[test]
289 fn test_to_certification_request() {
290 let mut der = vec![0x30, 0x82, 0x01, 0x00];
291 der.extend(vec![0x00; 252]);
292
293 let request = ReenrollRequest::new(der.clone());
294 let cr = request.to_certification_request();
295 assert_eq!(cr.version, 0);
296 assert_eq!(cr.tbs_der, der);
297 }
298}