//! X.509 v2 Certificate Revocation Lists (RFC 5280 §5).
//!
//! A CRL is a signed list of revoked certificate serials, scoped to one
//! issuer. The signer is the same authority that issued the certificates
//! being revoked. `purecrypto`'s `tls::pki` chain validator consults a
//! [`crate::tls::pki::CrlStore`] of these objects to short-circuit any
//! signed-by-the-anchor certificate that appears in a covering CRL.
//!
//! The wire format (RFC 5280 §5.1):
//!
//! ```text
//! CertificateList  ::=  SEQUENCE {
//!     tbsCertList          TBSCertList,
//!     signatureAlgorithm   AlgorithmIdentifier,
//!     signatureValue       BIT STRING
//! }
//!
//! TBSCertList  ::=  SEQUENCE {
//!     version              Version OPTIONAL,         -- v2 = 1 here
//!     signature            AlgorithmIdentifier,      -- inner; MUST equal outer
//!     issuer               Name,
//!     thisUpdate           Time,
//!     nextUpdate           Time OPTIONAL,
//!     revokedCertificates  SEQUENCE OF SEQUENCE {
//!         userCertificate     CertificateSerialNumber,  -- INTEGER
//!         revocationDate      Time,
//!         crlEntryExtensions  Extensions OPTIONAL
//!     } OPTIONAL,
//!     crlExtensions        [0] EXPLICIT Extensions OPTIONAL
//! }
//! ```
//!
//! See [`CertificateRevocationList`] and [`CrlBuilder`] for the read/write APIs.

use alloc::string::String;
use alloc::vec::Vec;

use super::time::read_time;
use super::{AnyPublicKey, CertSigner, DistinguishedName, Error, SignatureAlgId, Time, oid};
use crate::der::{
    Reader, encode_bit_string, encode_integer, encode_octet_string, encode_sequence, encode_tlv,
    oid_tlv, parse_oid, pem_decode, pem_encode, tag,
};

const PEM_LABEL: &str = "X509 CRL";

/// `id-ce-cRLNumber` (2.5.29.20) — non-critical monotonic CRL sequence number
/// (RFC 5280 §5.2.3). Recognized; carries no policy we must act on.
const OID_CRL_NUMBER: &[u64] = &[2, 5, 29, 20];
/// `id-ce-deltaCRLIndicator` (2.5.29.27) — marks a *delta* CRL and is always
/// critical (RFC 5280 §5.2.4). We do not implement delta-CRL processing, so a
/// CRL bearing it MUST be rejected rather than silently treated as a base CRL.
const OID_DELTA_CRL_INDICATOR: &[u64] = &[2, 5, 29, 27];
/// `id-ce-issuingDistributionPoint` (2.5.29.28) — critical scoping extension
/// (RFC 5280 §5.2.5). Recognized; see [`CertificateRevocationList::validate_extensions`]
/// for the (conservative) handling.
const OID_ISSUING_DISTRIBUTION_POINT: &[u64] = &[2, 5, 29, 28];

/// `id-ce-authorityKeyIdentifier` (2.5.29.35) — non-critical (RFC 5280 §5.2.1).
const OID_AUTHORITY_KEY_IDENTIFIER: &[u64] = &[2, 5, 29, 35];

/// `id-ce-certificateIssuer` (2.5.29.29) — per-entry extension used by
/// *indirect* CRLs; always critical (RFC 5280 §5.3.3). We do not implement
/// indirect CRLs, so an entry bearing it is refused as unsupported.
const OID_CERTIFICATE_ISSUER: &[u64] = &[2, 5, 29, 29];
/// `id-ce-invalidityDate` (2.5.29.24) — non-critical per-entry hint
/// (RFC 5280 §5.3.2). Recognized and ignored.
const OID_INVALIDITY_DATE: &[u64] = &[2, 5, 29, 24];

/// Reasons a certificate may be revoked (RFC 5280 §5.3.1).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum CrlReason {
    /// `unspecified` (0). No `crlEntryExtensions` will be emitted when this
    /// is the only reason on an entry.
    Unspecified = 0,
    /// `keyCompromise` (1).
    KeyCompromise = 1,
    /// `cACompromise` (2).
    CACompromise = 2,
    /// `affiliationChanged` (3).
    AffiliationChanged = 3,
    /// `superseded` (4).
    Superseded = 4,
    /// `cessationOfOperation` (5).
    CessationOfOperation = 5,
    /// `certificateHold` (6).
    CertificateHold = 6,
    /// `removeFromCRL` (8). Only meaningful in delta-CRLs.
    RemoveFromCRL = 8,
    /// `privilegeWithdrawn` (9).
    PrivilegeWithdrawn = 9,
    /// `aACompromise` (10).
    AaCompromise = 10,
}

impl CrlReason {
    /// Parses a single-byte ENUMERATED value into a `CrlReason`. Returns
    /// `Err(Error::Malformed)` for values outside the set defined in
    /// RFC 5280 §5.3.1 (value 7 is reserved; values ≥ 11 are unassigned).
    /// Callers that want a permissive mapping can `.unwrap_or(Unspecified)`
    /// explicitly.
    pub fn from_u8(v: u8) -> Result<Self, Error> {
        match v {
            0 => Ok(CrlReason::Unspecified),
            1 => Ok(CrlReason::KeyCompromise),
            2 => Ok(CrlReason::CACompromise),
            3 => Ok(CrlReason::AffiliationChanged),
            4 => Ok(CrlReason::Superseded),
            5 => Ok(CrlReason::CessationOfOperation),
            6 => Ok(CrlReason::CertificateHold),
            8 => Ok(CrlReason::RemoveFromCRL),
            9 => Ok(CrlReason::PrivilegeWithdrawn),
            10 => Ok(CrlReason::AaCompromise),
            _ => Err(Error::Malformed),
        }
    }
}

/// One row in the CRL's `revokedCertificates` list.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RevokedCertificate {
    /// Raw serial INTEGER body, big-endian (strict-DER canonical: at most
    /// one leading `0x00` to keep the value non-negative).
    pub serial: Vec<u8>,
    /// Time at which the certificate was revoked.
    pub revocation_date: Time,
    /// Optional reason code from the `cRLReason` entry extension.
    pub reason: Option<CrlReason>,
}

/// A signed, DER-encoded X.509 v2 CRL.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateRevocationList {
    der: Vec<u8>,
}

/// Builder for a [`CertificateRevocationList`].
pub struct CrlBuilder {
    issuer_der: Vec<u8>,
    this_update: Time,
    next_update: Option<Time>,
    entries: Vec<RevokedCertificate>,
}

impl CrlBuilder {
    /// Starts a new CRL keyed by `issuer`.
    pub fn new(issuer: &DistinguishedName, this_update: Time, next_update: Option<Time>) -> Self {
        CrlBuilder {
            issuer_der: issuer.to_der(),
            this_update,
            next_update,
            entries: Vec::new(),
        }
    }

    /// Adds a revoked entry. `serial_be` is the raw big-endian serial number
    /// (the integer magnitude); it is canonicalized into a strict-DER INTEGER
    /// body at sign time.
    pub fn revoke(
        &mut self,
        serial_be: &[u8],
        revocation_date: Time,
        reason: Option<CrlReason>,
    ) -> &mut Self {
        self.entries.push(RevokedCertificate {
            serial: serial_be.to_vec(),
            revocation_date,
            reason,
        });
        self
    }

    /// Signs the accumulated CRL with `signer`, producing a
    /// [`CertificateRevocationList`].
    pub fn sign(self, signer: &CertSigner<'_>) -> Result<CertificateRevocationList, Error> {
        let algid = signer.algorithm_identifier();
        let tbs = encode_tbs_cert_list(
            &self.issuer_der,
            &self.this_update,
            self.next_update.as_ref(),
            &self.entries,
            &algid,
        );
        let sig = signer.sign(&tbs)?;
        Ok(PreparedCrl { tbs, algid }.finish(&sig))
    }

    /// Begins two-phase signing for a CA key held outside the process
    /// (TPM/HSM): encodes the `TBSCertList` with the `sig_alg` algorithm
    /// identifier and returns a [`PreparedCrl`] exposing the exact bytes to
    /// sign. The caller signs [`PreparedCrl::tbs`] with its external key and
    /// calls [`PreparedCrl::finish`] with the signature to assemble the CRL.
    ///
    /// No key material is required here — `sig_alg` must match the algorithm
    /// the external signer will use, since it is written into both the inner
    /// `signature` field and the outer `signatureAlgorithm`.
    pub fn prepare(self, sig_alg: SignatureAlgId) -> PreparedCrl {
        let algid = sig_alg.algorithm_identifier();
        let tbs = encode_tbs_cert_list(
            &self.issuer_der,
            &self.this_update,
            self.next_update.as_ref(),
            &self.entries,
            &algid,
        );
        PreparedCrl { tbs, algid }
    }
}

/// A `TBSCertList` awaiting an externally produced signature.
///
/// Returned by [`CrlBuilder::prepare`]. Sign [`tbs`](Self::tbs) with the CA's
/// out-of-process key, then call [`finish`](Self::finish) with the resulting
/// signature to obtain the complete [`CertificateRevocationList`].
pub struct PreparedCrl {
    tbs: Vec<u8>,
    algid: Vec<u8>,
}

impl PreparedCrl {
    /// The DER `TBSCertList` bytes the external signer must sign. The signer
    /// applies the hash/padding of the algorithm passed to
    /// [`CrlBuilder::prepare`]; these bytes are unhashed.
    pub fn tbs(&self) -> &[u8] {
        &self.tbs
    }

    /// Assembles the final `CertificateList` from the externally produced
    /// `signature` (the raw `signatureValue` bytes, encoding as documented on
    /// the [`SignatureAlgId`] variant).
    pub fn finish(self, signature: &[u8]) -> CertificateRevocationList {
        let der = encode_sequence(&[self.tbs, self.algid, encode_bit_string(signature)].concat());
        CertificateRevocationList { der }
    }
}

/// Encodes a `cRLReason` per-entry extension carrying an ENUMERATED.
fn crl_reason_extension(reason: CrlReason) -> Vec<u8> {
    // ENUMERATED ::= a single-byte body for the reason code.
    let enumerated = encode_tlv(0x0a, &[reason as u8]);
    let mut ext = oid_tlv(oid::CRL_REASON_CODE);
    ext.extend_from_slice(&encode_octet_string(&enumerated));
    encode_sequence(&ext)
}

/// Encodes one row of `revokedCertificates`.
fn encode_revoked(entry: &RevokedCertificate) -> Vec<u8> {
    let serial = encode_integer(&entry.serial);
    let rdate = entry.revocation_date.to_der_choice();
    let mut body = Vec::new();
    body.extend_from_slice(&serial);
    body.extend_from_slice(&rdate);
    if let Some(reason) = entry.reason
        && reason != CrlReason::Unspecified
    {
        // `crlEntryExtensions ::= Extensions ::= SEQUENCE OF Extension`.
        let ext = crl_reason_extension(reason);
        body.extend_from_slice(&encode_sequence(&ext));
    }
    encode_sequence(&body)
}

/// Encodes a `TBSCertList`, with the inner `signature` algid set to `algid`
/// (DER bytes — the outer `signatureAlgorithm` must equal this).
fn encode_tbs_cert_list(
    issuer_der: &[u8],
    this_update: &Time,
    next_update: Option<&Time>,
    entries: &[RevokedCertificate],
    algid: &[u8],
) -> Vec<u8> {
    let mut body = Vec::new();
    // version = v2 (1) — required when extensions or v2 fields are present.
    body.extend_from_slice(&encode_integer(&[1]));
    body.extend_from_slice(algid);
    body.extend_from_slice(issuer_der);
    body.extend_from_slice(&this_update.to_der_choice());
    if let Some(n) = next_update {
        body.extend_from_slice(&n.to_der_choice());
    }
    if !entries.is_empty() {
        let mut list = Vec::new();
        for e in entries {
            list.extend_from_slice(&encode_revoked(e));
        }
        body.extend_from_slice(&encode_sequence(&list));
    }
    // `crlExtensions [0] EXPLICIT Extensions OPTIONAL` — none emitted here.
    encode_sequence(&body)
}

/// The three top-level parts of a `CertificateList`.
struct CrlParts<'a> {
    /// Raw `TBSCertList` TLV (used for signature verification).
    tbs: &'a [u8],
    /// Outer signature algorithm OID arcs.
    sig_alg: Vec<u64>,
    /// Signature bits.
    signature: &'a [u8],
}

impl CertificateRevocationList {
    /// Wraps existing CRL DER. Validates that it is a single SEQUENCE with no
    /// trailing bytes (strict, matching the certificate parser) and that any
    /// `crlExtensions` it carries are acceptable — in particular, any
    /// **unrecognized critical** top-level extension causes rejection
    /// (RFC 5280 §5.2), and a delta CRL (which we do not implement) is
    /// refused. This fails closed, mirroring the certificate parser.
    pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
        let mut r = Reader::new(&der);
        r.read_sequence()?;
        r.finish()?;
        let crl = CertificateRevocationList { der };
        crl.validate_extensions()?;
        Ok(crl)
    }

    /// Parses a PEM `X509 CRL` document (RFC 7468 label). Applies the same
    /// `crlExtensions` validation as [`from_der`](Self::from_der).
    pub fn from_pem(pem: &str) -> Result<Self, Error> {
        let crl = CertificateRevocationList {
            der: pem_decode(pem, PEM_LABEL)?,
        };
        crl.validate_extensions()?;
        Ok(crl)
    }

    /// The DER encoding.
    pub fn to_der(&self) -> &[u8] {
        &self.der
    }

    /// The PEM encoding.
    pub fn to_pem(&self) -> String {
        pem_encode(PEM_LABEL, &self.der)
    }

    /// Splits the outer CRL into its three top-level parts.
    fn parts(&self) -> Result<CrlParts<'_>, Error> {
        let mut outer = Reader::new(&self.der);
        let mut crl = outer.read_sequence()?;
        let tbs = crl.read_element()?;
        let mut alg = crl.read_sequence()?;
        let sig_alg = parse_oid(alg.read_oid()?)?;
        let signature = crl.read_bit_string()?;
        // Strict DER (X.690 §11): no trailing bytes inside the outer
        // SEQUENCE.
        crl.finish()?;
        Ok(CrlParts {
            tbs,
            sig_alg,
            signature,
        })
    }

    /// Sub-reader over the `TBSCertList` body, positioned after the inner
    /// signature `AlgorithmIdentifier`.
    fn tbs_after_algid(&self) -> Result<Reader<'_>, Error> {
        let tbs = self.parts()?.tbs;
        let mut outer = Reader::new(tbs);
        let mut seq = outer.read_sequence()?;
        // Optional INTEGER version; v2 = 1. RFC 5280 §5.1 defines only v1
        // (absent) and v2 (1) — anything else is malformed.
        if seq.peek_tag() == Some(tag::INTEGER) {
            let v = seq.read_integer_bytes()?;
            // Single-byte 0 (v1) or 1 (v2); reject everything else.
            if v.len() != 1 || (v[0] != 0 && v[0] != 1) {
                return Err(Error::Malformed);
            }
        }
        seq.read_sequence()?; // inner signature AlgorithmIdentifier
        Ok(seq)
    }

    /// Returns the DER bytes of the inner `signature` AlgorithmIdentifier
    /// inside `TBSCertList`.
    pub(crate) fn inner_signature_algid_der(&self) -> Result<&[u8], Error> {
        let tbs = self.parts()?.tbs;
        let mut outer = Reader::new(tbs);
        let mut seq = outer.read_sequence()?;
        if seq.peek_tag() == Some(tag::INTEGER) {
            let v = seq.read_integer_bytes()?;
            if v.len() != 1 || (v[0] != 0 && v[0] != 1) {
                return Err(Error::Malformed);
            }
        }
        Ok(seq.read_element()?)
    }

    /// Returns the DER bytes of the outer `signatureAlgorithm`
    /// AlgorithmIdentifier (RFC 5280 §5.1.1.2).
    pub(crate) fn outer_signature_algid_der(&self) -> Result<&[u8], Error> {
        let mut outer = Reader::new(&self.der);
        let mut crl = outer.read_sequence()?;
        crl.read_element()?; // skip TBSCertList
        Ok(crl.read_element()?)
    }

    /// RFC 5280 §5.1.1.2: the inner and outer signature AlgorithmIdentifier
    /// fields MUST be identical. Compares raw DER (parameters included),
    /// mirroring `super::Certificate::check_signature_algid_consistent`.
    pub fn check_signature_algid_consistent(&self) -> Result<(), Error> {
        let inner = self.inner_signature_algid_der()?;
        let outer = self.outer_signature_algid_der()?;
        if inner == outer {
            Ok(())
        } else {
            Err(Error::Malformed)
        }
    }

    /// The CRL issuer.
    pub fn issuer(&self) -> Result<DistinguishedName, Error> {
        let mut seq = self.tbs_after_algid()?;
        DistinguishedName::decode(&mut seq)
    }

    /// The raw DER bytes of the CRL's `issuer` field — the full `Name` TLV.
    pub fn issuer_der(&self) -> Result<&[u8], Error> {
        let mut seq = self.tbs_after_algid()?;
        Ok(seq.read_element()?)
    }

    /// `thisUpdate`.
    pub fn this_update(&self) -> Result<Time, Error> {
        let mut seq = self.tbs_after_algid()?;
        seq.read_element()?; // issuer
        read_time(&mut seq)
    }

    /// `nextUpdate`, if present.
    pub fn next_update(&self) -> Result<Option<Time>, Error> {
        let mut seq = self.tbs_after_algid()?;
        seq.read_element()?; // issuer
        read_time(&mut seq)?; // thisUpdate
        if seq.is_empty() {
            return Ok(None);
        }
        match seq.peek_tag() {
            Some(t) if t == tag::UTC_TIME || t == tag::GENERALIZED_TIME => {
                read_time(&mut seq).map(Some)
            }
            _ => Ok(None),
        }
    }

    /// The OID arcs of the CRL's outer `signatureAlgorithm` field.
    pub fn signature_algorithm_oid(&self) -> Result<Vec<u64>, Error> {
        Ok(self.parts()?.sig_alg)
    }

    /// Verifies the CRL signature against `issuer_key`, dispatching on the
    /// CRL's `signatureAlgorithm` through the signature registry.
    ///
    /// As mandated by RFC 5280 §5.1.1.2, this first confirms the outer
    /// `signatureAlgorithm` is byte-identical to the inner
    /// `TBSCertList.signature` AlgorithmIdentifier (returning
    /// [`Error::Malformed`] otherwise), closing the signature-algorithm-
    /// confusion gap. The check is idempotent.
    ///
    /// SECURITY: this method applies **no** signature-algorithm-strength or
    /// key-size policy. A SHA-1- or MD5-based signature, or an undersized RSA
    /// key, will verify **successfully** here. Callers MUST enforce their own
    /// policy (e.g. via `SignaturePolicy::permits`, as the TLS path does in
    /// `tls::pki::verify`). Do not treat a successful return as
    /// evidence the algorithm is acceptable.
    pub fn verify_signature_with(&self, issuer_key: &AnyPublicKey) -> Result<(), Error> {
        self.check_signature_algid_consistent()?;
        let parts = self.parts()?;
        issuer_key.verify(&parts.sig_alg, parts.tbs, parts.signature)
    }

    /// Returns a sub-reader positioned at the optional `crlExtensions [0]`
    /// field of the `TBSCertList`, having consumed every field that precedes
    /// it (issuer, thisUpdate, optional nextUpdate, optional
    /// revokedCertificates). The returned reader is empty when no
    /// `crlExtensions` field is present.
    fn tbs_at_crl_extensions(&self) -> Result<Reader<'_>, Error> {
        let mut seq = self.tbs_after_algid()?;
        seq.read_element()?; // issuer
        read_time(&mut seq)?; // thisUpdate
        // Optional nextUpdate.
        if let Some(t) = seq.peek_tag()
            && (t == tag::UTC_TIME || t == tag::GENERALIZED_TIME)
        {
            read_time(&mut seq)?;
        }
        // Optional revokedCertificates SEQUENCE OF SEQUENCE.
        if seq.peek_tag() == Some(tag::SEQUENCE) {
            seq.read_element()?;
        }
        Ok(seq)
    }

    /// Parses and vets the top-level `crlExtensions [0] EXPLICIT Extensions`
    /// field (RFC 5280 §5.2), failing closed exactly as the certificate parser
    /// does for certificate extensions:
    ///
    /// * Any **unrecognized critical** extension causes rejection — the CRL is
    ///   not silently consumed as understood.
    /// * `deltaCRLIndicator` (2.5.29.27) is refused outright: this crate does
    ///   not implement delta-CRL processing, so applying a delta CRL as if it
    ///   were a base CRL would mis-state revocation status.
    /// * A critical `issuingDistributionPoint` (2.5.29.28) is refused: the CRL
    ///   validator treats a CRL as covering everything its issuer signed, but a
    ///   critical IDP narrows that scope in ways we do not evaluate — honouring
    ///   the CRL anyway risks reporting a revoked certificate as good.
    /// * `cRLNumber` (2.5.29.20) and `authorityKeyIdentifier` (2.5.29.35) are
    ///   recognized; per RFC 5280 they MUST be non-critical, so a critical
    ///   instance is rejected.
    /// * A duplicate top-level extension OID is rejected (matching the
    ///   certificate parser's CVE-2014-1568-shaped guard).
    fn validate_extensions(&self) -> Result<(), Error> {
        let mut seq = self.tbs_at_crl_extensions()?;
        // crlExtensions [0] EXPLICIT — absent is fine.
        if seq.peek_tag() != Some(tag::context(0)) {
            return Ok(());
        }
        let wrapper = seq.read_tlv(tag::context(0))?;
        let mut outer = Reader::new(wrapper);
        let mut exts = outer.read_sequence()?;
        outer.finish()?;
        // Extensions ::= SEQUENCE SIZE (1..MAX): a present-but-empty block is
        // malformed.
        if exts.is_empty() {
            return Err(Error::Malformed);
        }
        let mut seen: Vec<Vec<u64>> = Vec::new();
        while !exts.is_empty() {
            let mut ext = exts.read_sequence()?;
            let id = parse_oid(ext.read_oid()?)?;
            if seen.iter().any(|prior| prior.as_slice() == id.as_slice()) {
                return Err(Error::Malformed);
            }
            seen.push(id.clone());
            let critical = if ext.peek_tag() == Some(tag::BOOLEAN) {
                ext.read_boolean()?
            } else {
                false
            };
            let _value = ext.read_octet_string()?;
            ext.finish()?;
            let id = id.as_slice();
            if id == OID_DELTA_CRL_INDICATOR {
                // Delta CRLs are not supported; refuse regardless of the
                // critical flag (the RFC requires it to be critical anyway).
                return Err(Error::UnsupportedAlgorithm);
            } else if id == OID_ISSUING_DISTRIBUTION_POINT {
                // We do not evaluate the IDP scoping fields; a critical IDP
                // must therefore be rejected (fail closed).
                if critical {
                    return Err(Error::UnsupportedAlgorithm);
                }
            } else if id == OID_CRL_NUMBER || id == OID_AUTHORITY_KEY_IDENTIFIER {
                // Recognized informational extensions. RFC 5280 mandates these
                // be non-critical; a critical instance is malformed.
                if critical {
                    return Err(Error::Malformed);
                }
            } else if critical {
                // RFC 5280 §5.2: an unrecognized critical extension MUST cause
                // the CRL to be rejected.
                return Err(Error::UnsupportedAlgorithm);
            }
            // Unrecognized non-critical extensions are ignored.
        }
        Ok(())
    }

    /// Iterates the `revokedCertificates` entries. Returns an empty list if
    /// the field is absent.
    pub fn entries(&self) -> Result<Vec<RevokedCertificate>, Error> {
        let mut out = Vec::new();
        let mut seq = self.tbs_after_algid()?;
        seq.read_element()?; // issuer
        read_time(&mut seq)?; // thisUpdate

        // Skip nextUpdate when present.
        if let Some(t) = seq.peek_tag()
            && (t == tag::UTC_TIME || t == tag::GENERALIZED_TIME)
        {
            read_time(&mut seq)?;
        }

        // Optional revokedCertificates SEQUENCE OF SEQUENCE.
        if seq.peek_tag() == Some(tag::SEQUENCE) {
            let inner = seq.read_element()?;
            let mut r = Reader::new(inner);
            let mut list = r.read_sequence()?;
            while !list.is_empty() {
                let mut entry = list.read_sequence()?;
                let serial = entry.read_unsigned_integer_bytes()?.to_vec();
                let revocation_date = read_time(&mut entry)?;
                let mut reason = None;
                // Optional crlEntryExtensions (Extensions ::= SEQUENCE OF
                // Extension); scan for the cRLReason entry.
                if !entry.is_empty() {
                    let mut exts = entry.read_sequence()?;
                    while !exts.is_empty() {
                        let mut ext = exts.read_sequence()?;
                        let id = parse_oid(ext.read_oid()?)?;
                        let critical = if ext.peek_tag() == Some(tag::BOOLEAN) {
                            ext.read_boolean()?
                        } else {
                            false
                        };
                        let value = ext.read_octet_string()?;
                        let id = id.as_slice();
                        if id == oid::CRL_REASON_CODE {
                            let mut vr = Reader::new(value);
                            let enum_body = vr.read_tlv(0x0a)?;
                            // ENUMERATED is single-byte in practice for the
                            // values defined by RFC 5280 §5.3.1; reject out-
                            // of-range codes (including the reserved value 7
                            // and any unassigned value ≥ 11).
                            if enum_body.len() != 1 {
                                return Err(Error::Malformed);
                            }
                            reason = Some(CrlReason::from_u8(enum_body[0])?);
                        } else if id == OID_CERTIFICATE_ISSUER {
                            // Indirect CRLs are not supported; refuse the entry
                            // rather than mis-attribute it to the CRL issuer.
                            return Err(Error::UnsupportedAlgorithm);
                        } else if id == OID_INVALIDITY_DATE {
                            // Recognized, non-critical hint; nothing to do.
                        } else if critical {
                            // RFC 5280 §5.2: an unrecognized *critical* entry
                            // extension MUST cause rejection. Fail closed.
                            return Err(Error::UnsupportedAlgorithm);
                        }
                        // Unrecognized non-critical entry extensions: ignored.
                    }
                }
                out.push(RevokedCertificate {
                    serial,
                    revocation_date,
                    reason,
                });
            }
        }
        Ok(out)
    }

    /// Whether `serial_be` (the raw INTEGER body, with at most one leading
    /// `0x00`) is revoked by this CRL.
    ///
    /// Comparison is on the *magnitude*: a leading sign-protection `0x00`
    /// is stripped from both sides, so `02 02 00 7F` matches `7F`.
    pub fn is_revoked(&self, serial_be: &[u8]) -> Result<bool, Error> {
        let needle = strip_leading_sign_zero(serial_be);
        for e in self.entries()? {
            if strip_leading_sign_zero(&e.serial) == needle {
                return Ok(true);
            }
        }
        Ok(false)
    }
}

/// Strips a single permitted leading `0x00` (the strict-DER positive-sign
/// pad) from `bytes`, leaving the bare magnitude.
fn strip_leading_sign_zero(bytes: &[u8]) -> &[u8] {
    if bytes.len() > 1 && bytes[0] == 0x00 {
        &bytes[1..]
    } else {
        bytes
    }
}

#[cfg(test)]
mod tests {
    use super::super::algorithm_identifier;
    use super::*;
    use crate::rsa::BoxedRsaPrivateKey;
    use crate::x509::{CertSigner, Validity};

    fn rsa_a() -> BoxedRsaPrivateKey {
        BoxedRsaPrivateKey::from_pkcs1_pem(include_str!("../../testdata/rsa2048_test_a.pem"))
            .expect("rsa key A")
    }
    fn rsa_b() -> BoxedRsaPrivateKey {
        BoxedRsaPrivateKey::from_pkcs1_pem(include_str!("../../testdata/rsa2048_test_b.pem"))
            .expect("rsa key B")
    }

    fn validity() -> Validity {
        Validity::new(
            Time::utc(2024, 1, 1, 0, 0, 0),
            Time::utc(2034, 1, 1, 0, 0, 0),
        )
    }

    fn issuer_dn() -> DistinguishedName {
        DistinguishedName::common_name("purecrypto CRL test")
    }

    #[test]
    fn roundtrip_two_entries_and_verify() {
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let mut b = CrlBuilder::new(
            &dn,
            Time::utc(2026, 1, 1, 0, 0, 0),
            Some(Time::utc(2026, 12, 31, 0, 0, 0)),
        );
        b.revoke(
            &[0x01],
            Time::utc(2026, 2, 1, 0, 0, 0),
            Some(CrlReason::KeyCompromise),
        );
        b.revoke(
            &[0x02, 0x03],
            Time::utc(2026, 2, 2, 0, 0, 0),
            Some(CrlReason::Superseded),
        );
        let crl = b.sign(&signer).unwrap();

        // Round-trip through DER and PEM.
        let from_der = CertificateRevocationList::from_der(crl.to_der().to_vec()).unwrap();
        assert_eq!(from_der, crl);
        let pem = crl.to_pem();
        assert!(pem.contains("BEGIN X509 CRL"));
        let from_pem = CertificateRevocationList::from_pem(&pem).unwrap();
        assert_eq!(from_pem, crl);

        // Issuer / dates / algid consistency.
        assert_eq!(crl.issuer().unwrap(), dn);
        assert_eq!(crl.this_update().unwrap(), Time::utc(2026, 1, 1, 0, 0, 0));
        assert_eq!(
            crl.next_update().unwrap().unwrap(),
            Time::utc(2026, 12, 31, 0, 0, 0)
        );
        crl.check_signature_algid_consistent().unwrap();

        // Entries decode the way we encoded.
        let entries = crl.entries().unwrap();
        assert_eq!(entries.len(), 2);
        assert_eq!(entries[0].serial, alloc::vec![0x01]);
        assert_eq!(entries[0].reason, Some(CrlReason::KeyCompromise));
        assert_eq!(entries[1].serial, alloc::vec![0x02, 0x03]);
        assert_eq!(entries[1].reason, Some(CrlReason::Superseded));

        // Signature verifies against the issuer.
        let issuer_pub = signer.public_key();
        crl.verify_signature_with(&issuer_pub).unwrap();

        // is_revoked matches on the magnitude.
        assert!(crl.is_revoked(&[0x01]).unwrap());
        assert!(crl.is_revoked(&[0x02, 0x03]).unwrap());
        // Strict-DER canonical comparison: a sign-protection 0x00 is
        // transparently stripped.
        assert!(crl.is_revoked(&[0x00, 0x01]).unwrap());
        // A different serial: not revoked.
        assert!(!crl.is_revoked(&[0x07]).unwrap());
    }

    #[test]
    fn two_phase_external_signing_matches_one_shot() {
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let build = || {
            let mut b = CrlBuilder::new(
                &dn,
                Time::utc(2026, 1, 1, 0, 0, 0),
                Some(Time::utc(2026, 12, 31, 0, 0, 0)),
            );
            b.revoke(
                &[0x01],
                Time::utc(2026, 2, 1, 0, 0, 0),
                Some(CrlReason::KeyCompromise),
            );
            b
        };

        // One-shot, in-process signing.
        let one_shot = build().sign(&signer).unwrap();

        // Two-phase: the "HSM" is the same in-process key, but invoked only on
        // the prepared TBS bytes — no key was handed to the builder.
        let prepared = build().prepare(SignatureAlgId::RsaPkcs1Sha256);
        let sig = signer.sign(prepared.tbs()).unwrap();
        let two_phase = prepared.finish(&sig);

        // RSA PKCS#1 v1.5 is deterministic, so the two paths are byte-identical.
        assert_eq!(one_shot.to_der(), two_phase.to_der());
        // And the externally assembled CRL verifies against the issuer key.
        two_phase
            .verify_signature_with(&signer.public_key())
            .unwrap();
    }

    #[test]
    fn verify_signature_rejects_wrong_key() {
        let key_a = rsa_a();
        let key_b = rsa_b();
        let signer = CertSigner::Rsa(&key_a);
        let dn = issuer_dn();
        let b = CrlBuilder::new(&dn, Time::utc(2026, 1, 1, 0, 0, 0), None);
        let crl = b.sign(&signer).unwrap();

        // Signed by A; verifies under A, rejects under B.
        crl.verify_signature_with(&CertSigner::Rsa(&key_a).public_key())
            .unwrap();
        assert!(
            crl.verify_signature_with(&CertSigner::Rsa(&key_b).public_key())
                .is_err()
        );
    }

    #[test]
    fn verify_signature_rejects_tampered_byte() {
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let mut b = CrlBuilder::new(&dn, Time::utc(2026, 1, 1, 0, 0, 0), None);
        b.revoke(&[0x05], Time::utc(2026, 1, 2, 0, 0, 0), None);
        let crl = b.sign(&signer).unwrap();

        let mut der = crl.to_der().to_vec();
        // Flip a byte inside the TBS.
        let idx = der.len() / 3;
        der[idx] ^= 1;
        let bad = CertificateRevocationList::from_der(der).unwrap();
        assert!(bad.verify_signature_with(&signer.public_key()).is_err());

        // The unmodified CRL still verifies.
        crl.verify_signature_with(&signer.public_key()).unwrap();
        let _ = validity(); // keep the helper used.
    }

    #[test]
    fn rejects_inner_outer_algid_mismatch() {
        // Construct a CRL whose outer signatureAlgorithm differs from the
        // inner one (RFC 5280 §5.1.1.2 forbids this). The signature itself
        // is over the TBS bytes (with the inner algid), so the *signature*
        // would technically verify — but the consistency check refuses the
        // CRL before that.
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();

        // Build a TBS with inner = ECDSA-SHA256 (any non-RSA algid will do).
        // We can't actually *sign* with ECDSA-SHA256 using an RSA key, so we
        // sign with the matching RSA algid for the wire encoding and then
        // splice the outer to a different OID.
        let inner_algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
        let tbs = encode_tbs_cert_list(
            &dn.to_der(),
            &Time::utc(2026, 1, 1, 0, 0, 0),
            None,
            &[],
            &inner_algid,
        );
        let sig = signer.sign(&tbs).unwrap();
        // Outer = sha1WithRSAEncryption (different OID than inner).
        let outer_algid = algorithm_identifier(oid::SHA1_WITH_RSA, true);
        let der = encode_sequence(&[tbs, outer_algid, encode_bit_string(&sig)].concat());
        let mismatched = CertificateRevocationList::from_der(der).unwrap();
        assert!(matches!(
            mismatched.check_signature_algid_consistent(),
            Err(Error::Malformed)
        ));
        // The public verify entry point must also refuse the mismatch
        // (RFC 5280 §5.1.1.2), even though the raw signature is valid over the
        // TBS. Finding 1: the algid-consistency check is now enforced inside
        // verify_signature_with, not just the standalone comparator.
        assert!(matches!(
            mismatched.verify_signature_with(&signer.public_key()),
            Err(Error::Malformed)
        ));
    }

    #[test]
    fn rejects_unknown_version() {
        // Forge a TBSCertList carrying version=99. RFC 5280 §5.1 only
        // defines v1 (absent) and v2 (1); anything else is malformed.
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);

        let mut body = Vec::new();
        body.extend_from_slice(&encode_integer(&[99])); // bogus version
        body.extend_from_slice(&algid);
        body.extend_from_slice(&dn.to_der());
        body.extend_from_slice(&Time::utc(2026, 1, 1, 0, 0, 0).to_der_choice());
        let tbs = encode_sequence(&body);
        let sig = signer.sign(&tbs).unwrap();
        let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());

        // `from_der` now validates the (absent) crlExtensions field, which
        // walks the version and surfaces the malformed value at construction
        // time — fail closed at the boundary.
        assert!(matches!(
            CertificateRevocationList::from_der(der),
            Err(Error::Malformed)
        ));
    }

    #[test]
    fn rejects_unknown_revocation_reason() {
        // Forge a CRL entry with reason ENUMERATED = 255 — outside the
        // {0..=6, 8..=10} set defined by RFC 5280 §5.3.1.
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);

        // Build a revoked-cert entry by hand.
        let serial = encode_integer(&[0x07]);
        let rdate = Time::utc(2026, 1, 2, 0, 0, 0).to_der_choice();
        let enumerated = encode_tlv(0x0a, &[0xff]);
        let mut ext_body = oid_tlv(oid::CRL_REASON_CODE);
        ext_body.extend_from_slice(&encode_octet_string(&enumerated));
        let ext = encode_sequence(&ext_body);
        let exts = encode_sequence(&ext);
        let mut entry = Vec::new();
        entry.extend_from_slice(&serial);
        entry.extend_from_slice(&rdate);
        entry.extend_from_slice(&exts);
        let revoked = encode_sequence(&entry);
        let revoked_seq = encode_sequence(&revoked);

        let mut tbs_body = Vec::new();
        tbs_body.extend_from_slice(&encode_integer(&[1]));
        tbs_body.extend_from_slice(&algid);
        tbs_body.extend_from_slice(&dn.to_der());
        tbs_body.extend_from_slice(&Time::utc(2026, 1, 1, 0, 0, 0).to_der_choice());
        tbs_body.extend_from_slice(&revoked_seq);
        let tbs = encode_sequence(&tbs_body);
        let sig = signer.sign(&tbs).unwrap();
        let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());

        let crl = CertificateRevocationList::from_der(der).unwrap();
        assert!(matches!(crl.entries(), Err(Error::Malformed)));
    }

    /// Encodes one `Extension ::= SEQUENCE { OID, critical?, OCTET STRING }`.
    fn ext(oid_arcs: &[u64], critical: bool, value: &[u8]) -> Vec<u8> {
        let mut body = oid_tlv(oid_arcs);
        if critical {
            body.extend_from_slice(&encode_tlv(0x01, &[0xff])); // BOOLEAN TRUE
        }
        body.extend_from_slice(&encode_octet_string(value));
        encode_sequence(&body)
    }

    /// Signs a `TBSCertList` carrying a top-level `crlExtensions [0]` built from
    /// the concatenated `exts`, returning the full CRL DER (unparsed).
    fn crl_der_with_extensions(exts: &[u8]) -> Vec<u8> {
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
        let mut tbs_body = Vec::new();
        tbs_body.extend_from_slice(&encode_integer(&[1]));
        tbs_body.extend_from_slice(&algid);
        tbs_body.extend_from_slice(&dn.to_der());
        tbs_body.extend_from_slice(&Time::utc(2026, 1, 1, 0, 0, 0).to_der_choice());
        // crlExtensions [0] EXPLICIT Extensions.
        let extensions = encode_sequence(exts);
        tbs_body.extend_from_slice(&encode_tlv(tag::context(0), &extensions));
        let tbs = encode_sequence(&tbs_body);
        let sig = signer.sign(&tbs).unwrap();
        encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat())
    }

    #[test]
    fn rejects_unknown_critical_crl_extension() {
        // A bogus critical top-level extension (OID 2.5.29.99) must cause the
        // CRL to be rejected at parse time (RFC 5280 §5.2 fail-closed).
        let der = crl_der_with_extensions(&ext(&[2, 5, 29, 99], true, &[0x05, 0x00]));
        assert!(matches!(
            CertificateRevocationList::from_der(der),
            Err(Error::UnsupportedAlgorithm)
        ));
    }

    #[test]
    fn rejects_delta_crl_indicator() {
        // deltaCRLIndicator (2.5.29.27) marks an unsupported delta CRL.
        let base_crl_number = encode_integer(&[0x01]);
        let der = crl_der_with_extensions(&ext(&[2, 5, 29, 27], true, &base_crl_number));
        assert!(matches!(
            CertificateRevocationList::from_der(der),
            Err(Error::UnsupportedAlgorithm)
        ));
    }

    #[test]
    fn rejects_critical_issuing_distribution_point() {
        // A critical IDP narrows scope in ways we don't evaluate → fail closed.
        let idp = encode_sequence(&[]); // empty IssuingDistributionPoint SEQUENCE
        let der = crl_der_with_extensions(&ext(&[2, 5, 29, 28], true, &idp));
        assert!(matches!(
            CertificateRevocationList::from_der(der),
            Err(Error::UnsupportedAlgorithm)
        ));
    }

    #[test]
    fn accepts_known_noncritical_crl_extensions() {
        // cRLNumber (2.5.29.20) + authorityKeyIdentifier (2.5.29.35), both
        // non-critical, are recognized and accepted.
        let crl_number = ext(&[2, 5, 29, 20], false, &encode_integer(&[0x2a]));
        let aki = ext(&[2, 5, 29, 35], false, &encode_sequence(&[]));
        let der = crl_der_with_extensions(&[crl_number, aki].concat());
        let crl = CertificateRevocationList::from_der(der).expect("known exts accepted");
        // And the parsed CRL still behaves normally.
        assert!(crl.entries().unwrap().is_empty());
    }

    #[test]
    fn rejects_critical_crl_number() {
        // cRLNumber MUST be non-critical (RFC 5280 §5.2.3): a critical instance
        // is malformed.
        let der = crl_der_with_extensions(&ext(&[2, 5, 29, 20], true, &encode_integer(&[0x01])));
        assert!(matches!(
            CertificateRevocationList::from_der(der),
            Err(Error::Malformed)
        ));
    }

    #[test]
    fn empty_crl_has_no_entries() {
        let key = rsa_a();
        let signer = CertSigner::Rsa(&key);
        let dn = issuer_dn();
        let b = CrlBuilder::new(&dn, Time::utc(2026, 1, 1, 0, 0, 0), None);
        let crl = b.sign(&signer).unwrap();
        assert!(crl.entries().unwrap().is_empty());
        assert!(!crl.is_revoked(&[0x42]).unwrap());
        assert!(crl.next_update().unwrap().is_none());
    }
}
