1use der::{Decode, Encode};
2use sha2::{Digest, Sha256};
3use x509_ocsp::OcspRequest;
4
5use crate::error::{CoreError, Result};
6
7pub const MAX_REQUEST_SIZE: usize = 8192;
9
10#[derive(Debug)]
12pub struct ParsedRequest {
13 pub cert_ids: Vec<ParsedCertId>,
14 pub nonce: Option<Vec<u8>>,
15 pub preferred_sig_algs: Vec<u16>,
18}
19
20#[derive(Debug, Clone)]
23pub struct ParsedCertId {
24 pub entry_key: [u8; 32],
25 pub certid_der: Vec<u8>,
26 pub issuer_name_hash: Vec<u8>,
27 pub issuer_key_hash: Vec<u8>,
28 pub serial_number: Vec<u8>,
29 pub hash_alg_oid: String,
33}
34
35pub fn parse_ocsp_request(der_bytes: &[u8]) -> Result<ParsedRequest> {
37 if der_bytes.is_empty() {
38 return Err(CoreError::EmptyRequest);
39 }
40 if der_bytes.len() > MAX_REQUEST_SIZE {
41 return Err(CoreError::RequestTooLarge {
42 size: der_bytes.len(),
43 max: MAX_REQUEST_SIZE,
44 });
45 }
46
47 let ocsp_req = OcspRequest::from_der(der_bytes).map_err(|e| CoreError::DerParse {
48 context: "OCSPRequest",
49 detail: e.to_string(),
50 })?;
51
52 let tbs = &ocsp_req.tbs_request;
53
54 if tbs.request_list.is_empty() {
55 return Err(CoreError::EmptyRequestList);
56 }
57
58 let mut cert_ids = Vec::with_capacity(tbs.request_list.len());
59
60 for req in tbs.request_list.iter() {
61 let cert_id = &req.req_cert;
62
63 let certid_der = cert_id.to_der().map_err(|e| CoreError::DerParse {
64 context: "CertID encode",
65 detail: e.to_string(),
66 })?;
67
68 let entry_key: [u8; 32] = Sha256::digest(&certid_der).into();
69
70 cert_ids.push(ParsedCertId {
71 entry_key,
72 certid_der,
73 issuer_name_hash: cert_id.issuer_name_hash.as_bytes().to_vec(),
74 issuer_key_hash: cert_id.issuer_key_hash.as_bytes().to_vec(),
75 serial_number: cert_id.serial_number.as_bytes().to_vec(),
76 hash_alg_oid: cert_id.hash_algorithm.oid.to_string(),
77 });
78 }
79
80 let nonce_ext = tbs.request_extensions.as_ref().and_then(|exts| {
88 exts.iter()
89 .find(|ext| ext.extn_id == der::oid::db::rfc6960::ID_PKIX_OCSP_NONCE)
90 });
91
92 let nonce = match nonce_ext {
93 Some(ext) => {
94 let raw = ext.extn_value.as_bytes();
95 let inner =
96 der::asn1::OctetStringRef::from_der(raw).map_err(|e| CoreError::DerParse {
97 context: "nonce extension value",
98 detail: e.to_string(),
99 })?;
100 Some(inner.as_bytes().to_vec())
101 }
102 None => None,
103 };
104
105 let preferred_sig_algs = parse_preferred_sig_algs(tbs.request_extensions.as_ref());
106
107 Ok(ParsedRequest {
108 cert_ids,
109 nonce,
110 preferred_sig_algs,
111 })
112}
113
114#[derive(der::Sequence)]
116struct PreferredSignatureAlgorithm<'a> {
117 sig_identifier: spki::AlgorithmIdentifierRef<'a>,
118 #[asn1(optional = "true")]
119 cert_identifier: Option<spki::AlgorithmIdentifierRef<'a>>,
120}
121
122fn sig_oid_to_discriminator(oid: &const_oid::ObjectIdentifier) -> Option<u16> {
125 const ECDSA_SHA256: const_oid::ObjectIdentifier =
126 const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
127 const ML_DSA_44: const_oid::ObjectIdentifier =
128 const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.3.17");
129 const ML_DSA_65: const_oid::ObjectIdentifier =
130 const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.3.18");
131 const ML_DSA_87: const_oid::ObjectIdentifier =
132 const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.3.19");
133
134 if *oid == ECDSA_SHA256 {
135 Some(ahu::ALG_DISC_DEFAULT)
136 } else if *oid == ML_DSA_44 {
137 Some(ahu::ALG_DISC_ML_DSA_44)
138 } else if *oid == ML_DSA_65 {
139 Some(ahu::ALG_DISC_ML_DSA_65)
140 } else if *oid == ML_DSA_87 {
141 Some(ahu::ALG_DISC_ML_DSA_87)
142 } else {
143 None
144 }
145}
146
147fn parse_preferred_sig_algs(extensions: Option<&x509_cert::ext::Extensions>) -> Vec<u16> {
148 use der::Decode;
149
150 let pref_sig_algs_oid = const_oid::ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.48.1.8");
151
152 let ext = match extensions {
153 Some(exts) => match exts.iter().find(|e| e.extn_id == pref_sig_algs_oid) {
154 Some(e) => e,
155 None => return Vec::new(),
156 },
157 None => return Vec::new(),
158 };
159
160 let raw = ext.extn_value.as_bytes();
161
162 let prefs: Vec<PreferredSignatureAlgorithm<'_>> = match Vec::from_der(raw) {
163 Ok(v) => v,
164 Err(e) => {
165 tracing::debug!("failed to parse PreferredSignatureAlgorithms: {e}");
166 return Vec::new();
167 }
168 };
169
170 prefs
171 .iter()
172 .filter_map(|p| sig_oid_to_discriminator(&p.sig_identifier.oid))
173 .collect()
174}
175
176pub fn decode_get_path(path: &str) -> Result<Vec<u8>> {
181 use base64::Engine;
182 use percent_encoding::percent_decode_str;
183
184 let decoded_path = percent_decode_str(path)
185 .decode_utf8()
186 .map_err(|e| CoreError::GetDecode(format!("URL decode: {e}")))?;
187
188 let trimmed = decoded_path.trim_start_matches('/');
189
190 base64::engine::general_purpose::STANDARD
191 .decode(trimmed)
192 .map_err(|e| CoreError::GetDecode(format!("base64: {e}")))
193}
194
195pub fn validate_nonce(nonce_bytes: &[u8]) -> NonceAction {
197 match nonce_bytes.len() {
198 0 => NonceAction::MalformedRequest,
199 1..=15 => NonceAction::MayOmit,
200 16..=32 => NonceAction::MustAccept,
201 33..=128 => NonceAction::MayOmit,
202 _ => NonceAction::MalformedRequest,
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum NonceAction {
208 MustAccept,
209 MayOmit,
210 MalformedRequest,
211}