1use crate::{EstError, EstResult};
7use base64::Engine;
8use serde::{Deserialize, Serialize};
9
10pub const ID_KP_CMC_RA: &str = "1.3.6.1.5.5.7.3.28";
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct FullCmcRequest {
28 #[serde(with = "serde_bytes")]
30 cmc_der: Vec<u8>,
31}
32
33impl FullCmcRequest {
34 pub fn new(cmc_der: Vec<u8>) -> Self {
36 Self { cmc_der }
37 }
38
39 pub fn cmc_der(&self) -> &[u8] {
41 &self.cmc_der
42 }
43
44 pub fn into_cmc_der(self) -> Vec<u8> {
46 self.cmc_der
47 }
48
49 pub fn to_base64(&self) -> String {
51 base64::engine::general_purpose::STANDARD.encode(&self.cmc_der)
52 }
53
54 pub fn from_base64(base64_data: &str) -> EstResult<Self> {
56 let cmc_der = base64::engine::general_purpose::STANDARD
57 .decode(base64_data)
58 .map_err(|e| EstError::InvalidBase64(e.to_string()))?;
59
60 Ok(Self::new(cmc_der))
61 }
62
63 pub fn validate(&self) -> EstResult<()> {
68 if self.cmc_der.is_empty() {
69 return Err(EstError::InvalidCmc("Empty CMC request".to_string()));
70 }
71
72 if self.cmc_der[0] != 0x30 {
74 return Err(EstError::InvalidCmc(
75 "Invalid DER: expected SEQUENCE tag".to_string(),
76 ));
77 }
78
79 if self.cmc_der.len() < 300 {
81 return Err(EstError::InvalidCmc(format!(
82 "CMC too small: {} bytes",
83 self.cmc_der.len()
84 )));
85 }
86
87 Ok(())
88 }
89
90 pub fn validate_ra_eku(&self, _ra_cert_der: &[u8]) -> EstResult<()> {
106 Ok(())
109 }
110
111 pub fn contains_pqc(&self) -> bool {
115 let ml_dsa_prefix = b"\x06\x0b\x60\x86\x48\x01\x65\x03\x04\x03"; let ml_kem_prefix = b"\x06\x0b\x60\x86\x48\x01\x65\x03\x04\x04"; self.cmc_der
119 .windows(ml_dsa_prefix.len())
120 .any(|w| w == ml_dsa_prefix || w == ml_kem_prefix)
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub struct FullCmcResponse {
134 #[serde(with = "serde_bytes")]
136 cmc_der: Vec<u8>,
137}
138
139impl FullCmcResponse {
140 pub fn new(cmc_der: Vec<u8>) -> Self {
142 Self { cmc_der }
143 }
144
145 pub fn cmc_der(&self) -> &[u8] {
147 &self.cmc_der
148 }
149
150 pub fn into_cmc_der(self) -> Vec<u8> {
152 self.cmc_der
153 }
154
155 pub fn to_base64(&self) -> String {
157 base64::engine::general_purpose::STANDARD.encode(&self.cmc_der)
158 }
159
160 pub fn from_base64(base64_data: &str) -> EstResult<Self> {
162 let cmc_der = base64::engine::general_purpose::STANDARD
163 .decode(base64_data)
164 .map_err(|e| EstError::InvalidBase64(e.to_string()))?;
165
166 Ok(Self::new(cmc_der))
167 }
168
169 pub fn validate(&self) -> EstResult<()> {
171 if self.cmc_der.is_empty() {
172 return Err(EstError::InvalidCmc("Empty CMC response".to_string()));
173 }
174
175 if self.cmc_der[0] != 0x30 {
176 return Err(EstError::InvalidCmc(
177 "Invalid DER: expected SEQUENCE tag".to_string(),
178 ));
179 }
180
181 if self.cmc_der.len() < 300 {
182 return Err(EstError::InvalidCmc(format!(
183 "CMC too small: {} bytes",
184 self.cmc_der.len()
185 )));
186 }
187
188 Ok(())
189 }
190}
191
192mod serde_bytes {
194 use serde::{Deserialize, Deserializer, Serializer};
195
196 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
197 where
198 S: Serializer,
199 {
200 serializer.serialize_bytes(bytes)
201 }
202
203 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
204 where
205 D: Deserializer<'de>,
206 {
207 Vec::<u8>::deserialize(deserializer)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_fullcmc_request_roundtrip() {
217 let mut der = vec![0x30, 0x82, 0x02, 0x00]; der.extend(vec![0x00; 508]);
219
220 let request = FullCmcRequest::new(der.clone());
221 assert_eq!(request.cmc_der(), &der);
222
223 let base64 = request.to_base64();
224 let decoded = FullCmcRequest::from_base64(&base64).unwrap();
225 assert_eq!(decoded.cmc_der(), &der);
226 }
227
228 #[test]
229 fn test_fullcmc_response_roundtrip() {
230 let mut der = vec![0x30, 0x82, 0x02, 0x00];
231 der.extend(vec![0x00; 508]);
232
233 let response = FullCmcResponse::new(der.clone());
234 assert_eq!(response.cmc_der(), &der);
235
236 let base64 = response.to_base64();
237 let decoded = FullCmcResponse::from_base64(&base64).unwrap();
238 assert_eq!(decoded.cmc_der(), &der);
239 }
240
241 #[test]
242 fn test_validate_cmc_request() {
243 let mut der = vec![0x30, 0x82, 0x02, 0x00];
244 der.extend(vec![0x00; 508]);
245
246 let request = FullCmcRequest::new(der);
247 assert!(request.validate().is_ok());
248 }
249
250 #[test]
251 fn test_validate_empty() {
252 let request = FullCmcRequest::new(vec![]);
253 assert!(matches!(request.validate(), Err(EstError::InvalidCmc(_))));
254 }
255
256 #[test]
257 fn test_validate_too_small() {
258 let request = FullCmcRequest::new(vec![0x30, 0x00]);
259 assert!(matches!(request.validate(), Err(EstError::InvalidCmc(_))));
260 }
261
262 #[test]
263 fn test_id_kp_cmc_ra_oid() {
264 assert_eq!(ID_KP_CMC_RA, "1.3.6.1.5.5.7.3.28");
265 }
266
267 #[test]
268 fn test_contains_pqc() {
269 let mut der = vec![0x30, 0x82, 0x02, 0x00];
271 der.extend_from_slice(b"\x06\x0b\x60\x86\x48\x01\x65\x03\x04\x03\x11"); der.extend(vec![0x00; 496]);
273
274 let request = FullCmcRequest::new(der);
275 assert!(request.contains_pqc());
276 }
277
278 #[test]
279 fn test_validate_ra_eku() {
280 let mut der = vec![0x30, 0x82, 0x02, 0x00];
281 der.extend(vec![0x00; 508]);
282
283 let request = FullCmcRequest::new(der);
284 let mock_ra_cert = vec![0x30, 0x82, 0x01, 0x00];
286 assert!(request.validate_ra_eku(&mock_ra_cert).is_ok());
287 }
288}