dryoc 1.0.0

Don't Roll Your Own Crypto: pure-Rust, hard to misuse cryptography library
Documentation
//! Precalculated secret key for use with `precalc_*` functions in
//! [`crate::dryocbox::DryocBox`]
//!
//! Precalculation avoids repeating the public-key operation when encrypting or
//! decrypting multiple messages between the same sender and receiver.
use std::fmt;

use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::constants::{
    CRYPTO_BOX_BEFORENMBYTES, CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES,
};
use crate::error::Error;
use crate::types::{ByteArray, Bytes, MutByteArray, MutBytes, StackByteArray};

type InnerKey = StackByteArray<CRYPTO_BOX_BEFORENMBYTES>;

/// Precalculated secret key for use with `precalc_*` functions in
/// [`crate::dryocbox::DryocBox`].
///
/// Use `precalc_*` functions to encrypt or decrypt multiple messages between
/// the same sender and receiver. They reuse this shared secret instead of
/// repeating the public-key operation for every message.
///
/// Using precalculated secret keys is compatible with libsodium's
/// `crypto_box_beforenm`.
#[derive(Zeroize, ZeroizeOnDrop, Clone)]
pub struct PrecalcSecretKey<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize>(InnerKey);

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> fmt::Debug
    for PrecalcSecretKey<InnerKey>
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("PrecalcSecretKey")
            .field(&"[REDACTED]")
            .finish()
    }
}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> PartialEq
    for PrecalcSecretKey<InnerKey>
{
    fn eq(&self, other: &Self) -> bool {
        self.0.as_slice().ct_eq(other.0.as_slice()).into()
    }
}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> Eq for PrecalcSecretKey<InnerKey> {}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Bytes + Zeroize> Bytes
    for PrecalcSecretKey<InnerKey>
{
    #[inline]
    fn as_slice(&self) -> &[u8] {
        self.0.as_slice()
    }

    #[inline]
    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    #[inline]
    fn len(&self) -> usize {
        self.0.len()
    }
}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> ByteArray<CRYPTO_BOX_BEFORENMBYTES>
    for PrecalcSecretKey<InnerKey>
{
    #[inline]
    fn as_array(&self) -> &[u8; CRYPTO_BOX_BEFORENMBYTES] {
        self.0.as_array()
    }
}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize + MutBytes> MutBytes
    for PrecalcSecretKey<InnerKey>
{
    #[inline]
    fn as_mut_slice(&mut self) -> &mut [u8] {
        self.0.as_mut_slice()
    }

    #[inline]
    fn copy_from_slice(&mut self, other: &[u8]) {
        self.0.copy_from_slice(other);
    }
}

impl<InnerKey: MutByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize>
    MutByteArray<CRYPTO_BOX_BEFORENMBYTES> for PrecalcSecretKey<InnerKey>
{
    #[inline]
    fn as_mut_array(&mut self) -> &mut [u8; CRYPTO_BOX_BEFORENMBYTES] {
        self.0.as_mut_array()
    }
}

impl PrecalcSecretKey<InnerKey> {
    /// Computes a stack-allocated shared secret key for the given
    /// `third_party_public_key` and `secret_key`.
    ///
    /// Compatible with libsodium's `crypto_box_beforenm`.
    ///
    /// # Errors
    ///
    /// Returns an error if `third_party_public_key` is an unacceptable
    /// low-order point.
    #[inline]
    pub fn precalculate<
        ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
        SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
    >(
        third_party_public_key: &ThirdPartyPublicKey,
        secret_key: &SecretKey,
    ) -> Result<Self, Error> {
        use crate::classic::crypto_box::crypto_box_beforenm;

        Ok(Self(
            crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?.into(),
        ))
    }
}

#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
pub mod protected {
    //! # Protected memory for [`PrecalcSecretKey`]
    use super::*;
    pub use crate::protected::*;

    type InnerKey = HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>;

    impl PrecalcSecretKey<Locked<InnerKey>> {
        /// Computes a heap-allocated, page-aligned, locked shared secret key
        /// for the given `third_party_public_key` and `secret_key`.
        ///
        /// Compatible with libsodium's `crypto_box_beforenm`.
        ///
        /// # Errors
        ///
        /// Returns an error if `third_party_public_key` is an unacceptable
        /// low-order point or the protected allocation cannot be locked.
        ///
        /// # Panics
        ///
        /// Panics if the page-aligned allocation cannot be created or its size
        /// cannot be represented with guard pages.
        pub fn precalculate_locked<
            ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
            SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
        >(
            third_party_public_key: &ThirdPartyPublicKey,
            secret_key: &SecretKey,
        ) -> Result<Self, Error> {
            use crate::classic::crypto_box::crypto_box_beforenm;

            let mut precalc = HeapByteArray::<CRYPTO_BOX_BEFORENMBYTES>::new_locked()?;
            let mut key =
                crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?;

            precalc.copy_from_slice(&key);
            key.zeroize();

            Ok(PrecalcSecretKey(precalc))
        }
    }

    impl PrecalcSecretKey<LockedRO<InnerKey>> {
        /// Computes a heap-allocated, page-aligned, locked, read-only shared
        /// secret key for the given `third_party_public_key` and
        /// `secret_key`.
        ///
        /// Compatible with libsodium's `crypto_box_beforenm`.
        ///
        /// # Errors
        ///
        /// Returns an error if `third_party_public_key` is an unacceptable
        /// low-order point, the protected allocation cannot be locked, or its
        /// page permissions cannot be changed to read-only.
        ///
        /// # Panics
        ///
        /// Panics if the page-aligned allocation cannot be created or its size
        /// cannot be represented with guard pages.
        pub fn precalculate_readonly_locked<
            ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
            SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
        >(
            third_party_public_key: &ThirdPartyPublicKey,
            secret_key: &SecretKey,
        ) -> Result<Self, Error> {
            use crate::classic::crypto_box::crypto_box_beforenm;

            let mut precalc = HeapByteArray::<CRYPTO_BOX_BEFORENMBYTES>::new_locked()?;
            let mut key =
                crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?;

            precalc.copy_from_slice(&key);
            key.zeroize();

            Ok(PrecalcSecretKey(precalc.mprotect_readonly()?))
        }
    }
}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> std::ops::Deref
    for PrecalcSecretKey<InnerKey>
{
    type Target = InnerKey;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> std::ops::DerefMut
    for PrecalcSecretKey<InnerKey>
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn precalculated_key_debug_redacts_contents_and_equality_is_value_based() {
        let key = PrecalcSecretKey(StackByteArray::from([0xabu8; CRYPTO_BOX_BEFORENMBYTES]));
        let same = key.clone();
        let different = PrecalcSecretKey(StackByteArray::from([0xcdu8; CRYPTO_BOX_BEFORENMBYTES]));

        assert_eq!(format!("{key:?}"), "PrecalcSecretKey(\"[REDACTED]\")");
        assert_eq!(key, same);
        assert_ne!(key, different);
    }
    use crate::constants::{CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES};

    #[test]
    fn test_precalculate() {
        let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
        public_key.as_mut_array()[0] = 9;
        let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::default();
        let precalc_key = PrecalcSecretKey::precalculate(&public_key, &secret_key).unwrap();
        assert!(!precalc_key.is_empty());
        assert_eq!(precalc_key.len(), CRYPTO_BOX_BEFORENMBYTES);

        let low_order_public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
        assert!(PrecalcSecretKey::precalculate(&low_order_public_key, &secret_key).is_err());
    }

    #[cfg(all(feature = "protected", any(unix, windows)))]
    #[test]
    fn test_precalculate_locked() {
        let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
        public_key.as_mut_array()[0] = 9;
        let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::default();
        let mut precalc_key =
            PrecalcSecretKey::precalculate_locked(&public_key, &secret_key).unwrap();
        assert!(!precalc_key.is_empty());
        assert_eq!(precalc_key.len(), CRYPTO_BOX_BEFORENMBYTES);

        // should be able to write now without blowing up
        precalc_key.as_mut_slice()[0] = 0;
        precalc_key.as_mut_array()[0] = 1;

        let low_order_public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
        assert!(PrecalcSecretKey::precalculate_locked(&low_order_public_key, &secret_key).is_err());
    }

    #[cfg(all(feature = "protected", any(unix, windows)))]
    #[test]
    fn test_precalculate_readonly_locked() {
        let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
        public_key.as_mut_array()[0] = 9;
        let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::default();
        let precalc_key =
            PrecalcSecretKey::precalculate_readonly_locked(&public_key, &secret_key).unwrap();
        assert!(!precalc_key.is_empty());
        assert_eq!(precalc_key.len(), CRYPTO_BOX_BEFORENMBYTES);

        let low_order_public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
        assert!(
            PrecalcSecretKey::precalculate_readonly_locked(&low_order_public_key, &secret_key)
                .is_err()
        );
    }
}