synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! NSS-backed [`crate::SignatureVerifier`], [`crate::crypto::KeyIdHasher`],
//! and [`crate::crypto::ErasedCertificateSigner`] implementations.
//!
//! ## Signature verification ([`NssSignatureVerifier`])
//!
//! Verifies X.509 certificate signatures by delegating to
//! `VFY_VerifyDataWithAlgorithmID` with the raw `AlgorithmIdentifier` from the
//! certificate.  NSS handles algorithm dispatch, so all algorithms supported by
//! the installed NSS library work automatically.  Includes:
//!
//! - 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)
//!
//! ## Certificate signing ([`NssSigner`])
//!
//! Signs TBSCertificate, TBSCertList, and ResponseData blobs by importing the
//! PKCS#8 private key into the NSS internal in-memory slot.  Dispatches to the
//! correct NSS primitive based on the algorithm:
//! - ECDSA, RSA PKCS#1 v1.5, ML-DSA: `SEC_SignData` (hash-then-sign).
//! - Ed25519: `PK11_Sign` with the raw TBS message (`CKM_EDDSA` on the NSS
//!   internal softokn slot performs hash-and-sign atomically).  `SEC_SignData`
//!   cannot be used: NSS's `SGN_Update` does not support multi-part `CKM_EDDSA`.
//! Ed448 is not supported (no `SEC_OID_ED448_SIGNATURE` in NSS ≤ 3.121).
//!
//! ## Key identifier hashing ([`NssKeyIdHasher`])
//!
//! Computes SHA-1, SHA-256, SHA-384, and SHA-512 hashes via NSS hash buffer
//! functions for use in Subject Key Identifier and Authority Key Identifier
//! extensions (RFC 5280 / RFC 7093).

pub mod composite;
pub(super) mod ffi;
pub mod hsm_signing;
pub mod key_ops;
pub mod rsa_transport;
pub mod signature;
pub mod signing;
pub mod symmetric;

pub(crate) use composite::{composite_mldsa_signer_from_pkcs8, priv_generate_composite_mldsa};
pub(crate) use hsm_signing::{nss_hsm_signer, priv_load_from_pkcs11_uri_nss};

pub(crate) use rsa_transport::{
    nss_rsa_oaep_decrypt, nss_rsa_oaep_encrypt, nss_rsa_pkcs1v15_decrypt, nss_rsa_pkcs1v15_encrypt,
};
pub use signature::{NssSignatureVerifier, NssVerifierError};
pub(crate) use signing::NssUnsupportedSigner;
pub use signing::{NssSigner, NssSignerError};
pub(crate) use symmetric::{
    nss_block_cipher_provider, nss_data_hasher, nss_hmac_provider, nss_pbkdf2_provider,
    nss_secure_random, nss_streaming_hasher, nss_streaming_hmac_provider,
};
pub use symmetric::{NssDataHasher, NssDataHasherError, NssHmacError, NssHmacProvider};

// ── One-time NSS initialisation ───────────────────────────────────────────────

use std::sync::OnceLock;

/// Lazily initialises NSS once per process with no database (`NSS_NoDB_Init`).
///
/// Returns `true` on success.  A second call is a no-op and also returns `true`.
pub(crate) fn ensure_nss_init() -> bool {
    static NSS_INIT: OnceLock<bool> = OnceLock::new();
    *NSS_INIT.get_or_init(|| {
        // SAFETY: `NSS_NoDB_Init` is safe to call once; passing a null pointer
        // selects the default (no persistent database) mode.
        let status = unsafe { nss_sys::NSS_NoDB_Init(std::ptr::null()) };
        status == nss_sys::SECSuccess
    })
}

use crate::crypto::{ErasedKeyIdHasher, KeyIdHasher, PrivateKeyError};
use crate::oids;

// ── Shared NSS FFI (all declarations live in ffi; import what this module needs) ─

use ffi::{
    PK11_HashBuf, SECOidTag, SEC_OID_MD5, SEC_OID_SHA1, SEC_OID_SHA224, SEC_OID_SHA256,
    SEC_OID_SHA384, SEC_OID_SHA3_256, SEC_OID_SHA3_384, SEC_OID_SHA3_512, SEC_OID_SHA512,
};

// ── Shared algorithm-name helper ──────────────────────────────────────────────

/// Map a string algorithm name (e.g. `"sha256"`) to the corresponding NSS
/// `SECOidTag` and output length in bytes.
///
/// Returns `None` for unsupported algorithm names.  Used by both
/// [`NssDataHasher`](symmetric::NssDataHasher) and any other NSS code that
/// needs to hash data identified by a string name.
pub(super) fn alg_name_to_hash_tag(algorithm: &str) -> Option<(SECOidTag, usize)> {
    match algorithm {
        "md5" => Some((SEC_OID_MD5, 16)),
        "sha1" => Some((SEC_OID_SHA1, 20)),
        "sha224" => Some((SEC_OID_SHA224, 28)),
        "sha256" => Some((SEC_OID_SHA256, 32)),
        "sha384" => Some((SEC_OID_SHA384, 48)),
        "sha512" => Some((SEC_OID_SHA512, 64)),
        "sha3-256" => Some((SEC_OID_SHA3_256, 32)),
        "sha3-384" => Some((SEC_OID_SHA3_384, 48)),
        "sha3-512" => Some((SEC_OID_SHA3_512, 64)),
        _ => None,
    }
}

// ── NssKeyIdHasher ────────────────────────────────────────────────────────────

/// NSS-backed key identifier hasher.
///
/// Implements [`KeyIdHasher`] using NSS hash buffer functions to support
/// SHA-1, SHA-256, SHA-384, and SHA-512.  Used by
/// [`encode_subject_key_identifier`] and [`encode_authority_key_identifier`]
/// when the `nss` feature is enabled.
///
/// [`encode_subject_key_identifier`]: crate::encode_subject_key_identifier
/// [`encode_authority_key_identifier`]: crate::encode_authority_key_identifier
pub struct NssKeyIdHasher;

/// Error type for [`NssKeyIdHasher`].
#[derive(Debug)]
pub struct NssKeyIdHasherError(String);

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

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

impl KeyIdHasher for NssKeyIdHasher {
    type Error = NssKeyIdHasherError;

    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, NssKeyIdHasherError> {
        if !ensure_nss_init() {
            return Err(NssKeyIdHasherError("NSS initialisation failed".to_string()));
        }
        let (nss_tag, out_len) = if algorithm_oid == oids::ID_SHA1 {
            (SEC_OID_SHA1, 20usize)
        } else if algorithm_oid == oids::ID_SHA256 {
            (SEC_OID_SHA256, 32usize)
        } else if algorithm_oid == oids::ID_SHA384 {
            (SEC_OID_SHA384, 48usize)
        } else if algorithm_oid == oids::ID_SHA512 {
            (SEC_OID_SHA512, 64usize)
        } else {
            return Err(NssKeyIdHasherError(format!(
                "unsupported hash algorithm OID: {:?}",
                algorithm_oid
            )));
        };

        let mut out = vec![0u8; out_len];

        // SAFETY:
        // - `out` is a valid writable buffer of `out_len` bytes, which matches
        //   the output size for `nss_tag`.
        // - `data` is a valid readable slice for the duration of this call.
        // - `PK11_HashBuf` is exported from `libnss3` (since NSS 3.2) and
        //   writes exactly `out_len` bytes to `out` on success.
        let status = unsafe {
            PK11_HashBuf(
                nss_tag,
                out.as_mut_ptr(),
                data.as_ptr(),
                data.len() as std::ffi::c_int,
            )
        };

        if status == nss_sys::SECSuccess {
            Ok(out)
        } else {
            Err(NssKeyIdHasherError("PK11_HashBuf failed".to_string()))
        }
    }
}

impl ErasedKeyIdHasher for NssKeyIdHasher {
    fn hash_erased(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
        self.hash(algorithm_oid, data).map_err(PrivateKeyError::new)
    }
}

// ── pub(crate) factory functions ──────────────────────────────────────────────

/// Return a boxed [`NssSignatureVerifier`] as an erased signature verifier.
///
/// Used by [`crate::crypto::BackendPublicKey::verify_signature`] and other
/// callers that need an erased verifier without naming the NSS-specific type.
pub(crate) fn nss_signature_verifier() -> Box<dyn crate::crypto::ErasedSignatureVerifier> {
    Box::new(NssSignatureVerifier)
}

/// Return a boxed [`NssKeyIdHasher`] as an erased key-identifier hasher.
///
/// Used by [`crate::default_key_id_hasher`] when the `nss` feature is enabled.
pub(crate) fn nss_key_id_hasher() -> Box<dyn ErasedKeyIdHasher> {
    Box::new(NssKeyIdHasher)
}