ring-native-ossl 0.1.8

A ring-compatible API backed by native-ossl (OpenSSL)
Documentation
//! HKDF key derivation, mirroring `ring::hkdf`.
//!
//! Implements the two-step HKDF construction (RFC 5869): [`Salt::extract`]
//! runs HKDF-Extract to produce a [`Prk`], and [`Prk::expand`] runs
//! HKDF-Expand to fill a caller-supplied buffer with output keying material.
//! PRK bytes are stored in [`native_ossl::util::SecretBuf`] and erased on drop.

use crate::error::Unspecified;
use native_ossl::digest::DigestAlg;
use native_ossl::kdf::{HkdfBuilder, HkdfMode};
use native_ossl::util::SecretBuf;

/// HKDF algorithm, wrapping a digest algorithm (mirrors `ring::hkdf::Algorithm`).
#[derive(Clone, Copy, Debug)]
pub struct Algorithm(pub(crate) &'static crate::digest::Algorithm);

pub static HKDF_SHA256: Algorithm = Algorithm(&crate::digest::SHA256);
pub static HKDF_SHA384: Algorithm = Algorithm(&crate::digest::SHA384);
pub static HKDF_SHA512: Algorithm = Algorithm(&crate::digest::SHA512);

impl Algorithm {
    #[must_use]
    pub fn hmac_algorithm(&self) -> crate::hmac::Algorithm {
        crate::hmac::Algorithm(self.0)
    }
}

/// Trait for HKDF output key types (mirrors `ring::hkdf::KeyType`).
pub trait KeyType {
    fn len(&self) -> usize;
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

/// HKDF Salt — holds salt bytes and algorithm for the extract step
/// (mirrors `ring::hkdf::Salt`).
#[derive(Debug)]
pub struct Salt {
    alg: Algorithm,
    salt: Vec<u8>,
}

impl Salt {
    #[must_use]
    pub fn new(algorithm: Algorithm, value: &[u8]) -> Self {
        Self {
            alg: algorithm,
            salt: value.to_vec(),
        }
    }

    /// HKDF-Extract: combine salt + IKM → PRK.
    ///
    /// # Panics
    ///
    /// Panics if OpenSSL cannot load the digest algorithm or the HKDF-Extract operation fails.
    #[must_use]
    pub fn extract(self, secret: &[u8]) -> Prk {
        let digest_alg = DigestAlg::fetch(self.alg.0.name, None)
            .unwrap_or_else(|e| panic!("OpenSSL digest unavailable: {e}"));
        let prk = HkdfBuilder::new(&digest_alg)
            .mode(HkdfMode::ExtractOnly)
            .key(secret)
            .salt(&self.salt)
            .derive_to_vec(self.alg.0.output_len)
            .unwrap_or_else(|e| panic!("HKDF extract failed: {e}"));
        Prk {
            alg: self.alg,
            prk: SecretBuf::new(prk),
        }
    }

    #[must_use]
    pub fn algorithm(&self) -> Algorithm {
        self.alg
    }
}

/// HKDF Pseudorandom Key — result of the extract step
/// (mirrors `ring::hkdf::Prk`).
pub struct Prk {
    alg: Algorithm,
    prk: SecretBuf,
}

impl std::fmt::Debug for Prk {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Prk")
            .field("alg", &self.alg)
            .finish_non_exhaustive()
    }
}

impl Prk {
    /// Create a `Prk` directly from pre-existing PRK bytes (e.g. for `ExpandOnly` use).
    #[must_use]
    pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self {
        Self {
            alg: algorithm,
            prk: SecretBuf::from_slice(value),
        }
    }

    /// HKDF-Expand: PRK + info → OKM of the given length.
    ///
    /// # Errors
    ///
    /// Returns `Unspecified` if the requested length is invalid.
    pub fn expand<'a, L: KeyType>(
        &'a self,
        info: &'a [&'a [u8]],
        len: L,
    ) -> Result<Okm<'a, L>, Unspecified> {
        Ok(Okm {
            prk: self,
            info,
            len,
        })
    }
}

/// HKDF Output Keying Material — lazy, fills on demand
/// (mirrors `ring::hkdf::Okm`).
pub struct Okm<'a, L: KeyType> {
    prk: &'a Prk,
    info: &'a [&'a [u8]],
    len: L,
}

impl<L: KeyType> Okm<'_, L> {
    #[must_use]
    pub fn len(&self) -> &L {
        &self.len
    }

    /// Fill `out` with the derived key material.
    ///
    /// # Errors
    ///
    /// Returns `Unspecified` if `out.len()` does not match the declared key length,
    /// or if the underlying OpenSSL HKDF-Expand operation fails.
    pub fn fill(self, out: &mut [u8]) -> Result<(), Unspecified> {
        if out.len() != self.len.len() {
            return Err(Unspecified);
        }
        let digest_alg = DigestAlg::fetch(self.prk.alg.0.name, None).map_err(|_| Unspecified)?;

        // Concatenate info slices.
        let info_concat: Vec<u8> = self.info.iter().flat_map(|s| s.iter().copied()).collect();

        HkdfBuilder::new(&digest_alg)
            .mode(HkdfMode::ExpandOnly)
            .key(self.prk.prk.as_ref())
            .info(&info_concat)
            .derive(out)
            .map_err(|_| Unspecified)
    }
}