Skip to main content

dryoc/
precalc.rs

1//! Precalculated secret key for use with `precalc_*` functions in
2//! [`crate::dryocbox::DryocBox`]
3//!
4//! Precalculation avoids repeating the public-key operation when encrypting or
5//! decrypting multiple messages between the same sender and receiver.
6use std::fmt;
7
8use subtle::ConstantTimeEq;
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11use crate::constants::{
12    CRYPTO_BOX_BEFORENMBYTES, CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES,
13};
14use crate::error::Error;
15use crate::types::{ByteArray, Bytes, MutByteArray, MutBytes, StackByteArray};
16
17type InnerKey = StackByteArray<CRYPTO_BOX_BEFORENMBYTES>;
18
19/// Precalculated secret key for use with `precalc_*` functions in
20/// [`crate::dryocbox::DryocBox`].
21///
22/// Use `precalc_*` functions to encrypt or decrypt multiple messages between
23/// the same sender and receiver. They reuse this shared secret instead of
24/// repeating the public-key operation for every message.
25///
26/// Using precalculated secret keys is compatible with libsodium's
27/// `crypto_box_beforenm`.
28#[derive(Zeroize, ZeroizeOnDrop, Clone)]
29pub struct PrecalcSecretKey<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize>(InnerKey);
30
31impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> fmt::Debug
32    for PrecalcSecretKey<InnerKey>
33{
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.debug_tuple("PrecalcSecretKey")
36            .field(&"[REDACTED]")
37            .finish()
38    }
39}
40
41impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> PartialEq
42    for PrecalcSecretKey<InnerKey>
43{
44    fn eq(&self, other: &Self) -> bool {
45        self.0.as_slice().ct_eq(other.0.as_slice()).into()
46    }
47}
48
49impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> Eq for PrecalcSecretKey<InnerKey> {}
50
51impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Bytes + Zeroize> Bytes
52    for PrecalcSecretKey<InnerKey>
53{
54    #[inline]
55    fn as_slice(&self) -> &[u8] {
56        self.0.as_slice()
57    }
58
59    #[inline]
60    fn is_empty(&self) -> bool {
61        self.0.is_empty()
62    }
63
64    #[inline]
65    fn len(&self) -> usize {
66        self.0.len()
67    }
68}
69
70impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> ByteArray<CRYPTO_BOX_BEFORENMBYTES>
71    for PrecalcSecretKey<InnerKey>
72{
73    #[inline]
74    fn as_array(&self) -> &[u8; CRYPTO_BOX_BEFORENMBYTES] {
75        self.0.as_array()
76    }
77}
78
79impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize + MutBytes> MutBytes
80    for PrecalcSecretKey<InnerKey>
81{
82    #[inline]
83    fn as_mut_slice(&mut self) -> &mut [u8] {
84        self.0.as_mut_slice()
85    }
86
87    #[inline]
88    fn copy_from_slice(&mut self, other: &[u8]) {
89        self.0.copy_from_slice(other);
90    }
91}
92
93impl<InnerKey: MutByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize>
94    MutByteArray<CRYPTO_BOX_BEFORENMBYTES> for PrecalcSecretKey<InnerKey>
95{
96    #[inline]
97    fn as_mut_array(&mut self) -> &mut [u8; CRYPTO_BOX_BEFORENMBYTES] {
98        self.0.as_mut_array()
99    }
100}
101
102impl PrecalcSecretKey<InnerKey> {
103    /// Computes a stack-allocated shared secret key for the given
104    /// `third_party_public_key` and `secret_key`.
105    ///
106    /// Compatible with libsodium's `crypto_box_beforenm`.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if `third_party_public_key` is an unacceptable
111    /// low-order point.
112    #[inline]
113    pub fn precalculate<
114        ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
115        SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
116    >(
117        third_party_public_key: &ThirdPartyPublicKey,
118        secret_key: &SecretKey,
119    ) -> Result<Self, Error> {
120        use crate::classic::crypto_box::crypto_box_beforenm;
121
122        Ok(Self(
123            crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?.into(),
124        ))
125    }
126}
127
128#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
129#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
130pub mod protected {
131    //! # Protected memory for [`PrecalcSecretKey`]
132    use super::*;
133    pub use crate::protected::*;
134
135    type InnerKey = HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>;
136
137    impl PrecalcSecretKey<Locked<InnerKey>> {
138        /// Computes a heap-allocated, page-aligned, locked shared secret key
139        /// for the given `third_party_public_key` and `secret_key`.
140        ///
141        /// Compatible with libsodium's `crypto_box_beforenm`.
142        ///
143        /// # Errors
144        ///
145        /// Returns an error if `third_party_public_key` is an unacceptable
146        /// low-order point or the protected allocation cannot be locked.
147        ///
148        /// # Panics
149        ///
150        /// Panics if the page-aligned allocation cannot be created or its size
151        /// cannot be represented with guard pages.
152        pub fn precalculate_locked<
153            ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
154            SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
155        >(
156            third_party_public_key: &ThirdPartyPublicKey,
157            secret_key: &SecretKey,
158        ) -> Result<Self, Error> {
159            use crate::classic::crypto_box::crypto_box_beforenm;
160
161            let mut precalc = HeapByteArray::<CRYPTO_BOX_BEFORENMBYTES>::new_locked()?;
162            let mut key =
163                crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?;
164
165            precalc.copy_from_slice(&key);
166            key.zeroize();
167
168            Ok(PrecalcSecretKey(precalc))
169        }
170    }
171
172    impl PrecalcSecretKey<LockedRO<InnerKey>> {
173        /// Computes a heap-allocated, page-aligned, locked, read-only shared
174        /// secret key for the given `third_party_public_key` and
175        /// `secret_key`.
176        ///
177        /// Compatible with libsodium's `crypto_box_beforenm`.
178        ///
179        /// # Errors
180        ///
181        /// Returns an error if `third_party_public_key` is an unacceptable
182        /// low-order point, the protected allocation cannot be locked, or its
183        /// page permissions cannot be changed to read-only.
184        ///
185        /// # Panics
186        ///
187        /// Panics if the page-aligned allocation cannot be created or its size
188        /// cannot be represented with guard pages.
189        pub fn precalculate_readonly_locked<
190            ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
191            SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
192        >(
193            third_party_public_key: &ThirdPartyPublicKey,
194            secret_key: &SecretKey,
195        ) -> Result<Self, Error> {
196            use crate::classic::crypto_box::crypto_box_beforenm;
197
198            let mut precalc = HeapByteArray::<CRYPTO_BOX_BEFORENMBYTES>::new_locked()?;
199            let mut key =
200                crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?;
201
202            precalc.copy_from_slice(&key);
203            key.zeroize();
204
205            Ok(PrecalcSecretKey(precalc.mprotect_readonly()?))
206        }
207    }
208}
209
210impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> std::ops::Deref
211    for PrecalcSecretKey<InnerKey>
212{
213    type Target = InnerKey;
214
215    fn deref(&self) -> &Self::Target {
216        &self.0
217    }
218}
219
220impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> std::ops::DerefMut
221    for PrecalcSecretKey<InnerKey>
222{
223    fn deref_mut(&mut self) -> &mut Self::Target {
224        &mut self.0
225    }
226}
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn precalculated_key_debug_redacts_contents_and_equality_is_value_based() {
233        let key = PrecalcSecretKey(StackByteArray::from([0xabu8; CRYPTO_BOX_BEFORENMBYTES]));
234        let same = key.clone();
235        let different = PrecalcSecretKey(StackByteArray::from([0xcdu8; CRYPTO_BOX_BEFORENMBYTES]));
236
237        assert_eq!(format!("{key:?}"), "PrecalcSecretKey(\"[REDACTED]\")");
238        assert_eq!(key, same);
239        assert_ne!(key, different);
240    }
241    use crate::constants::{CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES};
242
243    #[test]
244    fn test_precalculate() {
245        let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
246        public_key.as_mut_array()[0] = 9;
247        let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::default();
248        let precalc_key = PrecalcSecretKey::precalculate(&public_key, &secret_key).unwrap();
249        assert!(!precalc_key.is_empty());
250        assert_eq!(precalc_key.len(), CRYPTO_BOX_BEFORENMBYTES);
251
252        let low_order_public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
253        assert!(PrecalcSecretKey::precalculate(&low_order_public_key, &secret_key).is_err());
254    }
255
256    #[cfg(all(feature = "protected", any(unix, windows)))]
257    #[test]
258    fn test_precalculate_locked() {
259        let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
260        public_key.as_mut_array()[0] = 9;
261        let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::default();
262        let mut precalc_key =
263            PrecalcSecretKey::precalculate_locked(&public_key, &secret_key).unwrap();
264        assert!(!precalc_key.is_empty());
265        assert_eq!(precalc_key.len(), CRYPTO_BOX_BEFORENMBYTES);
266
267        // should be able to write now without blowing up
268        precalc_key.as_mut_slice()[0] = 0;
269        precalc_key.as_mut_array()[0] = 1;
270
271        let low_order_public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
272        assert!(PrecalcSecretKey::precalculate_locked(&low_order_public_key, &secret_key).is_err());
273    }
274
275    #[cfg(all(feature = "protected", any(unix, windows)))]
276    #[test]
277    fn test_precalculate_readonly_locked() {
278        let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
279        public_key.as_mut_array()[0] = 9;
280        let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::default();
281        let precalc_key =
282            PrecalcSecretKey::precalculate_readonly_locked(&public_key, &secret_key).unwrap();
283        assert!(!precalc_key.is_empty());
284        assert_eq!(precalc_key.len(), CRYPTO_BOX_BEFORENMBYTES);
285
286        let low_order_public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
287        assert!(
288            PrecalcSecretKey::precalculate_readonly_locked(&low_order_public_key, &secret_key)
289                .is_err()
290        );
291    }
292}