tasign 0.2.0

TA ELF signing utilities with CMS/PKCS#7 support
//! Certificate chain verification via `x509-cert` + `pkix-path`.

extern crate alloc;

use alloc::vec::Vec;

use super::pkix_path::{validate_path, TrustAnchor, ValidationPolicy};
use x509_cert::der::Decode;
use x509_cert::Certificate;

use super::error::{Result, X509Error};
use super::sm2_verifier::TasignSignatureVerifier;

/// Verify a certificate chain against PEM trust anchors.
///
/// **Security (`kernel-verify`)**: without `std`, this calls [`validation_time_secs`]
/// which returns `u64::MAX`. Per `pkix_path::ValidationPolicy` documentation, this **disables
/// expiry checking** (notBefore/notAfter against a real wall clock). Production kernel code with
/// RTC should call [`verify_chain_at`] with an explicit Unix timestamp. See
/// [wall-clock plan](../../../docs/plans/2026-06-18-cert-chain-wall-clock-plan.md).
pub fn verify_chain(chain_der: &[&[u8]], trust_roots_pem: &[u8]) -> Result<()> {
    verify_chain_at(chain_der, trust_roots_pem, validation_time_secs()?)
}

/// Verify a certificate chain at a fixed Unix timestamp (for `no_std` / kernel callers).
pub fn verify_chain_at(chain_der: &[&[u8]], trust_roots_pem: &[u8], unix_secs: u64) -> Result<()> {
    if chain_der.is_empty() {
        return Err(X509Error::InvalidInput);
    }

    let chain: Vec<Certificate> = chain_der
        .iter()
        .map(|der| Certificate::from_der(der).map_err(map_der_err))
        .collect::<Result<_>>()?;

    let anchors = load_trust_anchors(trust_roots_pem)?;
    if anchors.is_empty() {
        return Err(X509Error::UntrustedRoot);
    }

    let policy = ValidationPolicy::new(unix_secs);
    validate_path(&chain, &anchors, &policy, &TasignSignatureVerifier)
        .map_err(map_pkix_err)
        .map(|_| ())
}

fn load_trust_anchors(pem: &[u8]) -> Result<Vec<TrustAnchor>> {
    let certs = Certificate::load_pem_chain(pem).map_err(map_der_err)?;
    Ok(certs.iter().map(TrustAnchor::from).collect())
}

#[cfg(feature = "std")]
fn validation_time_secs() -> Result<u64> {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|_| X509Error::InternalError)
        .map(|d| d.as_secs())
}

/// Kernel / bare-metal: no wall clock. Returns `u64::MAX` so `pkix_path` skips real
/// notBefore/notAfter checks (see `ValidationPolicy::current_time_unix` warning).
/// Callers with RTC must use [`verify_chain_at`] instead.
#[cfg(not(feature = "std"))]
fn validation_time_secs() -> Result<u64> {
    Ok(u64::MAX)
}

fn map_der_err(_e: der::Error) -> X509Error {
    X509Error::InvalidCertificate
}

fn map_pkix_err(err: super::pkix_path::Error) -> X509Error {
    use super::pkix_path::Error as PkixError;
    match err {
        PkixError::SignatureInvalid { .. } => X509Error::SignatureVerificationFailed,
        PkixError::ValidityPeriod { .. } => X509Error::CertificateExpired,
        PkixError::NoTrustedPath | PkixError::ChainBroken { .. } => X509Error::ChainBuildFailed,
        PkixError::MalformedCertificate { .. } => X509Error::InvalidCertificate,
        PkixError::NotCA { .. }
        | PkixError::PathTooLong
        | PkixError::KeyUsageMissing { .. }
        | PkixError::CrlSignMissing { .. }
        | PkixError::UnhandledCriticalExtension { .. } => X509Error::InvalidCertificate,
        _ => X509Error::Message(alloc::format!("pkix-path: {err:?}")),
    }
}