synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! X.509 key identifier hashing (RFC 5280 / RFC 7093).

use super::errors::{NoKeyIdHasherError, PrivateKeyError};

// ── KeyIdMethod ───────────────────────────────────────────────────────────────

/// Method for computing an X.509 key identifier value.
///
/// RFC 5280 §4.2.1.2 originally defined a single method (SHA-1 of the
/// `subjectPublicKey` BIT STRING value).  RFC 7093 adds four supplementary
/// methods covering stronger hash algorithms and the full
/// `SubjectPublicKeyInfo` DER encoding as input:
///
/// | Method | Input | Hash | Output |
/// |--------|-------|------|--------|
/// | RFC 5280 | BIT STRING value of `subjectPublicKey` | SHA-1 | full 20 bytes |
/// | RFC 7093 §2 m1 | BIT STRING value of `subjectPublicKey` | SHA-256 | leftmost 160 bits (20 bytes) |
/// | RFC 7093 §2 m2 | BIT STRING value of `subjectPublicKey` | SHA-384 | leftmost 160 bits (20 bytes) |
/// | RFC 7093 §2 m3 | BIT STRING value of `subjectPublicKey` | SHA-512 | leftmost 160 bits (20 bytes) |
/// | RFC 7093 §2 m4 | Full `SubjectPublicKeyInfo` DER encoding | configurable | full hash bytes |
#[derive(Clone, Debug)]
pub enum KeyIdMethod {
    /// RFC 5280 §4.2.1.2: SHA-1 of the BIT STRING value of `subjectPublicKey` (full 20 bytes).
    Rfc5280Sha1,
    /// RFC 7093 §2 method 1: SHA-256 of the BIT STRING value, leftmost 160 bits (20 bytes).
    Rfc7093Method1Sha256,
    /// RFC 7093 §2 method 2: SHA-384 of the BIT STRING value, leftmost 160 bits (20 bytes).
    Rfc7093Method2Sha384,
    /// RFC 7093 §2 method 3: SHA-512 of the BIT STRING value, leftmost 160 bits (20 bytes).
    Rfc7093Method3Sha512,
    /// RFC 7093 §2 method 4: hash of the full `SubjectPublicKeyInfo` DER encoding.
    ///
    /// `algorithm_oid` selects the hash algorithm; the full hash value is used
    /// (no truncation).  The RFC recommends SHA-256 as the example algorithm.
    Rfc7093Method4 { algorithm_oid: Vec<u32> },
}

impl KeyIdMethod {
    /// OID component array of the hash algorithm used by this method.
    pub fn algorithm_oid(&self) -> &[u32] {
        match self {
            Self::Rfc5280Sha1 => crate::ID_SHA1,
            Self::Rfc7093Method1Sha256 => crate::ID_SHA256,
            Self::Rfc7093Method2Sha384 => crate::ID_SHA384,
            Self::Rfc7093Method3Sha512 => crate::ID_SHA512,
            Self::Rfc7093Method4 { algorithm_oid } => algorithm_oid.as_slice(),
        }
    }

    /// Whether this method hashes the full `SubjectPublicKeyInfo` DER encoding
    /// rather than the BIT STRING value of `subjectPublicKey`.
    ///
    /// Returns `true` only for [`Rfc7093Method4`][Self::Rfc7093Method4].
    pub fn uses_full_spki_der(&self) -> bool {
        matches!(self, Self::Rfc7093Method4 { .. })
    }

    /// Apply the output-length rule for this method.
    ///
    /// RFC 7093 methods 1–3 truncate the hash to its leftmost 160 bits
    /// (20 bytes).  All other methods return the full hash unchanged.
    pub fn apply_output_length(&self, hash: Vec<u8>) -> Vec<u8> {
        match self {
            Self::Rfc7093Method1Sha256
            | Self::Rfc7093Method2Sha384
            | Self::Rfc7093Method3Sha512 => hash.into_iter().take(20).collect(),
            _ => hash,
        }
    }
}

// ── KeyIdHasher ───────────────────────────────────────────────────────────────

/// Compute a cryptographic hash for use as an X.509 key identifier.
///
/// Implementations call into a crypto backend (e.g. OpenSSL) to compute
/// the hash requested by a [`KeyIdMethod`].  Implementations should support
/// at minimum:
///
/// - `ID_SHA1` — required for [`KeyIdMethod::Rfc5280Sha1`]
/// - `ID_SHA256`, `ID_SHA384`, `ID_SHA512` — required for RFC 7093 methods 1–4
///
/// The hash result is returned in full; any truncation required by a specific
/// method is applied by the caller via [`KeyIdMethod::apply_output_length`].
pub trait KeyIdHasher {
    /// Error type returned on hash failure.
    type Error: std::error::Error + Send + Sync + 'static;

    /// Compute a hash of `data` using the algorithm identified by `algorithm_oid`.
    ///
    /// `algorithm_oid` is an OID component array, e.g. `crate::ID_SHA1`.
    /// Returns the full un-truncated hash bytes on success.
    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, Self::Error>;
}

/// Sentinel [`KeyIdHasher`] that always returns an error.
///
/// Use as a placeholder when no crypto backend is available.
pub struct NoKeyIdHasher;

impl KeyIdHasher for NoKeyIdHasher {
    type Error = NoKeyIdHasherError;

    fn hash(&self, _algorithm_oid: &[u32], _data: &[u8]) -> Result<Vec<u8>, NoKeyIdHasherError> {
        Err(NoKeyIdHasherError)
    }
}

// ── ErasedKeyIdHasher ─────────────────────────────────────────────────────────

/// Object-safe [`KeyIdHasher`] variant with a type-erased error.
///
/// Used as the return type of [`default_key_id_hasher`] so that callers
/// receive a `Box<dyn ErasedKeyIdHasher>` without naming the backend type.
/// The [`encode_subject_key_identifier`] and [`encode_authority_key_identifier`]
/// helpers accept any `&dyn ErasedKeyIdHasher` via the blanket [`KeyIdHasher`]
/// impl below.
///
/// [`encode_subject_key_identifier`]: crate::encode_subject_key_identifier
/// [`encode_authority_key_identifier`]: crate::encode_authority_key_identifier
pub trait ErasedKeyIdHasher {
    /// Hash `data` with the algorithm identified by `algorithm_oid`;
    /// returns [`PrivateKeyError`] on failure.
    fn hash_erased(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, PrivateKeyError>;
}

/// Blanket impl: `&dyn ErasedKeyIdHasher` implements [`KeyIdHasher`].
impl KeyIdHasher for dyn ErasedKeyIdHasher + '_ {
    type Error = PrivateKeyError;

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

/// Blanket impl: `Box<dyn ErasedKeyIdHasher>` implements [`KeyIdHasher`].
///
/// This lets a `Box<dyn ErasedKeyIdHasher>` (as returned by
/// [`default_key_id_hasher`]) be passed directly to
/// [`crate::encode_subject_key_identifier`] and
/// [`crate::encode_authority_key_identifier`] without calling `.as_ref()`.
impl KeyIdHasher for Box<dyn ErasedKeyIdHasher> {
    type Error = PrivateKeyError;

    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
        self.as_ref().hash_erased(algorithm_oid, data)
    }
}

/// Return a key-identifier hasher backed by the default crypto backend.
///
/// When the `openssl` feature is enabled, returns an OpenSSL-backed hasher.
/// Returns a [`PrivateKeyError`]-yielding stub when no backend is available.
///
/// The returned object implements [`KeyIdHasher`] via a blanket impl on
/// `dyn ErasedKeyIdHasher`.
///
/// # Example
///
/// ```rust,ignore
/// use synta_certificate::{default_key_id_hasher, encode_subject_key_identifier, KeyIdMethod};
///
/// let hasher = default_key_id_hasher();
/// let ski_der = encode_subject_key_identifier(&spki_der, KeyIdMethod::Rfc5280Sha1, hasher.as_ref())?;
/// ```
pub fn default_key_id_hasher() -> Box<dyn ErasedKeyIdHasher> {
    #[cfg(all(feature = "nss", not(feature = "openssl")))]
    {
        crate::nss_backend::nss_key_id_hasher()
    }
    #[cfg(feature = "openssl")]
    {
        crate::openssl_backend::openssl_key_id_hasher()
    }
    #[cfg(not(any(feature = "openssl", feature = "nss")))]
    {
        Box::new(NoKeyIdHasherErased)
    }
}

/// No-op erased hasher used when no backend is compiled in.
#[cfg(not(any(feature = "openssl", feature = "nss")))]
struct NoKeyIdHasherErased;

#[cfg(not(any(feature = "openssl", feature = "nss")))]
impl ErasedKeyIdHasher for NoKeyIdHasherErased {
    fn hash_erased(
        &self,
        _algorithm_oid: &[u32],
        _data: &[u8],
    ) -> Result<Vec<u8>, PrivateKeyError> {
        Err(PrivateKeyError::new(NoKeyIdHasherError))
    }
}