1use der::asn1::OctetString;
9use der::{Decode, Encode};
10use sha1::Sha1;
11use sha2::Digest;
12use signature::Signer;
13use spki::{DynSignatureAlgorithmIdentifier, SignatureBitStringEncoding};
14use x509_ocsp::builder::OcspResponseBuilder;
15use x509_ocsp::{
16 BasicOcspResponse, CertId, CertStatus, OcspGeneralizedTime, OcspResponse, ResponderId,
17 SingleResponse, ext::Nonce,
18};
19
20use crate::error::{Result, SignError};
21use crate::generate::ocsp_time;
22use x509_cert::Certificate as CertificateForLive;
23
24#[derive(Debug, Clone)]
26pub enum LiveCertStatus {
27 Good,
28 Unknown,
29 Revoked {
30 revocation_time: u64,
31 reason: Option<x509_cert::ext::pkix::CrlReason>,
32 },
33}
34
35#[allow(clippy::too_many_arguments)]
41pub fn sign_live_response<S, Sig>(
42 cert_id_der: &[u8],
43 status: LiveCertStatus,
44 nonce_bytes: &[u8],
45 responder_key_bytes: &[u8],
46 signer: &mut S,
47 now: u64,
48 validity_secs: u64,
49 responder_cert_der: Option<&[u8]>,
50) -> Result<Vec<u8>>
51where
52 S: Signer<Sig> + DynSignatureAlgorithmIdentifier,
53 Sig: SignatureBitStringEncoding,
54{
55 let next = now
56 .checked_add(validity_secs)
57 .ok_or_else(|| SignError::Config("live validity overflow".into()))?;
58 sign_live_response_with_window(
59 cert_id_der,
60 status,
61 nonce_bytes,
62 responder_key_bytes,
63 signer,
64 now,
65 now,
66 next,
67 responder_cert_der,
68 )
69}
70
71#[allow(clippy::too_many_arguments)]
74pub fn sign_live_response_with_window<S, Sig>(
75 cert_id_der: &[u8],
76 status: LiveCertStatus,
77 nonce_bytes: &[u8],
78 responder_key_bytes: &[u8],
79 signer: &mut S,
80 now: u64,
81 source_this_update: u64,
82 source_next_update: u64,
83 responder_cert_der: Option<&[u8]>,
84) -> Result<Vec<u8>>
85where
86 S: Signer<Sig> + DynSignatureAlgorithmIdentifier,
87 Sig: SignatureBitStringEncoding,
88{
89 if source_this_update > now
90 || source_next_update <= now
91 || source_next_update <= source_this_update
92 {
93 return Err(SignError::Config(
94 "live source is not currently valid".into(),
95 ));
96 }
97 let mut next_update_epoch = source_next_update;
98 if let Some(bytes) = responder_cert_der {
99 let cert = CertificateForLive::from_der(bytes).map_err(SignError::Der)?;
100 let validity = &cert.tbs_certificate.validity;
101 let before = validity.not_before.to_unix_duration().as_secs();
102 let after = validity.not_after.to_unix_duration().as_secs();
103 if now < before || now >= after {
104 return Err(SignError::Config(
105 "responder certificate is not currently valid".into(),
106 ));
107 }
108 next_update_epoch = next_update_epoch.min(after);
109 }
110 let cert_id = CertId::from_der(cert_id_der).map_err(SignError::Der)?;
111
112 let cert_status = match status {
113 LiveCertStatus::Good => CertStatus::good(),
114 LiveCertStatus::Unknown => CertStatus::unknown(),
115 LiveCertStatus::Revoked {
116 revocation_time,
117 reason,
118 } => {
119 let revoked_info = x509_ocsp::RevokedInfo {
120 revocation_time: ocsp_time(revocation_time)?,
121 revocation_reason: reason,
122 };
123 CertStatus::revoked(revoked_info)
124 }
125 };
126
127 let this_update = ocsp_time(source_this_update)?;
128 let next_update = ocsp_time(next_update_epoch)?;
129 let produced_at = ocsp_time(now)?;
130
131 let single =
132 SingleResponse::new(cert_id, cert_status, this_update).with_next_update(next_update);
133
134 let responder_key_hash = Sha1::digest(responder_key_bytes);
135 let responder_id =
136 ResponderId::ByKey(OctetString::new(responder_key_hash.to_vec()).map_err(SignError::Der)?);
137
138 let nonce = Nonce::new(nonce_bytes.to_vec()).map_err(SignError::Der)?;
139
140 let builder = OcspResponseBuilder::new(responder_id)
141 .with_single_response(single)
142 .with_extension(nonce)
143 .map_err(|e| SignError::OcspBuilder(e.to_string()))?;
144
145 let certs = responder_cert_der
146 .map(|c| {
147 let cert = x509_cert::Certificate::from_der(c).map_err(SignError::Der)?;
148 Ok::<_, SignError>(vec![cert])
149 })
150 .transpose()?;
151 let ocsp_response = builder
152 .sign(signer, certs, produced_at)
153 .map_err(SignError::from)?;
154
155 ocsp_response.to_der().map_err(SignError::Der)
156}
157
158pub struct LiveSigningMaterial {
160 pub key: p256::ecdsa::SigningKey,
161 pub responder_key_bytes: Vec<u8>,
162 pub responder_cert_der: Option<Vec<u8>>,
163}
164
165pub fn load_live_material(
166 ca: &hoike_core::config::CaConfig,
167) -> std::result::Result<LiveSigningMaterial, String> {
168 use hoike_core::config::SigningKeyConfig;
169 let key = match &ca.signing_key {
170 Some(SigningKeyConfig::File { path }) => {
171 crate::load_ecdsa_p256_key(path).map_err(|e| e.to_string())?
172 }
173 Some(SigningKeyConfig::Demo) => crate::demo_ecdsa_p256_key(),
174 _ => return Err("live signing requires an ECDSA file or explicit demo key".into()),
175 };
176 let cert_der = crate::orchestrate::load_responder_cert(ca)?;
177 let responder_key_bytes = if let Some(bytes) = &cert_der {
178 let cert = CertificateForLive::from_der(bytes).map_err(|e| e.to_string())?;
179 let now = std::time::SystemTime::now()
180 .duration_since(std::time::UNIX_EPOCH)
181 .map_err(|e| e.to_string())?
182 .as_secs();
183 if now
184 < cert
185 .tbs_certificate
186 .validity
187 .not_before
188 .to_unix_duration()
189 .as_secs()
190 || now
191 >= cert
192 .tbs_certificate
193 .validity
194 .not_after
195 .to_unix_duration()
196 .as_secs()
197 {
198 return Err("live responder certificate is outside its validity period".into());
199 }
200 let bytes = cert
201 .tbs_certificate
202 .subject_public_key_info
203 .subject_public_key
204 .as_bytes()
205 .ok_or_else(|| "responder public key has unused bits".to_string())?;
206 let certificate_key =
207 p256::ecdsa::VerifyingKey::from_sec1_bytes(bytes).map_err(|e| e.to_string())?;
208 if certificate_key != *key.verifying_key() {
209 return Err("live signing key does not match responder certificate".into());
210 }
211 bytes.to_vec()
212 } else {
213 let bytes = crate::orchestrate::decode_issuer_key(ca)?;
214 if !matches!(ca.signing_key, Some(SigningKeyConfig::Demo)) {
215 let issuer_key = p256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes)
216 .map_err(|e| format!("CA-direct issuer key: {e}"))?;
217 if issuer_key != *key.verifying_key() {
218 return Err("CA-direct signing key does not match issuer key".into());
219 }
220 }
221 bytes
222 };
223 Ok(LiveSigningMaterial {
224 key,
225 responder_key_bytes,
226 responder_cert_der: cert_der,
227 })
228}
229
230pub struct LiveResponseSource {
232 pub status: LiveCertStatus,
233 pub this_update: u64,
234 pub next_update: u64,
235}
236
237pub fn extract_status_for_cert(
238 response_der: &[u8],
239 cert_id_der: &[u8],
240) -> Result<LiveResponseSource> {
241 let requested = CertId::from_der(cert_id_der).map_err(SignError::Der)?;
242 let basic = parse_basic(response_der)?;
243 let mut matches = basic
244 .tbs_response_data
245 .responses
246 .iter()
247 .filter(|r| r.cert_id == requested);
248 let single = matches
249 .next()
250 .ok_or_else(|| SignError::OcspBuilder("requested CertID absent from response".into()))?;
251 if matches.next().is_some() {
252 return Err(SignError::OcspBuilder(
253 "ambiguous duplicate CertID in response".into(),
254 ));
255 }
256 let next = single
257 .next_update
258 .as_ref()
259 .ok_or_else(|| SignError::OcspBuilder("live source requires nextUpdate".into()))?;
260 Ok(LiveResponseSource {
261 status: status_from_single(single),
262 this_update: generalized_time_to_epoch(&single.this_update),
263 next_update: generalized_time_to_epoch(next),
264 })
265}
266
267fn parse_basic(response_der: &[u8]) -> Result<BasicOcspResponse> {
268 let response = OcspResponse::from_der(response_der).map_err(SignError::Der)?;
269 if response.response_status != x509_ocsp::OcspResponseStatus::Successful {
270 return Err(SignError::OcspBuilder(
271 "source OCSP response was not successful".into(),
272 ));
273 }
274 let bytes = response
275 .response_bytes
276 .ok_or_else(|| SignError::OcspBuilder("missing responseBytes".into()))?;
277 if bytes.response_type.to_string() != "1.3.6.1.5.5.7.48.1.1" {
278 return Err(SignError::OcspBuilder("unsupported response type".into()));
279 }
280 BasicOcspResponse::from_der(bytes.response.as_bytes()).map_err(SignError::Der)
281}
282
283fn status_from_single(single: &SingleResponse) -> LiveCertStatus {
284 match &single.cert_status {
285 CertStatus::Good(_) => LiveCertStatus::Good,
286 CertStatus::Unknown(_) => LiveCertStatus::Unknown,
287 CertStatus::Revoked(info) => LiveCertStatus::Revoked {
288 revocation_time: generalized_time_to_epoch(&info.revocation_time),
289 reason: info.revocation_reason,
290 },
291 }
292}
293
294pub fn extract_status_from_response(response_der: &[u8]) -> Result<LiveCertStatus> {
296 let basic = parse_basic(response_der)?;
297 if basic.tbs_response_data.responses.len() != 1 {
298 return Err(SignError::OcspBuilder(
299 "CertID required for a batched response".into(),
300 ));
301 }
302 Ok(status_from_single(&basic.tbs_response_data.responses[0]))
303}
304
305fn generalized_time_to_epoch(gt: &OcspGeneralizedTime) -> u64 {
306 gt.0.to_unix_duration().as_secs()
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use der::Encode;
313 use p256::ecdsa::SigningKey;
314
315 fn test_key() -> SigningKey {
316 SigningKey::from_bytes((&[1u8; 32]).into()).unwrap()
317 }
318
319 #[test]
320 fn sign_live_good_with_nonce() {
321 let sha256_oid = const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
322 let cert_id = CertId {
323 hash_algorithm: spki::AlgorithmIdentifierOwned {
324 oid: sha256_oid,
325 parameters: Some(der::asn1::Null.into()),
326 },
327 issuer_name_hash: OctetString::new(vec![0xAA; 32]).unwrap(),
328 issuer_key_hash: OctetString::new(vec![0xBB; 32]).unwrap(),
329 serial_number: x509_cert::serial_number::SerialNumber::new(&[42u8]).unwrap(),
330 };
331 let cert_id_der = cert_id.to_der().unwrap();
332
333 let nonce = vec![
334 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
335 0x0F, 0x10,
336 ];
337
338 let mut key = test_key();
339 let resp_der = sign_live_response::<_, p256::ecdsa::DerSignature>(
340 &cert_id_der,
341 LiveCertStatus::Good,
342 &nonce,
343 &[0xBB; 32],
344 &mut key,
345 1700000000,
346 86400,
347 None,
348 )
349 .unwrap();
350
351 let resp = OcspResponse::from_der(&resp_der).unwrap();
352 assert_eq!(
353 resp.response_status,
354 x509_ocsp::OcspResponseStatus::Successful
355 );
356
357 let basic =
358 BasicOcspResponse::from_der(resp.response_bytes.unwrap().response.as_bytes()).unwrap();
359
360 let resp_nonce = basic.nonce().expect("response should contain nonce");
362 assert_eq!(resp_nonce.0.as_bytes(), &nonce);
363 }
364
365 #[test]
366 fn sign_live_revoked_with_nonce() {
367 let sha256_oid = const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
368 let cert_id = CertId {
369 hash_algorithm: spki::AlgorithmIdentifierOwned {
370 oid: sha256_oid,
371 parameters: Some(der::asn1::Null.into()),
372 },
373 issuer_name_hash: OctetString::new(vec![0xAA; 32]).unwrap(),
374 issuer_key_hash: OctetString::new(vec![0xBB; 32]).unwrap(),
375 serial_number: x509_cert::serial_number::SerialNumber::new(&[100u8]).unwrap(),
376 };
377 let cert_id_der = cert_id.to_der().unwrap();
378
379 let nonce = vec![0xAA; 16];
380
381 let mut key = test_key();
382 let resp_der = sign_live_response::<_, p256::ecdsa::DerSignature>(
383 &cert_id_der,
384 LiveCertStatus::Revoked {
385 revocation_time: 1699900000,
386 reason: Some(x509_cert::ext::pkix::CrlReason::KeyCompromise),
387 },
388 &nonce,
389 &[0xBB; 32],
390 &mut key,
391 1700000000,
392 86400,
393 None,
394 )
395 .unwrap();
396
397 let resp = OcspResponse::from_der(&resp_der).unwrap();
398 assert_eq!(
399 resp.response_status,
400 x509_ocsp::OcspResponseStatus::Successful
401 );
402 }
403
404 #[test]
405 fn extract_status_round_trip() {
406 let sha256_oid = const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
407 let cert_id = CertId {
408 hash_algorithm: spki::AlgorithmIdentifierOwned {
409 oid: sha256_oid,
410 parameters: Some(der::asn1::Null.into()),
411 },
412 issuer_name_hash: OctetString::new(vec![0xAA; 32]).unwrap(),
413 issuer_key_hash: OctetString::new(vec![0xBB; 32]).unwrap(),
414 serial_number: x509_cert::serial_number::SerialNumber::new(&[42u8]).unwrap(),
415 };
416 let cert_id_der = cert_id.to_der().unwrap();
417
418 let mut key = test_key();
419 let resp_der = sign_live_response::<_, p256::ecdsa::DerSignature>(
420 &cert_id_der,
421 LiveCertStatus::Good,
422 &[0xFF; 16],
423 &[0xBB; 32],
424 &mut key,
425 1700000000,
426 86400,
427 None,
428 )
429 .unwrap();
430
431 let extracted = extract_status_from_response(&resp_der).unwrap();
432 assert!(matches!(extracted, LiveCertStatus::Good));
433 }
434}