Skip to main content

rama_crypto/
ocsp.rs

1//! Generic OCSP response builder (TLS-backend agnostic).
2//!
3//! Builds and DER-encodes an OCSP response asserting a single certificate's
4//! status, signed by its issuer. Hashing and signing are supplied *by the
5//! caller* (the TLS backend), so this module pulls in no crypto backend — it is
6//! pure ASN.1 assembly on the `yasna` DER writer already in the dependency tree.
7//!
8//! BoringSSL (and others) can *staple* a pre-built OCSP response on the server
9//! side but cannot *build* one — there is no responder/builder API. This is
10//! that builder, kept generic so every TLS backend (`rama-tls-boring`,
11//! `rama-tls-rustls`, …) can share it; only the cert/key/hash/sign glue lives
12//! in the backend crate.
13//!
14//! Primary use: a MITM proxy stapling an issuer-signed `good` status onto a
15//! re-signed leaf, so revocation-strict clients (e.g. cargo / schannel on
16//! Windows) accept it inline without an external responder.
17
18use std::time::{Duration, SystemTime};
19
20use rama_core::error::{BoxError, ErrorContext};
21use yasna::{
22    Tag,
23    models::{GeneralizedTime, ObjectIdentifier},
24};
25
26fn oid_sha1() -> ObjectIdentifier {
27    ObjectIdentifier::from_slice(&[1, 3, 14, 3, 2, 26])
28}
29fn oid_ecdsa_sha256() -> ObjectIdentifier {
30    ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2])
31}
32fn oid_rsa_sha256() -> ObjectIdentifier {
33    ObjectIdentifier::from_slice(&[1, 2, 840, 113549, 1, 1, 11])
34}
35fn oid_ocsp_basic() -> ObjectIdentifier {
36    ObjectIdentifier::from_slice(&[1, 3, 6, 1, 5, 5, 7, 48, 1, 1])
37}
38fn oid_ocsp_nonce() -> ObjectIdentifier {
39    ObjectIdentifier::from_slice(&[1, 3, 6, 1, 5, 5, 7, 48, 1, 2])
40}
41fn oid_ad_ocsp() -> ObjectIdentifier {
42    ObjectIdentifier::from_slice(&[1, 3, 6, 1, 5, 5, 7, 48, 1])
43}
44
45/// DER of an `AuthorityInfoAccessSyntax` extension value with a single
46/// `id-ad-ocsp` responder URI, for embedding as the `1.3.6.1.5.5.7.1.1`
47/// extension on a re-signed leaf.
48#[must_use]
49pub fn authority_info_access_ocsp_der(uri: &str) -> Vec<u8> {
50    yasna::construct_der(|w| {
51        w.write_sequence(|w| {
52            w.next().write_sequence(|w| {
53                w.next().write_oid(&oid_ad_ocsp());
54                // accessLocation: uniformResourceIdentifier [6] IA5String
55                w.next().write_tagged_implicit(Tag::context(6), |w| {
56                    w.write_bytes(uri.as_bytes());
57                });
58            });
59        });
60    })
61}
62
63/// DER of the `sha1` CertID `hashAlgorithm` (`AlgorithmIdentifier { sha1, NULL }`),
64/// for callers building a `CertID` without an inbound request to echo (e.g. a
65/// stapled `good` response).
66#[must_use]
67pub fn sha1_hash_algorithm_der() -> Vec<u8> {
68    yasna::construct_der(|w| {
69        w.write_sequence(|w| {
70            w.next().write_oid(&oid_sha1());
71            w.next().write_null();
72        });
73    })
74}
75
76/// Status to assert for the certificate.
77#[derive(Debug, Clone, Copy)]
78pub enum OcspCertStatus {
79    /// The certificate is valid.
80    Good,
81    /// The certificate is revoked (RFC 6960 `RevokedInfo`, reason omitted).
82    Revoked {
83        /// When the certificate was revoked.
84        revocation_time: SystemTime,
85    },
86}
87
88/// Signature algorithm the caller used to sign the `tbsResponseData`.
89#[derive(Debug, Clone, Copy)]
90pub enum OcspSignatureAlgorithm {
91    /// `ecdsa-with-SHA256` (1.2.840.10045.4.3.2) — parameters absent.
92    EcdsaSha256,
93    /// `sha256WithRSAEncryption` (1.2.840.113549.1.1.11) — NULL parameters.
94    RsaSha256,
95}
96
97/// Identifies the certificate whose status is attested (RFC 6960 `CertID`).
98///
99/// All fields are caller-computed so this crate needs no hash backend. When
100/// answering a request, set `hash_algorithm_der` to the request's verbatim
101/// `hashAlgorithm` and the hashes to the request's values, so the response
102/// `CertID` matches byte-for-byte; for a stapled response use
103/// [`sha1_hash_algorithm_der`] with SHA-1 hashes.
104#[derive(Debug, Clone, Copy)]
105pub struct OcspCertId<'a> {
106    /// DER of the issuer's subject `Name` (the full `SEQUENCE` TLV), used for
107    /// the `responderID` byName field.
108    pub issuer_name_der: &'a [u8],
109    /// The `hashAlgorithm` `AlgorithmIdentifier` as full DER.
110    pub hash_algorithm_der: &'a [u8],
111    /// Hash over the issuer `Name` (the `issuerNameHash`).
112    pub issuer_name_hash: &'a [u8],
113    /// Hash over the issuer's `subjectPublicKey` BIT STRING value
114    /// (the `issuerKeyHash`).
115    pub issuer_key_hash: &'a [u8],
116    /// The leaf's serial number as a big-endian unsigned magnitude.
117    pub serial: &'a [u8],
118}
119
120/// Build a DER-encoded `OCSPResponse` attesting `cert`'s `status`.
121///
122/// `sign_tbs` signs the `tbsResponseData` DER with the issuer key and reports
123/// which algorithm it used. `produced_at` sets `producedAt`/`thisUpdate`;
124/// `nextUpdate` = `produced_at + validity`.
125///
126/// The public surface takes only `std` time types — `time::OffsetDateTime` is
127/// an internal detail of the DER `GeneralizedTime` encoding.
128/// `nonce`, when set, is echoed verbatim as the `id-pkix-ocsp-nonce`
129/// `responseExtensions` value (the bytes a request's matching `extnValue`
130/// carried); pass the value [`parse_ocsp_request`] returned.
131pub fn build_ocsp_response(
132    cert: &OcspCertId<'_>,
133    status: OcspCertStatus,
134    produced_at: SystemTime,
135    validity: Duration,
136    nonce: Option<&[u8]>,
137    sign_tbs: impl FnOnce(&[u8]) -> Result<(OcspSignatureAlgorithm, Vec<u8>), BoxError>,
138) -> Result<Vec<u8>, BoxError> {
139    // certStatus: Good is [0] NULL; Revoked is [1] IMPLICIT RevokedInfo, whose
140    // revocationTime must be encoded before the (infallible) writer closure.
141    let revoked_at = match status {
142        OcspCertStatus::Good => None,
143        OcspCertStatus::Revoked { revocation_time } => Some(generalized_time(revocation_time)?),
144    };
145
146    // producedAt and thisUpdate are the same instant — encode it once.
147    let produced = generalized_time(produced_at)?;
148    let next_at = produced_at
149        .checked_add(validity)
150        .ok_or_else(|| BoxError::from("ocsp: nextUpdate overflow"))?;
151    let next_update = generalized_time(next_at)?;
152
153    // tbsResponseData (ResponseData) — exactly the bytes that get signed.
154    // Borrow `cert`'s slices straight into the writer; no intermediate copies.
155    let tbs_der = yasna::construct_der(|w| {
156        w.write_sequence(|w| {
157            // version [0] DEFAULT v1 — omitted.
158            // responderID ::= byName [1] EXPLICIT Name
159            w.next()
160                .write_tagged(Tag::context(1), |w| w.write_der(cert.issuer_name_der));
161            // producedAt
162            w.next().write_generalized_time(&produced);
163            // responses ::= SEQUENCE OF SingleResponse (one entry)
164            w.next().write_sequence(|w| {
165                w.next().write_sequence(|w| {
166                    // certID
167                    w.next().write_sequence(|w| {
168                        w.next().write_der(cert.hash_algorithm_der);
169                        w.next().write_bytes(cert.issuer_name_hash);
170                        w.next().write_bytes(cert.issuer_key_hash);
171                        w.next().write_bigint_bytes(cert.serial, true);
172                    });
173                    // certStatus ::= good [0] IMPLICIT NULL
174                    //              | revoked [1] IMPLICIT RevokedInfo { revocationTime }
175                    match &revoked_at {
176                        None => {
177                            w.next()
178                                .write_tagged_implicit(Tag::context(0), |w| w.write_null());
179                        }
180                        Some(revoked_at) => {
181                            w.next().write_tagged_implicit(Tag::context(1), |w| {
182                                w.write_sequence(|w| {
183                                    w.next().write_generalized_time(revoked_at);
184                                });
185                            });
186                        }
187                    }
188                    // thisUpdate (same instant as producedAt)
189                    w.next().write_generalized_time(&produced);
190                    // nextUpdate [0] EXPLICIT GeneralizedTime
191                    w.next()
192                        .write_tagged(Tag::context(0), |w| w.write_generalized_time(&next_update));
193                });
194            });
195            // responseExtensions [1] EXPLICIT Extensions — nonce echo only.
196            if let Some(nonce) = nonce {
197                w.next().write_tagged(Tag::context(1), |w| {
198                    w.write_sequence(|w| {
199                        w.next().write_sequence(|w| {
200                            w.next().write_oid(&oid_ocsp_nonce());
201                            w.next().write_bytes(nonce);
202                        });
203                    });
204                });
205            }
206        });
207    });
208
209    let (alg, signature) = sign_tbs(&tbs_der)?;
210
211    // BasicOCSPResponse
212    let basic_der = yasna::construct_der(|w| {
213        w.write_sequence(|w| {
214            w.next().write_der(&tbs_der);
215            // signatureAlgorithm
216            w.next().write_sequence(|w| match alg {
217                OcspSignatureAlgorithm::EcdsaSha256 => {
218                    w.next().write_oid(&oid_ecdsa_sha256());
219                }
220                OcspSignatureAlgorithm::RsaSha256 => {
221                    w.next().write_oid(&oid_rsa_sha256());
222                    w.next().write_null();
223                }
224            });
225            // signature BIT STRING (0 unused bits)
226            w.next().write_bitvec_bytes(&signature, signature.len() * 8);
227        });
228    });
229
230    // OCSPResponse
231    let resp_der = yasna::construct_der(|w| {
232        w.write_sequence(|w| {
233            // responseStatus = successful (0)
234            w.next().write_enum(0);
235            // responseBytes [0] EXPLICIT ResponseBytes
236            w.next().write_tagged(Tag::context(0), |w| {
237                w.write_sequence(|w| {
238                    w.next().write_oid(&oid_ocsp_basic());
239                    w.next().write_bytes(&basic_der);
240                });
241            });
242        });
243    });
244
245    Ok(resp_der)
246}
247
248/// A single `CertID` extracted from an OCSP request.
249#[derive(Debug, Clone)]
250pub struct OcspRequestCertId {
251    /// The `hashAlgorithm` `AlgorithmIdentifier` as full DER, to echo verbatim
252    /// so the response `CertID` matches the request.
253    pub hash_algorithm_der: Vec<u8>,
254    /// `issuerNameHash` value.
255    pub issuer_name_hash: Vec<u8>,
256    /// `issuerKeyHash` value.
257    pub issuer_key_hash: Vec<u8>,
258    /// `serialNumber` as a big-endian unsigned magnitude.
259    pub serial: Vec<u8>,
260}
261
262/// A parsed OCSP request: the requested `CertID`s and the optional nonce.
263#[derive(Debug, Clone, Default)]
264pub struct OcspRequestInfo {
265    /// One entry per `Request` in the `requestList`.
266    pub certs: Vec<OcspRequestCertId>,
267    /// `id-pkix-ocsp-nonce` `extnValue` contents, for verbatim echo via
268    /// [`build_ocsp_response`].
269    pub nonce: Option<Vec<u8>>,
270}
271
272/// Parse a DER `OCSPRequest` (RFC 6960 §4.1.1).
273///
274/// Returns each requested `CertID` and the optional nonce. `version`,
275/// `requestorName`, the optional signature and per-request extensions are
276/// accepted but ignored.
277pub fn parse_ocsp_request(der: &[u8]) -> Result<OcspRequestInfo, BoxError> {
278    yasna::parse_der(der, |r| {
279        r.read_sequence(|r| {
280            let info = r.next().read_sequence(|r| {
281                // version [0] EXPLICIT DEFAULT v1
282                r.read_optional(|r| r.read_tagged(Tag::context(0), |r| r.read_i64()))?;
283                // requestorName [1] EXPLICIT GeneralName
284                r.read_optional(|r| r.read_tagged(Tag::context(1), |r| r.read_der()))?;
285                // requestList ::= SEQUENCE OF Request
286                let mut certs = Vec::new();
287                r.next().read_sequence_of(|r| {
288                    r.read_sequence(|r| {
289                        let cert = r.next().read_sequence(|r| {
290                            let hash_algorithm_der = r.next().read_der()?;
291                            let issuer_name_hash = r.next().read_bytes()?;
292                            let issuer_key_hash = r.next().read_bytes()?;
293                            let (serial, _positive) = r.next().read_bigint_bytes()?;
294                            // DER prepends a 0x00 sign byte to a positive integer
295                            // whose MSB is set; strip it so `serial` is the unsigned
296                            // magnitude (matching ledger serials and the echoed CertID).
297                            let serial = match serial.split_first() {
298                                Some((0x00, rest)) if !rest.is_empty() => rest.to_vec(),
299                                _ => serial,
300                            };
301                            Ok(OcspRequestCertId {
302                                hash_algorithm_der,
303                                issuer_name_hash,
304                                issuer_key_hash,
305                                serial,
306                            })
307                        })?;
308                        // singleRequestExtensions [0] EXPLICIT
309                        r.read_optional(|r| r.read_tagged(Tag::context(0), |r| r.read_der()))?;
310                        certs.push(cert);
311                        Ok(())
312                    })
313                })?;
314                // requestExtensions [2] EXPLICIT Extensions
315                let nonce = r
316                    .read_optional(|r| {
317                        r.read_tagged(Tag::context(2), |r| {
318                            let mut nonce = None;
319                            r.read_sequence_of(|r| {
320                                r.read_sequence(|r| {
321                                    let oid = r.next().read_oid()?;
322                                    r.read_optional(|r| r.read_bool())?;
323                                    let value = r.next().read_bytes()?;
324                                    if oid == oid_ocsp_nonce() {
325                                        nonce = Some(value);
326                                    }
327                                    Ok(())
328                                })
329                            })?;
330                            Ok(nonce)
331                        })
332                    })?
333                    .flatten();
334                Ok(OcspRequestInfo { certs, nonce })
335            })?;
336            // optionalSignature [0] EXPLICIT
337            r.read_optional(|r| r.read_tagged(Tag::context(0), |r| r.read_der()))?;
338            Ok(info)
339        })
340    })
341    .map_err(|e| BoxError::from(format!("ocsp: parse request: {e}")))
342}
343
344/// Convert a `SystemTime` to a DER `GeneralizedTime`. `time::OffsetDateTime` is
345/// used only here (internal); it never appears in the public API.
346fn generalized_time(t: SystemTime) -> Result<GeneralizedTime, BoxError> {
347    let secs = t
348        .duration_since(SystemTime::UNIX_EPOCH)
349        .context("ocsp: timestamp before unix epoch")?
350        .as_secs();
351    let odt = time::OffsetDateTime::from_unix_timestamp(secs as i64)
352        .map_err(|e| BoxError::from(format!("ocsp: invalid timestamp: {e}")))?;
353    Ok(GeneralizedTime::from_datetime(odt))
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    /// The builder emits a well-formed `OCSPResponse`: `successful` status,
361    /// `id-pkix-ocsp-basic` responseBytes wrapping a 3-element
362    /// BasicOCSPResponse, and it feeds the caller a non-empty `tbsResponseData`
363    /// to sign. Parsed back with yasna (no extra deps).
364    #[test]
365    fn builds_wellformed_ocsp_response() {
366        let cert = OcspCertId {
367            issuer_name_der: &yasna::construct_der(|w| {
368                // a minimal Name: SEQUENCE {} (empty RDNSequence) — enough for shape.
369                w.write_sequence(|_| {});
370            }),
371            hash_algorithm_der: &sha1_hash_algorithm_der(),
372            issuer_name_hash: &[0xAA; 20],
373            issuer_key_hash: &[0xBB; 20],
374            serial: &[0x12, 0x34, 0x56],
375        };
376
377        let mut signed_tbs: Vec<u8> = Vec::new();
378        let der = build_ocsp_response(
379            &cert,
380            OcspCertStatus::Good,
381            SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
382            Duration::from_hours(24 * 7),
383            None,
384            |tbs| {
385                signed_tbs = tbs.to_vec();
386                Ok((
387                    OcspSignatureAlgorithm::EcdsaSha256,
388                    vec![0xDE, 0xAD, 0xBE, 0xEF],
389                ))
390            },
391        )
392        .expect("build ocsp response");
393
394        assert!(
395            !signed_tbs.is_empty(),
396            "tbsResponseData was handed to the signer"
397        );
398
399        // Parse the outer OCSPResponse: SEQUENCE { ENUMERATED, [0] { OID, OCTET STRING } }.
400        let basic_der = yasna::parse_der(&der, |r| {
401            r.read_sequence(|r| {
402                let status = r.next().read_enum()?;
403                assert_eq!(status, 0, "responseStatus successful");
404                r.next().read_tagged(Tag::context(0), |r| {
405                    r.read_sequence(|r| {
406                        let oid = r.next().read_oid()?;
407                        assert_eq!(oid, oid_ocsp_basic(), "responseType id-pkix-ocsp-basic");
408                        r.next().read_bytes()
409                    })
410                })
411            })
412        })
413        .expect("parse OCSPResponse");
414
415        // Inner BasicOCSPResponse: SEQUENCE { tbs, sigAlg, signature }.
416        yasna::parse_der(&basic_der, |r| {
417            r.read_sequence(|r| {
418                // tbsResponseData round-trips byte-for-byte with what we signed.
419                let tbs = r.next().read_der()?;
420                assert_eq!(tbs, signed_tbs, "embedded tbs == signed tbs");
421                // signatureAlgorithm
422                r.next().read_sequence(|r| {
423                    let oid = r.next().read_oid()?;
424                    assert_eq!(oid, oid_ecdsa_sha256());
425                    Ok(())
426                })?;
427                // signature BIT STRING
428                let (sig, _bits) = r.next().read_bitvec_bytes()?;
429                assert_eq!(sig, vec![0xDE, 0xAD, 0xBE, 0xEF]);
430                Ok(())
431            })
432        })
433        .expect("parse BasicOCSPResponse");
434    }
435
436    /// SHA-256 CertID `hashAlgorithm` `AlgorithmIdentifier` (params absent).
437    fn sha256_hash_algorithm_der() -> Vec<u8> {
438        yasna::construct_der(|w| {
439            w.write_sequence(|w| {
440                w.next().write_oid(&ObjectIdentifier::from_slice(&[
441                    2, 16, 840, 1, 101, 3, 4, 2, 1,
442                ]));
443            });
444        })
445    }
446
447    /// Build a minimal `OCSPRequest` with one `CertID` (using `algid` as the
448    /// `hashAlgorithm`), optionally a version field and a nonce extension.
449    fn build_request(version: bool, nonce: Option<&[u8]>, algid: &[u8]) -> Vec<u8> {
450        yasna::construct_der(|w| {
451            w.write_sequence(|w| {
452                w.next().write_sequence(|w| {
453                    if version {
454                        w.next().write_tagged(Tag::context(0), |w| w.write_i64(0));
455                    }
456                    w.next().write_sequence(|w| {
457                        w.next().write_sequence(|w| {
458                            w.next().write_sequence(|w| {
459                                w.next().write_der(algid);
460                                w.next().write_bytes(&[0xAA; 20]);
461                                w.next().write_bytes(&[0xBB; 20]);
462                                w.next().write_bigint_bytes(&[0x12, 0x34, 0x56], true);
463                            });
464                        });
465                    });
466                    if let Some(nonce) = nonce {
467                        w.next().write_tagged(Tag::context(2), |w| {
468                            w.write_sequence(|w| {
469                                w.next().write_sequence(|w| {
470                                    w.next().write_oid(&oid_ocsp_nonce());
471                                    w.next().write_bytes(nonce);
472                                });
473                            });
474                        });
475                    }
476                });
477            });
478        })
479    }
480
481    fn contains(haystack: &[u8], needle: &[u8]) -> bool {
482        haystack.windows(needle.len()).any(|w| w == needle)
483    }
484
485    #[test]
486    fn parses_minimal_request() {
487        let info = parse_ocsp_request(&build_request(false, None, &sha1_hash_algorithm_der()))
488            .expect("parse");
489        assert_eq!(info.certs.len(), 1);
490        let c = &info.certs[0];
491        assert_eq!(c.hash_algorithm_der, sha1_hash_algorithm_der());
492        assert_eq!(c.issuer_name_hash, vec![0xAA; 20]);
493        assert_eq!(c.issuer_key_hash, vec![0xBB; 20]);
494        assert_eq!(c.serial, vec![0x12, 0x34, 0x56]);
495        assert!(info.nonce.is_none());
496    }
497
498    #[test]
499    fn parses_request_with_version_and_nonce() {
500        let nonce_value = yasna::construct_der(|w| w.write_bytes(&[1, 2, 3, 4, 5, 6, 7, 8]));
501        let info = parse_ocsp_request(&build_request(
502            true,
503            Some(&nonce_value),
504            &sha1_hash_algorithm_der(),
505        ))
506        .expect("parse");
507        assert_eq!(info.certs.len(), 1);
508        assert_eq!(info.nonce.as_deref(), Some(nonce_value.as_slice()));
509    }
510
511    /// A serial whose MSB is set (DER adds a 0x00 sign byte) parses back to its
512    /// unsigned magnitude — so it matches a ledger serial / the CertID we echo.
513    #[test]
514    fn parses_msb_set_serial_as_unsigned_magnitude() {
515        let algid = sha1_hash_algorithm_der();
516        let req = yasna::construct_der(|w| {
517            w.write_sequence(|w| {
518                w.next().write_sequence(|w| {
519                    w.next().write_sequence(|w| {
520                        w.next().write_sequence(|w| {
521                            w.next().write_sequence(|w| {
522                                w.next().write_der(&algid);
523                                w.next().write_bytes(&[0xAA; 20]);
524                                w.next().write_bytes(&[0xBB; 20]);
525                                w.next().write_bigint_bytes(&[0xDE, 0xAD, 0xBE, 0xEF], true);
526                            });
527                        });
528                    });
529                });
530            });
531        });
532        let info = parse_ocsp_request(&req).expect("parse");
533        assert_eq!(
534            info.certs[0].serial,
535            vec![0xDE, 0xAD, 0xBE, 0xEF],
536            "leading 0x00 sign byte stripped"
537        );
538    }
539
540    /// A response built from a parsed request echoes the request's `CertID`
541    /// (hashes + serial) and nonce verbatim.
542    #[test]
543    fn response_echoes_request_certid_and_nonce() {
544        let nonce_value = yasna::construct_der(|w| w.write_bytes(&[9, 9, 9, 9]));
545        let info = parse_ocsp_request(&build_request(
546            true,
547            Some(&nonce_value),
548            &sha1_hash_algorithm_der(),
549        ))
550        .expect("parse");
551        let c = &info.certs[0];
552        let issuer = yasna::construct_der(|w| w.write_sequence(|_| {}));
553        let cert = OcspCertId {
554            issuer_name_der: &issuer,
555            hash_algorithm_der: &c.hash_algorithm_der,
556            issuer_name_hash: &c.issuer_name_hash,
557            issuer_key_hash: &c.issuer_key_hash,
558            serial: &c.serial,
559        };
560        let der = build_ocsp_response(
561            &cert,
562            OcspCertStatus::Good,
563            SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
564            Duration::from_hours(24),
565            info.nonce.as_deref(),
566            |_| Ok((OcspSignatureAlgorithm::EcdsaSha256, vec![0x00])),
567        )
568        .expect("build ocsp response");
569
570        assert!(contains(&der, &[0xAA; 20]), "issuerNameHash echoed");
571        assert!(contains(&der, &[0xBB; 20]), "issuerKeyHash echoed");
572        assert!(contains(&der, &[0x12, 0x34, 0x56]), "serial echoed");
573        assert!(contains(&der, &nonce_value), "nonce echoed");
574    }
575
576    /// The first byte of the `certStatus` TLV in a built response: `[0]` (0x80)
577    /// for good, `[1]` (0xA1) for revoked.
578    fn cert_status_first_byte(response_der: &[u8]) -> u8 {
579        let basic = yasna::parse_der(response_der, |r| {
580            r.read_sequence(|r| {
581                let _status = r.next().read_enum()?;
582                r.next().read_tagged(Tag::context(0), |r| {
583                    r.read_sequence(|r| {
584                        let _oid = r.next().read_oid()?;
585                        r.next().read_bytes()
586                    })
587                })
588            })
589        })
590        .expect("parse OCSPResponse");
591        yasna::parse_der(&basic, |r| {
592            r.read_sequence(|r| {
593                let tbs = r.next().read_der()?;
594                let _alg = r.next().read_der()?;
595                let _sig = r.next().read_bitvec_bytes()?;
596                yasna::parse_der(&tbs, |r| {
597                    r.read_sequence(|r| {
598                        let _responder = r.next().read_der()?;
599                        let _produced = r.next().read_der()?;
600                        r.next().read_sequence(|r| {
601                            r.next().read_sequence(|r| {
602                                let _cert_id = r.next().read_der()?;
603                                let cert_status = r.next().read_der()?;
604                                let _this = r.next().read_der()?;
605                                let _next = r.next().read_der()?;
606                                Ok(cert_status[0])
607                            })
608                        })
609                    })
610                })
611            })
612        })
613        .expect("parse BasicOCSPResponse")
614    }
615
616    fn good_or_revoked(status: OcspCertStatus) -> u8 {
617        let cert = OcspCertId {
618            issuer_name_der: &yasna::construct_der(|w| w.write_sequence(|_| {})),
619            hash_algorithm_der: &sha1_hash_algorithm_der(),
620            issuer_name_hash: &[0xAA; 20],
621            issuer_key_hash: &[0xBB; 20],
622            serial: &[0x12, 0x34, 0x56],
623        };
624        let der = build_ocsp_response(
625            &cert,
626            status,
627            SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
628            Duration::from_hours(24),
629            None,
630            |_| Ok((OcspSignatureAlgorithm::EcdsaSha256, vec![0x00])),
631        )
632        .expect("build ocsp response");
633        cert_status_first_byte(&der)
634    }
635
636    /// `Good` encodes `certStatus` as `[0]`, `Revoked` as `[1]`.
637    #[test]
638    fn revoked_status_encodes_as_context_1() {
639        assert_eq!(good_or_revoked(OcspCertStatus::Good), 0x80, "good is [0]");
640        assert_eq!(
641            good_or_revoked(OcspCertStatus::Revoked {
642                revocation_time: SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000),
643            }),
644            0xA1,
645            "revoked is [1] IMPLICIT RevokedInfo"
646        );
647    }
648
649    /// A non-SHA-1 CertID `hashAlgorithm` is echoed verbatim into the response.
650    #[test]
651    fn echoes_sha256_cert_id_hash_algorithm() {
652        let sha256 = sha256_hash_algorithm_der();
653        let info = parse_ocsp_request(&build_request(false, None, &sha256)).expect("parse");
654        let c = &info.certs[0];
655        assert_eq!(c.hash_algorithm_der, sha256, "parsed verbatim");
656        let issuer = yasna::construct_der(|w| w.write_sequence(|_| {}));
657        let cert = OcspCertId {
658            issuer_name_der: &issuer,
659            hash_algorithm_der: &c.hash_algorithm_der,
660            issuer_name_hash: &c.issuer_name_hash,
661            issuer_key_hash: &c.issuer_key_hash,
662            serial: &c.serial,
663        };
664        let der = build_ocsp_response(
665            &cert,
666            OcspCertStatus::Good,
667            SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
668            Duration::from_hours(24),
669            None,
670            |_| Ok((OcspSignatureAlgorithm::EcdsaSha256, vec![0x00])),
671        )
672        .expect("build ocsp response");
673        assert!(contains(&der, &sha256), "sha256 hashAlgorithm echoed");
674    }
675
676    #[test]
677    fn aia_ocsp_carries_the_responder_uri() {
678        let uri = "http://127.0.0.1:9999/ocsp/abc";
679        let der = authority_info_access_ocsp_der(uri);
680        let oid = yasna::parse_der(&der, |r| {
681            r.read_sequence(|r| {
682                r.next().read_sequence(|r| {
683                    let oid = r.next().read_oid()?;
684                    let _loc = r.next().read_der()?;
685                    Ok(oid)
686                })
687            })
688        })
689        .expect("AIA structure");
690        assert_eq!(oid, oid_ad_ocsp());
691        assert!(contains(&der, uri.as_bytes()), "responder URI present");
692    }
693}