synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! Shared utilities used by multiple crypto backend implementations.

// Split a DER-encoded `AlgorithmIdentifier` SEQUENCE into the OID, raw OID
// value bytes, and the optional parameters TLV.
//
// # Arguments
//
// - `der` — a complete DER-encoded `AlgorithmIdentifier` SEQUENCE.
// - `to_err` — maps a [`synta::Error`] to the caller's concrete error type.
//
// # Returns
//
// `(oid, oid_value, params_der)` where all are zero-copy slices or parsed values
// derived from `der`:
//
// - `oid` — the parsed `ObjectIdentifier` (usable via `.components()`).
// - `oid_value` — raw OID value bytes (without the `0x06` tag and length prefix);
//   suitable as `SECAlgorithmID.algorithm.data` in NSS.
// - `params_der` — complete DER TLV of the parameters element, or an empty
//   slice when no parameters are present.
//
// # Usage
//
// ```rust,ignore
// // In a backend using BackendError that implements From<synta::Error>:
// let (oid, _oid_value, params) = split_alg_id(algorithm_der, Into::into)?;
//
// // In a backend that needs raw OID bytes (e.g. NSS):
// let (_, oid_value, params) = split_alg_id(algorithm_der, |e| MyError(e.to_string()))?;
// ```

#[cfg(any(feature = "openssl", feature = "nss"))]
use synta::ObjectIdentifier;

/// Extract the algorithm OID from a DER-encoded PKCS#8 PrivateKeyInfo.
#[cfg(any(feature = "openssl", feature = "nss"))]
pub(crate) fn pkcs8_key_oid(pkcs8_der: &[u8]) -> Option<ObjectIdentifier> {
    crate::pkcs8_types::PrivateKeyInfo::from_der(pkcs8_der)
        .ok()
        .map(|pki| pki.private_key_algorithm.algorithm)
}

/// Map a public-key algorithm OID to a conventional lowercase key-type string.
///
/// Returns `"rsa"`, `"ec"`, `"ed25519"`, `"ed448"`, `"dsa"`, one of the
/// lowercase PQC variant strings (`"ml-dsa-44"` etc.), `"composite-mldsa"` for
/// any composite ML-DSA variant, or `"unknown"`.
#[cfg(any(feature = "openssl", feature = "nss"))]
pub(crate) fn key_type_from_oid(oid: &ObjectIdentifier) -> &'static str {
    use crate::names;
    // Check composite ML-DSA arc before the identify_public_key_algorithm match,
    // since composite OIDs are in a separate arc not returned by that function.
    let comps = oid.components();
    if comps.len() == 9 && comps.starts_with(crate::oids::COMPOSITE_MLDSA_ARC) {
        return "composite-mldsa";
    }
    match crate::identify_public_key_algorithm(oid) {
        Some(names::RSA) => "rsa",
        Some(names::ECDSA) => "ec",
        Some(names::ED25519) => "ed25519",
        Some(names::ED448) => "ed448",
        Some(names::DSA) => "dsa",
        Some(names::ML_DSA_44) => "ml-dsa-44",
        Some(names::ML_DSA_65) => "ml-dsa-65",
        Some(names::ML_DSA_87) => "ml-dsa-87",
        Some(names::ML_KEM_512) => "ml-kem-512",
        Some(names::ML_KEM_768) => "ml-kem-768",
        Some(names::ML_KEM_1024) => "ml-kem-1024",
        None | Some(_) => "unknown",
    }
}

/// Derive the key type string from PKCS#8 DER using OID parsing only.
#[cfg(any(feature = "openssl", feature = "nss"))]
pub(crate) fn key_type_from_pkcs8(pkcs8_der: &[u8]) -> &'static str {
    pkcs8_key_oid(pkcs8_der)
        .as_ref()
        .map(key_type_from_oid)
        .unwrap_or("unknown")
}

#[cfg(any(feature = "openssl", feature = "nss"))]
use synta::{Decoder, Encoding};

/// Compute a DER-encoded signing `AlgorithmIdentifier` from a PKCS#8 private
/// key and a hash algorithm name string.
///
/// Parses the key type OID from the PKCS#8 `PrivateKeyInfo.algorithm` field,
/// then delegates to [`crate::signing_algorithm_der`] to map it together with
/// `hash_algo` to the signing OID and produce the DER encoding.
///
/// Returns `None` if the PKCS#8 DER is malformed, the key type is unrecognised,
/// or `hash_algo` is not valid for the key type.
#[cfg(all(feature = "nss", not(feature = "openssl")))]
pub(crate) fn sig_alg_der_from_pkcs8(pkcs8_der: &[u8], hash_algo: &str) -> Option<Vec<u8>> {
    use synta::{Encoding, Integer};

    // PKCS#8 PrivateKeyInfo:
    //   SEQUENCE {
    //     INTEGER (version = 0)
    //     SEQUENCE { OID [params] }   -- key AlgorithmIdentifier
    //     OCTET STRING                -- private key bytes
    //   }
    let mut dec = synta::Decoder::new(pkcs8_der, Encoding::Der);

    // Enter the outer SEQUENCE (discard tag + length).
    dec.read_tag().ok()?;
    dec.read_length().ok()?.definite().ok()?;

    // Skip the version INTEGER (always 02 01 00).
    let _: Integer = dec.decode().ok()?;

    // Capture the key AlgorithmIdentifier SEQUENCE start position.
    let alg_start = dec.position();
    dec.read_tag().ok()?;
    let alg_content_len = dec.read_length().ok()?.definite().ok()?;
    let alg_end = dec.position() + alg_content_len;

    // Re-enter the AlgorithmIdentifier to read the key OID.
    let mut alg_dec = synta::Decoder::new(&pkcs8_der[alg_start..alg_end], Encoding::Der);
    alg_dec.read_tag().ok()?; // SEQUENCE tag
    alg_dec.read_length().ok()?.definite().ok()?; // SEQUENCE length
    let key_oid: synta::ObjectIdentifier = alg_dec.decode().ok()?;

    crate::signing_algorithm_der(&key_oid, hash_algo)
}

#[cfg(any(feature = "openssl", feature = "nss"))]
pub(crate) fn split_alg_id<E>(
    der: &[u8],
    to_err: impl Fn(synta::Error) -> E,
) -> Result<(ObjectIdentifier, &[u8], &[u8]), E> {
    // Enter the outer SEQUENCE.
    let mut outer = Decoder::new(der, Encoding::Der);
    outer.read_tag().map_err(&to_err)?;
    let seq_len = outer
        .read_length()
        .and_then(|l| l.definite())
        .map_err(&to_err)?;
    let seq_start = outer.position();

    // The SEQUENCE body.
    let seq_body = &der[seq_start..seq_start + seq_len];

    // Read the OID tag + length to locate the raw OID value bytes within seq_body.
    let mut inner = Decoder::new(seq_body, Encoding::Der);
    inner.read_tag().map_err(&to_err)?;
    let oid_len = inner
        .read_length()
        .and_then(|l| l.definite())
        .map_err(&to_err)?;
    let oid_value_start = inner.position();
    let oid_value = &seq_body[oid_value_start..oid_value_start + oid_len];

    // Decode the full OID element (re-reads from the top of seq_body).
    let mut inner2 = Decoder::new(seq_body, Encoding::Der);
    let oid: ObjectIdentifier = inner2.decode().map_err(&to_err)?;
    let params_start = inner2.position();

    // Everything remaining in the SEQUENCE body is the parameters TLV (or empty).
    Ok((oid, oid_value, &seq_body[params_start..]))
}