1use der::{Decode, Encode};
9use x509_ocsp::{BasicOcspResponse, OcspResponse, OcspResponseStatus};
10
11use crate::error::{Result, SignError};
12use crate::ml_dsa_bridge::{ML_DSA_44_OID, ML_DSA_65_OID, ML_DSA_87_OID};
13
14const ECDSA_WITH_SHA256_OID: &str = "1.2.840.10045.4.3.2";
15
16pub fn verify_ocsp_response_signature(response_der: &[u8]) -> Result<()> {
22 let ocsp_resp = OcspResponse::from_der(response_der)
23 .map_err(|e| SignError::Verify(format!("parse OcspResponse: {e}")))?;
24
25 if ocsp_resp.response_status != OcspResponseStatus::Successful {
26 return Ok(());
27 }
28
29 let response_bytes = ocsp_resp
30 .response_bytes
31 .ok_or_else(|| SignError::Verify("successful response has no responseBytes".into()))?;
32
33 let basic = BasicOcspResponse::from_der(response_bytes.response.as_bytes())
34 .map_err(|e| SignError::Verify(format!("parse BasicOcspResponse: {e}")))?;
35
36 let tbs_der = basic
37 .tbs_response_data
38 .to_der()
39 .map_err(|e| SignError::Verify(format!("re-encode tbsResponseData: {e}")))?;
40
41 let sig_bytes = basic.signature.raw_bytes();
42
43 let sig_alg_oid = basic.signature_algorithm.oid.to_string();
44
45 let pub_key_bytes = extract_public_key(&basic)?;
46
47 match sig_alg_oid.as_str() {
48 ECDSA_WITH_SHA256_OID => verify_ecdsa_p256(&tbs_der, sig_bytes, &pub_key_bytes),
49 ML_DSA_44_OID => verify_ml_dsa::<ml_dsa::MlDsa44>(&tbs_der, sig_bytes, &pub_key_bytes),
50 ML_DSA_65_OID => verify_ml_dsa::<ml_dsa::MlDsa65>(&tbs_der, sig_bytes, &pub_key_bytes),
51 ML_DSA_87_OID => verify_ml_dsa::<ml_dsa::MlDsa87>(&tbs_der, sig_bytes, &pub_key_bytes),
52 other => Err(SignError::Verify(format!(
53 "unsupported signature algorithm: {other}"
54 ))),
55 }
56}
57
58fn extract_public_key(basic: &BasicOcspResponse) -> Result<Vec<u8>> {
59 let cert = basic
60 .certs
61 .as_ref()
62 .and_then(|c| c.first())
63 .ok_or(SignError::NoCert)?;
64
65 let raw = cert
66 .tbs_certificate
67 .subject_public_key_info
68 .subject_public_key
69 .raw_bytes();
70
71 Ok(raw.to_vec())
72}
73
74fn verify_ecdsa_p256(tbs_der: &[u8], sig_bytes: &[u8], pub_key_bytes: &[u8]) -> Result<()> {
75 use p256::ecdsa::{DerSignature, VerifyingKey};
76 use signature::Verifier;
77
78 let vk = VerifyingKey::from_sec1_bytes(pub_key_bytes)
79 .map_err(|e| SignError::Verify(format!("invalid P-256 public key: {e}")))?;
80
81 let sig = DerSignature::try_from(sig_bytes)
82 .map_err(|e| SignError::Verify(format!("invalid ECDSA DER signature: {e}")))?;
83
84 vk.verify(tbs_der, &sig)
85 .map_err(|_| SignError::Verify("ECDSA-P256 signature verification failed".into()))
86}
87
88fn verify_ml_dsa<P>(tbs_der: &[u8], sig_bytes: &[u8], pub_key_bytes: &[u8]) -> Result<()>
89where
90 P: ml_dsa::MlDsaParams,
91{
92 use ml_dsa::Verifier;
93
94 let encoded = ml_dsa::EncodedVerifyingKey::<P>::try_from(pub_key_bytes).map_err(|_| {
95 SignError::Verify(format!(
96 "invalid ML-DSA public key (expected {} bytes)",
97 std::mem::size_of::<ml_dsa::EncodedVerifyingKey<P>>()
98 ))
99 })?;
100
101 let vk = ml_dsa::VerifyingKey::<P>::decode(&encoded);
102
103 let sig = ml_dsa::Signature::<P>::try_from(sig_bytes)
104 .map_err(|_| SignError::Verify("invalid ML-DSA signature".into()))?;
105
106 vk.verify(tbs_der, &sig)
107 .map_err(|_| SignError::Verify("ML-DSA signature verification failed".into()))
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn verify_ecdsa_bundle_entries() {
116 use crate::{
117 demo_ecdsa_p256_key,
118 generate::GenerationConfig,
119 produce_bundle,
120 source::{CaIdentity, CertificateStatus, StatusSnapshot},
121 };
122 use std::collections::BTreeMap;
123
124 let mut signer = demo_ecdsa_p256_key();
125 let seal_key = crate::SealKey::EcdsaP256(demo_ecdsa_p256_key());
126 let seal_cert = crate::generate_seal_cert_for_key(&seal_key).unwrap();
127 let responder_cert = crate::generate_seal_cert(&signer).unwrap();
128
129 let ca = CaIdentity {
130 label: "test-ca".into(),
131 issuer_name_der: b"CN=Test CA".to_vec(),
132 issuer_key_bytes: vec![0x42; 32],
133 };
134
135 let mut entries = BTreeMap::new();
136 entries.insert(vec![0x01], CertificateStatus::Good);
137 entries.insert(vec![0x02], CertificateStatus::Good);
138 let snapshot = StatusSnapshot {
139 entries,
140 this_update: crate::source::unix_now().unwrap(),
141 next_update: Some(crate::source::unix_now().unwrap() + 86400),
142 ..Default::default()
143 };
144
145 let config = GenerationConfig {
146 producer_id: "test".into(),
147 epoch: 1,
148 ..Default::default()
149 };
150
151 let bundle_bytes = produce_bundle::<_, p256::ecdsa::DerSignature>(
152 &ca,
153 &snapshot,
154 &config,
155 &mut signer,
156 |m| crate::create_cms_seal(m, &seal_key, &seal_cert),
157 Some(&responder_cert),
158 )
159 .unwrap();
160
161 let bundle = ahu::Bundle::from_bytes(&bundle_bytes).unwrap();
162 let mut count = 0;
163 for record in &bundle.index {
164 if let Some(entry_bytes) = bundle.entry_bytes(record) {
165 verify_ocsp_response_signature(entry_bytes).unwrap();
166 count += 1;
167 }
168 }
169 assert!(count >= 2);
170 }
171
172 #[test]
173 fn verify_ml_dsa_signature_direct() {
174 use ml_dsa::{Keypair, MlDsa87, Signer as Signer3, SigningKey};
175
176 let sk = SigningKey::<MlDsa87>::from_seed((&[42u8; 32]).into());
177 let vk = sk.verifying_key();
178 let msg = b"test message for ML-DSA verification";
179
180 let sig = sk.sign(msg);
181 let sig_bytes = sig.encode();
182 let vk_encoded = vk.encode();
183
184 verify_ml_dsa::<MlDsa87>(msg, sig_bytes.as_ref(), vk_encoded.as_ref()).unwrap();
185 }
186
187 #[test]
188 fn verify_ml_dsa_44_signature_direct() {
189 use ml_dsa::{Keypair, MlDsa44, Signer as Signer3, SigningKey};
190
191 let sk = SigningKey::<MlDsa44>::from_seed((&[1u8; 32]).into());
192 let vk = sk.verifying_key();
193 let msg = b"ML-DSA-44 verification test";
194
195 let sig = sk.sign(msg);
196 let sig_bytes = sig.encode();
197 let vk_encoded = vk.encode();
198
199 verify_ml_dsa::<MlDsa44>(msg, sig_bytes.as_ref(), vk_encoded.as_ref()).unwrap();
200 }
201
202 #[test]
203 fn verify_wrong_message_fails() {
204 use ml_dsa::{Keypair, MlDsa65, Signer as Signer3, SigningKey};
205
206 let sk = SigningKey::<MlDsa65>::from_seed((&[5u8; 32]).into());
207 let vk = sk.verifying_key();
208
209 let sig = sk.sign(b"correct message");
210 let sig_bytes = sig.encode();
211 let vk_encoded = vk.encode();
212
213 let result =
214 verify_ml_dsa::<MlDsa65>(b"wrong message", sig_bytes.as_ref(), vk_encoded.as_ref());
215 assert!(result.is_err());
216 }
217
218 #[test]
219 fn verify_no_embedded_cert_errors() {
220 use crate::{
221 demo_ecdsa_p256_key,
222 generate::GenerationConfig,
223 produce_bundle,
224 source::{CaIdentity, CertificateStatus, StatusSnapshot},
225 };
226 use std::collections::BTreeMap;
227
228 let mut signer = demo_ecdsa_p256_key();
229 let seal_key = crate::SealKey::EcdsaP256(demo_ecdsa_p256_key());
230 let seal_cert = crate::generate_seal_cert_for_key(&seal_key).unwrap();
231
232 let ca = CaIdentity {
233 label: "test".into(),
234 issuer_name_der: b"CN=Test".to_vec(),
235 issuer_key_bytes: vec![0x42; 32],
236 };
237
238 let mut entries = BTreeMap::new();
239 entries.insert(vec![0x01], CertificateStatus::Good);
240 let snapshot = StatusSnapshot {
241 entries,
242 this_update: crate::source::unix_now().unwrap(),
243 next_update: Some(crate::source::unix_now().unwrap() + 86400),
244 ..Default::default()
245 };
246 let config = GenerationConfig {
247 producer_id: "test".into(),
248 epoch: 1,
249 ..Default::default()
250 };
251
252 let bundle_bytes = produce_bundle::<_, p256::ecdsa::DerSignature>(
253 &ca,
254 &snapshot,
255 &config,
256 &mut signer,
257 |m| crate::create_cms_seal(m, &seal_key, &seal_cert),
258 None,
259 )
260 .unwrap();
261
262 let bundle = ahu::Bundle::from_bytes(&bundle_bytes).unwrap();
263 for record in &bundle.index {
264 if let Some(entry_bytes) = bundle.entry_bytes(record) {
265 let result = verify_ocsp_response_signature(entry_bytes);
266 assert!(result.is_err());
267 assert!(matches!(
268 result.unwrap_err(),
269 crate::error::SignError::NoCert
270 ));
271 }
272 }
273 }
274}