synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! Signature verification backed by NSS (Network Security Services).
//!
//! Two dispatch paths:
//!
//! - **Ed25519**: `PK11_Verify` with the raw TBS message.  NSS registers the
//!   Ed25519 OID (1.3.101.112) twice in its internal table — once as a signature
//!   algorithm (tag 373) and once as a public-key type (tag 374).  The lookup
//!   finds tag 374 first, so `VFY_VerifyDataWithAlgorithmID` returns
//!   `SEC_ERROR_INVALID_ALGORITHM` for Ed25519.  `PK11_Verify` bypasses the OID
//!   lookup and uses the key's native `CKM_EDDSA` mechanism directly.
//!
//! - **RSA, ECDSA, ML-DSA, and all other algorithms**: `VFY_VerifyDataWithAlgorithmID`
//!   with the raw `AlgorithmIdentifier` DER from the certificate.  NSS resolves
//!   the algorithm and verifies in one call.

use std::ptr;

use crate::crypto::utils::split_alg_id;
use crate::crypto::PrivateKeyError;
use crate::crypto::{ErasedSignatureVerifier, SignatureVerifier};
use crate::oids;

use super::ensure_nss_init;

/// Error type for [`NssSignatureVerifier`].
#[derive(Debug)]
pub struct NssVerifierError(pub(crate) String);

impl std::fmt::Display for NssVerifierError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl std::error::Error for NssVerifierError {}

// ── NSS FFI declarations ──────────────────────────────────────────────────────

use nss_sys::{SECAlgorithmIDStr, SECItemStr, SECItemType};

// All shared key types and FFI declarations live in the central ffi module to
// eliminate clashing_extern_declarations warnings.
use super::ffi::{
    PK11_Verify, SECKEY_DecodeDERSubjectPublicKeyInfo, SECKEY_DestroyPublicKey,
    SECKEY_DestroySubjectPublicKeyInfo, SECKEY_ExtractPublicKey, VFY_VerifyDataWithAlgorithmID,
};

// ── NssSignatureVerifier ──────────────────────────────────────────────────────

/// NSS-backed certificate signature verifier.
///
/// Implements [`SignatureVerifier`] for use with the `synta-x509-verification`
/// path validator.  NSS dispatches over all algorithms that the installed NSS
/// library supports, including:
///
/// - RSA PKCS#1 v1.5: SHA-1, SHA-256, SHA-384, SHA-512
/// - RSA-PSS (RSASSA-PSS with parameters)
/// - ECDSA: SHA-256, SHA-384, SHA-512
/// - EdDSA: Ed25519, Ed448
/// - ML-DSA-44, ML-DSA-65, ML-DSA-87 (requires NSS ≥ 3.103)
///
/// Algorithm dispatch:
/// - Ed25519: `PK11_Verify` with the raw TBS message (see module doc for why).
/// - All others: `VFY_VerifyDataWithAlgorithmID` with the `AlgorithmIdentifier`
///   DER from the certificate — NSS dispatches over all supported algorithms.
///
/// # Example
///
/// ```rust,ignore
/// use synta_certificate::NssSignatureVerifier;
/// use synta_x509_verification::policy::PolicyDefinition;
///
/// let policy = PolicyDefinition::new_server(
///     NssSignatureVerifier,
///     Some(subject),
///     validation_time,
/// );
/// ```
pub struct NssSignatureVerifier;

fn do_verify_signature(
    tbs_der: &[u8],
    sig_alg_der: &[u8],
    signature_bits: &[u8],
    issuer_spki_der: &[u8],
) -> Result<(), NssVerifierError> {
    if !ensure_nss_init() {
        return Err(NssVerifierError("NSS initialisation failed".to_string()));
    }

    // Parse the OID and parameters from the AlgorithmIdentifier DER.
    // Both slices are zero-copy references into sig_alg_der.
    let (oid, oid_value, params) =
        split_alg_id(sig_alg_der, |e| NssVerifierError(format!("{:?}", e)))?;

    // Intercept composite ML-DSA OIDs before attempting to decode the issuer
    // SPKI — NSS cannot parse composite SPKI DER (unknown OID + raw concat PK).
    {
        let comps = oid.components();
        if comps.starts_with(oids::COMPOSITE_MLDSA_ARC) && comps.len() == 9 {
            let sub_arc = comps[8];
            return super::composite::verify_composite_mldsa_signature(
                tbs_der,
                sub_arc,
                signature_bits,
                issuer_spki_der,
            );
        }
    }

    // Decode the issuer's SubjectPublicKeyInfo DER without PKCS#11.
    // SECKEY_DecodeDERSubjectPublicKeyInfo + SECKEY_ExtractPublicKey is the
    // correct path for raw key import; SECKEY_ImportDERPublicKey goes through
    // the PKCS#11 subsystem and may fail without a configured key database.
    let spki_item = SECItemStr {
        type_: SECItemType::siBuffer,
        data: issuer_spki_der.as_ptr() as *mut _,
        len: issuer_spki_der.len() as u32,
    };

    // SAFETY: reads only from `spki_item.data` for `spki_item.len` bytes;
    // the slice is valid for the duration of the call.
    let spki_info = unsafe { SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item) };
    if spki_info.is_null() {
        return Err(NssVerifierError(
            "SECKEY_DecodeDERSubjectPublicKeyInfo failed: could not decode issuer SPKI".to_string(),
        ));
    }

    // SAFETY: `spki_info` is non-null and was returned by the decode call.
    let pub_key = unsafe { SECKEY_ExtractPublicKey(spki_info) };
    // SAFETY: always destroy spki_info, whether pub_key is null or not.
    unsafe { SECKEY_DestroySubjectPublicKeyInfo(spki_info) };
    if pub_key.is_null() {
        return Err(NssVerifierError(
            "SECKEY_ExtractPublicKey failed: could not extract public key from SPKI".to_string(),
        ));
    }

    // Wrap the raw signature bytes in a SECItem for NSS.
    let sig_item = SECItemStr {
        type_: SECItemType::siBuffer,
        data: signature_bits.as_ptr() as *mut _,
        len: signature_bits.len() as u32,
    };

    let status = if oid.components() == oids::ED25519 {
        // Ed25519 path: use PK11_Verify with the raw TBS message.
        //
        // VFY_VerifyDataWithAlgorithmID fails for Ed25519 with
        // SEC_ERROR_INVALID_ALGORITHM because NSS registers OID 1.3.101.112
        // twice in its internal table (tag 373 = signature algorithm, tag 374 =
        // public-key type) and the lookup finds tag 374 first.  PK11_Verify
        // bypasses the OID lookup and uses the key's CKM_EDDSA mechanism
        // directly, signing/verifying the raw message atomically.
        let msg_item = SECItemStr {
            type_: SECItemType::siBuffer,
            data: tbs_der.as_ptr() as *mut _,
            len: tbs_der.len() as u32,
        };
        // SAFETY:
        // - `pub_key` is non-null and was returned by SECKEY_ExtractPublicKey.
        // - `sig_item.data` points into `signature_bits` which outlives this call.
        // - `msg_item.data` points into `tbs_der` which outlives this call.
        // - `wincx` is NULL (no PIN context required).
        unsafe { PK11_Verify(pub_key, &sig_item, &msg_item, ptr::null_mut()) }
    } else {
        // Hash-then-verify path: RSA, ECDSA, ML-DSA, and all other algorithms.
        // Build the SECAlgorithmID from the parsed OID value bytes and parameters.
        let alg_id = SECAlgorithmIDStr {
            algorithm: SECItemStr {
                type_: SECItemType::siDEROID,
                data: oid_value.as_ptr() as *mut _,
                len: oid_value.len() as u32,
            },
            parameters: SECItemStr {
                type_: SECItemType::siBuffer,
                data: if params.is_empty() {
                    ptr::null_mut()
                } else {
                    params.as_ptr() as *mut _
                },
                len: params.len() as u32,
            },
        };

        // SAFETY:
        // - `tbs_der` is a valid byte slice valid for the duration of this call.
        // - `pub_key` is a valid non-null pointer returned by SECKEY_ExtractPublicKey.
        // - `sig_item` and `alg_id` are stack-allocated; their `.data` pointers
        //   reference slices that outlive this call.
        // - `canon_alg` is NULL (NSS uses `sig_alg` directly).
        // - `wincx` is NULL (no PIN context required for server-side verification).
        unsafe {
            VFY_VerifyDataWithAlgorithmID(
                tbs_der.as_ptr(),
                tbs_der.len() as std::ffi::c_int,
                pub_key,
                &sig_item,
                &alg_id,
                ptr::null(),
                ptr::null_mut(),
            )
        }
    };

    // SAFETY: `pub_key` is non-null and was returned by SECKEY_ExtractPublicKey.
    unsafe { SECKEY_DestroyPublicKey(pub_key) };

    if status == nss_sys::SECSuccess {
        Ok(())
    } else {
        Err(NssVerifierError(
            "signature verification failed".to_string(),
        ))
    }
}

impl SignatureVerifier for NssSignatureVerifier {
    type Error = NssVerifierError;

    fn verify_certificate_signature(
        &self,
        tbs_der: &[u8],
        sig_alg_der: &[u8],
        signature_bits: &[u8],
        issuer_spki_der: &[u8],
    ) -> Result<(), NssVerifierError> {
        do_verify_signature(tbs_der, sig_alg_der, signature_bits, issuer_spki_der)
    }
}

impl ErasedSignatureVerifier for NssSignatureVerifier {
    fn verify_certificate_signature_erased(
        &self,
        tbs_der: &[u8],
        sig_alg_der: &[u8],
        signature_bits: &[u8],
        issuer_spki_der: &[u8],
    ) -> Result<(), PrivateKeyError> {
        do_verify_signature(tbs_der, sig_alg_der, signature_bits, issuer_spki_der)
            .map_err(PrivateKeyError::new)
    }
}