ring-native-ossl 0.1.10

A ring-compatible API backed by native-ossl (OpenSSL)
Documentation
//! Authenticated encryption with associated data (AEAD), mirroring `ring::aead`.
//!
//! Provides AES-128-GCM, AES-256-GCM, and ChaCha20-Poly1305 via OpenSSL.
//! Key material is stored in [`native_ossl::util::SecretBuf`] and erased on
//! drop.  The nonce is always 12 bytes; the authentication tag is always 16
//! bytes for all supported algorithms.

use crate::error::Unspecified;
use native_ossl::cipher::{AeadDecryptCtx, AeadEncryptCtx, CipherAlg};
use native_ossl::util::SecretBuf;
use std::ffi::CStr;

pub const MAX_TAG_LEN: usize = 16;
pub const NONCE_LEN: usize = 12;

/// AEAD algorithm descriptor (mirrors `ring::aead::Algorithm`).
#[derive(Debug)]
pub struct Algorithm {
    cipher_name: &'static CStr,
    key_len: usize,
    tag_len: usize,
}

pub static AES_128_GCM: Algorithm = Algorithm {
    cipher_name: c"AES-128-GCM",
    key_len: 16,
    tag_len: 16,
};
pub static AES_256_GCM: Algorithm = Algorithm {
    cipher_name: c"AES-256-GCM",
    key_len: 32,
    tag_len: 16,
};
pub static CHACHA20_POLY1305: Algorithm = Algorithm {
    cipher_name: c"ChaCha20-Poly1305",
    key_len: 32,
    tag_len: 16,
};

impl Algorithm {
    #[must_use]
    pub fn key_len(&self) -> usize {
        self.key_len
    }

    #[must_use]
    pub fn tag_len(&self) -> usize {
        self.tag_len
    }

    #[must_use]
    pub fn nonce_len(&self) -> usize {
        NONCE_LEN
    }
}

/// A 12-byte AEAD nonce (mirrors `ring::aead::Nonce`).
#[derive(Clone, Copy, Debug)]
pub struct Nonce(pub [u8; NONCE_LEN]);

impl Nonce {
    #[must_use]
    pub fn assume_unique_for_key(bytes: [u8; NONCE_LEN]) -> Self {
        Self(bytes)
    }

    /// # Errors
    ///
    /// Returns `Unspecified` if `bytes` is not exactly `NONCE_LEN` bytes long.
    pub fn try_assume_unique_for_key(bytes: &[u8]) -> Result<Self, Unspecified> {
        let arr: [u8; NONCE_LEN] = bytes.try_into().map_err(|_| Unspecified)?;
        Ok(Self(arr))
    }
}

/// Additional associated data (mirrors `ring::aead::Aad`).
pub struct Aad<A>(pub A);

impl<A: AsRef<[u8]>> Aad<A> {
    pub fn from(val: A) -> Self {
        Aad(val)
    }
}

impl Aad<[u8; 0]> {
    #[must_use]
    pub fn empty() -> Self {
        Aad([])
    }
}

/// An AEAD key not yet bound to a direction (mirrors `ring::aead::UnboundKey`).
pub struct UnboundKey {
    alg: &'static Algorithm,
    key: SecretBuf,
}

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

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

impl UnboundKey {
    /// # Errors
    ///
    /// Returns `Unspecified` if `key_bytes` length does not match the algorithm's key length.
    pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self, Unspecified> {
        if key_bytes.len() != algorithm.key_len {
            return Err(Unspecified);
        }
        Ok(Self {
            alg: algorithm,
            key: SecretBuf::from_slice(key_bytes),
        })
    }

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

/// A sealing/opening key that does not enforce nonce uniqueness
/// (mirrors `ring::aead::LessSafeKey`).
pub struct LessSafeKey {
    alg: &'static Algorithm,
    key: SecretBuf,
}

impl LessSafeKey {
    #[must_use]
    pub fn new(key: UnboundKey) -> Self {
        Self {
            alg: key.alg,
            key: key.key,
        }
    }

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

    /// Encrypt `in_out` in-place and append the authentication tag.
    ///
    /// # Errors
    ///
    /// Returns `Unspecified` if the underlying OpenSSL AEAD operation fails.
    pub fn seal_in_place_append_tag<A: AsRef<[u8]>>(
        &self,
        nonce: Nonce,
        aad: &Aad<A>,
        in_out: &mut Vec<u8>,
    ) -> Result<(), Unspecified> {
        let cipher_alg = CipherAlg::fetch(self.alg.cipher_name, None).map_err(|_| Unspecified)?;
        let mut enc = AeadEncryptCtx::new(&cipher_alg, self.key.as_ref(), &nonce.0, None)
            .map_err(|_| Unspecified)?;
        enc.set_aad(aad.0.as_ref()).map_err(|_| Unspecified)?;

        let plaintext_len = in_out.len();
        // encrypt in a temporary buffer, then write back
        let mut ciphertext = vec![0u8; plaintext_len + 32];
        let n = enc
            .update(in_out, &mut ciphertext)
            .map_err(|_| Unspecified)?;
        let n2 = enc
            .finalize(&mut ciphertext[n..])
            .map_err(|_| Unspecified)?;
        let ct_len = n + n2;

        let mut tag = [0u8; 16];
        enc.tag(&mut tag[..self.alg.tag_len])
            .map_err(|_| Unspecified)?;

        in_out.clear();
        in_out.extend_from_slice(&ciphertext[..ct_len]);
        in_out.extend_from_slice(&tag[..self.alg.tag_len]);
        Ok(())
    }

    /// Decrypt `in_out` in-place; on success returns the plaintext slice.
    ///
    /// `in_out` must end with the authentication tag.
    ///
    /// # Errors
    ///
    /// Returns `Unspecified` if authentication fails or the OpenSSL operation fails.
    pub fn open_in_place<'in_out, A: AsRef<[u8]>>(
        &self,
        nonce: Nonce,
        aad: &Aad<A>,
        in_out: &'in_out mut [u8],
    ) -> Result<&'in_out mut [u8], Unspecified> {
        let tag_len = self.alg.tag_len;
        if in_out.len() < tag_len {
            return Err(Unspecified);
        }
        let ciphertext_len = in_out.len() - tag_len;
        let (ciphertext, tag) = in_out.split_at_mut(ciphertext_len);
        let tag = &*tag; // shared borrow

        let cipher_alg = CipherAlg::fetch(self.alg.cipher_name, None).map_err(|_| Unspecified)?;
        let mut dec = AeadDecryptCtx::new(&cipher_alg, self.key.as_ref(), &nonce.0, None)
            .map_err(|_| Unspecified)?;
        dec.set_aad(aad.0.as_ref()).map_err(|_| Unspecified)?;
        dec.set_tag(tag).map_err(|_| Unspecified)?;

        let mut plaintext = vec![0u8; ciphertext_len + 32];
        let n = dec
            .update(ciphertext, &mut plaintext)
            .map_err(|_| Unspecified)?;
        let n2 = dec.finalize(&mut plaintext[n..]).map_err(|_| Unspecified)?;
        let pt_len = n + n2;

        in_out[..pt_len].copy_from_slice(&plaintext[..pt_len]);
        Ok(&mut in_out[..pt_len])
    }

    /// Variant that accepts `in_out` starting at `in_prefix_len` bytes of prefix.
    ///
    /// # Errors
    ///
    /// Returns `Unspecified` if authentication fails or the buffer is too short.
    pub fn open_within<'in_out, A: AsRef<[u8]>>(
        &self,
        nonce: Nonce,
        aad: &Aad<A>,
        in_out: &'in_out mut [u8],
        in_prefix_len: usize,
    ) -> Result<&'in_out mut [u8], Unspecified> {
        let tag_len = self.alg.tag_len;
        let total = in_out.len();
        if total < in_prefix_len + tag_len {
            return Err(Unspecified);
        }
        let ciphertext_and_tag = &mut in_out[in_prefix_len..];
        let result = self.open_in_place(nonce, aad, ciphertext_and_tag)?;
        let pt_len = result.len();
        // move plaintext to start of buffer
        in_out.copy_within(in_prefix_len..in_prefix_len + pt_len, 0);
        Ok(&mut in_out[..pt_len])
    }
}