Skip to main content

dryoc/classic/
crypto_box.rs

1//! # Authenticated public-key cryptography functions
2//!
3//! Implements libsodium's public-key authenticated crypto boxes.
4//!
5//! For details, refer to [libsodium docs](https://libsodium.gitbook.io/doc/public-key_cryptography/authenticated_encryption).
6//!
7//! ## Classic API example
8//!
9//! ```
10//! use dryoc::classic::crypto_box::*;
11//! use dryoc::constants::CRYPTO_BOX_MACBYTES;
12//! use dryoc::types::*;
13//!
14//! // Create a random sender keypair
15//! let (sender_pk, sender_sk) = crypto_box_keypair();
16//!
17//! // Create a random recipient keypair
18//! let (recipient_pk, recipient_sk) = crypto_box_keypair();
19//!
20//! // Generate a random nonce
21//! let nonce = Nonce::generate();
22//!
23//! let message = "hello".as_bytes();
24//! // Encrypt message
25//! let mut ciphertext = vec![0u8; message.len() + CRYPTO_BOX_MACBYTES];
26//! crypto_box_easy(&mut ciphertext, message, &nonce, &recipient_pk, &sender_sk)
27//!     .expect("encrypt failed");
28//!
29//! // Decrypt message
30//! let mut decrypted_message = vec![0u8; ciphertext.len() - CRYPTO_BOX_MACBYTES];
31//! crypto_box_open_easy(
32//!     &mut decrypted_message,
33//!     &ciphertext,
34//!     &nonce,
35//!     &sender_pk,
36//!     &recipient_sk,
37//! )
38//! .expect("decrypt failed");
39//!
40//! assert_eq!(message, decrypted_message);
41//! ```
42
43use zeroize::{Zeroize, Zeroizing};
44
45use super::crypto_generichash::{
46    crypto_generichash_final, crypto_generichash_init, crypto_generichash_update,
47};
48use crate::classic::crypto_box_impl::*;
49use crate::classic::crypto_secretbox::*;
50use crate::classic::crypto_secretbox_impl::*;
51use crate::constants::*;
52use crate::error::Error;
53use crate::types::*;
54
55/// Crypto box message authentication code.
56pub type Mac = [u8; CRYPTO_BOX_MACBYTES];
57
58/// Nonce for crypto boxes.
59pub type Nonce = [u8; CRYPTO_BOX_NONCEBYTES];
60/// Public key for public key authenticated crypto boxes.
61pub type PublicKey = [u8; CRYPTO_BOX_PUBLICKEYBYTES];
62/// Secret key for public key authenticated crypto boxes.
63pub type SecretKey = [u8; CRYPTO_BOX_SECRETKEYBYTES];
64
65/// In-place variant of [`crypto_box_keypair`]
66pub fn crypto_box_keypair_inplace(public_key: &mut PublicKey, secret_key: &mut SecretKey) {
67    crypto_box_curve25519xsalsa20poly1305_keypair_inplace(public_key, secret_key)
68}
69
70/// In-place variant of [`crypto_box_seed_keypair`]
71pub fn crypto_box_seed_keypair_inplace(
72    public_key: &mut PublicKey,
73    secret_key: &mut SecretKey,
74    seed: &[u8; CRYPTO_BOX_SEEDBYTES],
75) {
76    crypto_box_curve25519xsalsa20poly1305_seed_keypair_inplace(public_key, secret_key, seed)
77}
78
79/// Generates a public/secret key pair using OS provided data using
80/// [`rand::rngs::SysRng`].
81pub fn crypto_box_keypair() -> (PublicKey, SecretKey) {
82    crypto_box_curve25519xsalsa20poly1305_keypair()
83}
84
85/// Deterministically derives a keypair from a 32-byte `seed`.
86///
87/// Compatible with libsodium's `crypto_box_seed_keypair`.
88pub fn crypto_box_seed_keypair(seed: &[u8; CRYPTO_BOX_SEEDBYTES]) -> (PublicKey, SecretKey) {
89    crypto_box_curve25519xsalsa20poly1305_seed_keypair(seed)
90}
91
92/// Computes a shared secret for the given `public_key` and `private_key`.
93/// Resulting shared secret can be used with the precalculation interface.
94///
95/// Compatible with libsodium's `crypto_box_beforenm`.
96///
97/// # Errors
98///
99/// Returns an error if `public_key` is an unacceptable low-order key.
100pub fn crypto_box_beforenm(public_key: &PublicKey, secret_key: &SecretKey) -> Result<Key, Error> {
101    crypto_box_curve25519xsalsa20poly1305_beforenm(public_key, secret_key)
102}
103
104/// Precalculation variant of [`crypto_box_detached`].
105///
106/// Compatible with libsodium's `crypto_box_detached_afternm`.
107///
108/// # Errors
109///
110/// Returns an error if `message` is too long or `ciphertext` is shorter than
111/// `message`.
112pub fn crypto_box_detached_afternm(
113    ciphertext: &mut [u8],
114    mac: &mut Mac,
115    message: &[u8],
116    nonce: &Nonce,
117    key: &Key,
118) -> Result<(), Error> {
119    crypto_secretbox_detached(ciphertext, mac, message, nonce, key)
120}
121
122/// In-place variant of [`crypto_box_detached_afternm`].
123pub fn crypto_box_detached_afternm_inplace(
124    ciphertext: &mut [u8],
125    mac: &mut Mac,
126    nonce: &Nonce,
127    key: &Key,
128) {
129    crypto_secretbox_detached_inplace(ciphertext, mac, nonce, key)
130}
131
132/// Encrypts a message using a key computed by [`crypto_box_beforenm`].
133///
134/// The result is placed into `ciphertext`, which must be exactly
135/// [`CRYPTO_BOX_MACBYTES`] bytes longer than `message`.
136///
137/// Compatible with libsodium's `crypto_box_easy_afternm`.
138///
139/// # Errors
140///
141/// Returns an error if `message` is too long or `ciphertext` has the wrong
142/// length.
143pub fn crypto_box_easy_afternm(
144    ciphertext: &mut [u8],
145    message: &[u8],
146    nonce: &Nonce,
147    key: &Key,
148) -> Result<(), Error> {
149    if message.len() > CRYPTO_BOX_MESSAGEBYTES_MAX {
150        return Err(
151            length_error!(crate::ErrorContext::Message, message.len(), max CRYPTO_BOX_MESSAGEBYTES_MAX),
152        );
153    }
154
155    let expected_ciphertext_len = message.len() + CRYPTO_BOX_MACBYTES;
156    if ciphertext.len() != expected_ciphertext_len {
157        return Err(length_error!(
158            crate::ErrorContext::Ciphertext,
159            ciphertext.len(),
160            exact expected_ciphertext_len
161        ));
162    }
163
164    let (mac, ciphertext) = ciphertext.split_at_mut(CRYPTO_BOX_MACBYTES);
165    crypto_box_detached_afternm(
166        ciphertext,
167        MutByteArray::as_mut_array(mac),
168        message,
169        nonce,
170        key,
171    )
172}
173
174/// Detached variant of [`crypto_box_easy`].
175///
176/// Compatible with libsodium's `crypto_box_detached`.
177///
178/// # Errors
179///
180/// Returns an error if `message` is too long, `recipient_public_key` is
181/// unacceptable, or `ciphertext` is shorter than `message`.
182pub fn crypto_box_detached(
183    ciphertext: &mut [u8],
184    mac: &mut Mac,
185    message: &[u8],
186    nonce: &Nonce,
187    recipient_public_key: &PublicKey,
188    sender_secret_key: &SecretKey,
189) -> Result<(), Error> {
190    let key = Zeroizing::new(crypto_box_beforenm(
191        recipient_public_key,
192        sender_secret_key,
193    )?);
194
195    crypto_box_detached_afternm(ciphertext, mac, message, nonce, &key)
196}
197
198/// In-place variant of [`crypto_box_detached`].
199///
200/// # Errors
201///
202/// Returns an error if `recipient_public_key` is unacceptable.
203pub fn crypto_box_detached_inplace(
204    message: &mut [u8],
205    mac: &mut Mac,
206    nonce: &Nonce,
207    recipient_public_key: &PublicKey,
208    sender_secret_key: &SecretKey,
209) -> Result<(), Error> {
210    let key = Zeroizing::new(crypto_box_beforenm(
211        recipient_public_key,
212        sender_secret_key,
213    )?);
214
215    crypto_box_detached_afternm_inplace(message, mac, nonce, &key);
216
217    Ok(())
218}
219/// Encrypts a message in a box.
220///
221/// Encrypts `message` with recipient's public key `recipient_public_key`,
222/// sender's secret key `sender_secret_key`, and `nonce`. The result is placed
223/// into `ciphertext` which must be the length of the message plus
224/// [`CRYPTO_BOX_MACBYTES`] bytes, for the message tag.
225///
226/// Compatible with libsodium's `crypto_box_easy`.
227///
228/// # Errors
229///
230/// Returns an error if `message` is too long, `ciphertext` has the wrong
231/// length, or `recipient_public_key` is unacceptable.
232pub fn crypto_box_easy(
233    ciphertext: &mut [u8],
234    message: &[u8],
235    nonce: &Nonce,
236    recipient_public_key: &PublicKey,
237    sender_secret_key: &SecretKey,
238) -> Result<(), Error> {
239    if message.len() > CRYPTO_BOX_MESSAGEBYTES_MAX {
240        Err(
241            length_error!(crate::ErrorContext::Message, message.len(), max CRYPTO_BOX_MESSAGEBYTES_MAX),
242        )
243    } else if ciphertext.len() != message.len() + CRYPTO_BOX_MACBYTES {
244        Err(length_error!(
245            crate::ErrorContext::Ciphertext,
246            ciphertext.len(),
247            exact message.len() + CRYPTO_BOX_MACBYTES
248        ))
249    } else {
250        let (mac, ciphertext) = ciphertext.split_at_mut(CRYPTO_BOX_MACBYTES);
251        let mac = MutByteArray::as_mut_array(mac);
252        crypto_box_detached(
253            ciphertext,
254            mac,
255            message,
256            nonce,
257            recipient_public_key,
258            sender_secret_key,
259        )?;
260
261        Ok(())
262    }
263}
264
265pub(crate) fn crypto_box_seal_nonce(nonce: &mut Nonce, epk: &PublicKey, rpk: &SecretKey) {
266    let mut state = crypto_generichash_init(None, CRYPTO_BOX_NONCEBYTES).expect("state");
267    crypto_generichash_update(&mut state, epk);
268    crypto_generichash_update(&mut state, rpk);
269    crypto_generichash_final(state, nonce).expect("hash error");
270}
271
272fn crypto_box_seal_ciphertext_len(message_len: usize) -> Result<usize, Error> {
273    message_len
274        .checked_add(CRYPTO_BOX_SEALBYTES)
275        .ok_or(Error::arithmetic_overflow(crate::ErrorContext::SealedBox))
276}
277
278/// Encrypts and seals a message in a box.
279///
280/// Encrypts `message` with recipient's public key `recipient_public_key`, using
281/// an ephemeral keypair and nonce. The length of `ciphertext` must be the
282/// length of the message plus [`CRYPTO_BOX_SEALBYTES`] bytes for the message
283/// tag and ephemeral public key.
284///
285/// Compatible with libsodium's `crypto_box_seal`.
286///
287/// # Errors
288///
289/// Returns an error if `ciphertext` has the wrong length, `message` is too
290/// long, or `recipient_public_key` is unacceptable.
291///
292/// # Panics
293///
294/// Panics if the operating system's random number generator fails while
295/// creating the ephemeral keypair.
296pub fn crypto_box_seal(
297    ciphertext: &mut [u8],
298    message: &[u8],
299    recipient_public_key: &PublicKey,
300) -> Result<(), Error> {
301    let expected_ciphertext_len = crypto_box_seal_ciphertext_len(message.len())?;
302    if ciphertext.len() != expected_ciphertext_len {
303        Err(length_error!(
304            crate::ErrorContext::Ciphertext,
305            ciphertext.len(),
306            exact expected_ciphertext_len
307        ))
308    } else {
309        let mut nonce = Nonce::new_byte_array();
310        let (mut epk, esk) = crypto_box_keypair();
311        let esk = Zeroizing::new(esk);
312        crypto_box_seal_nonce(&mut nonce, &epk, recipient_public_key);
313
314        crypto_box_easy(
315            &mut ciphertext[CRYPTO_BOX_PUBLICKEYBYTES..],
316            message,
317            &nonce,
318            recipient_public_key,
319            &esk,
320        )?;
321
322        ciphertext[..CRYPTO_BOX_PUBLICKEYBYTES].copy_from_slice(&epk);
323
324        epk.zeroize();
325        nonce.zeroize();
326
327        Ok(())
328    }
329}
330
331/// Encrypts a message in-place in a box.
332///
333/// Encrypts `message` with recipient's public key `recipient_public_key` and
334/// sender's secret key `sender_secret_key` using `nonce` in-place in `data`,
335/// without allocating additional memory for the message.
336///
337/// The caller of this function is responsible for allocating `data` such that
338/// there's enough capacity for the message plus the additional
339/// [`CRYPTO_BOX_MACBYTES`] bytes for the authentication tag.
340///
341/// For this reason, the last [`CRYPTO_BOX_MACBYTES`] bytes from the input
342/// is ignored. The length of `data` should be the length of your message plus
343/// [`CRYPTO_BOX_MACBYTES`] bytes.
344///
345/// # Errors
346///
347/// Returns an error if `data` is too short or too long, or
348/// `recipient_public_key` is unacceptable.
349pub fn crypto_box_easy_inplace(
350    data: &mut [u8],
351    nonce: &Nonce,
352    recipient_public_key: &PublicKey,
353    sender_secret_key: &SecretKey,
354) -> Result<(), Error> {
355    if data.len() < CRYPTO_BOX_MACBYTES {
356        Err(length_error!(crate::ErrorContext::Data, data.len(), min CRYPTO_BOX_MACBYTES))
357    } else if data.len() - CRYPTO_BOX_MACBYTES > CRYPTO_BOX_MESSAGEBYTES_MAX {
358        Err(length_error!(
359            crate::ErrorContext::Data,
360            data.len(),
361            max CRYPTO_BOX_MESSAGEBYTES_MAX + CRYPTO_BOX_MACBYTES
362        ))
363    } else {
364        let key = Zeroizing::new(crypto_box_beforenm(
365            recipient_public_key,
366            sender_secret_key,
367        )?);
368
369        data.rotate_right(CRYPTO_BOX_MACBYTES);
370
371        let (mac, data) = data.split_at_mut(CRYPTO_BOX_MACBYTES);
372        let mac = MutByteArray::as_mut_array(mac);
373
374        crypto_box_detached_afternm_inplace(data, mac, nonce, &key);
375
376        Ok(())
377    }
378}
379
380/// Precalculation variant of [`crypto_box_open_detached`].
381///
382/// Compatible with libsodium's `crypto_box_open_detached_afternm`.
383///
384/// # Errors
385///
386/// Returns an error if `ciphertext` is too long, `message` is shorter than
387/// `ciphertext`, or authentication fails.
388pub fn crypto_box_open_detached_afternm(
389    message: &mut [u8],
390    mac: &Mac,
391    ciphertext: &[u8],
392    nonce: &Nonce,
393    key: &Key,
394) -> Result<(), Error> {
395    crypto_secretbox_open_detached(message, mac, ciphertext, nonce, key)
396}
397
398/// In-place variant of [`crypto_box_open_detached_afternm`].
399///
400/// # Errors
401///
402/// Returns an error if authentication fails.
403pub fn crypto_box_open_detached_afternm_inplace(
404    data: &mut [u8],
405    mac: &Mac,
406    nonce: &Nonce,
407    key: &Key,
408) -> Result<(), Error> {
409    crypto_secretbox_open_detached_inplace(data, mac, nonce, key)
410}
411
412/// Decrypts a box using a key computed by [`crypto_box_beforenm`].
413///
414/// Compatible with libsodium's `crypto_box_open_easy_afternm`.
415///
416/// # Errors
417///
418/// Returns an error if `ciphertext` is shorter than an authentication tag,
419/// `message` has the wrong length, or authentication fails.
420pub fn crypto_box_open_easy_afternm(
421    message: &mut [u8],
422    ciphertext: &[u8],
423    nonce: &Nonce,
424    key: &Key,
425) -> Result<(), Error> {
426    if ciphertext.len() < CRYPTO_BOX_MACBYTES {
427        return Err(
428            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_BOX_MACBYTES),
429        );
430    }
431
432    let expected_message_len = ciphertext.len() - CRYPTO_BOX_MACBYTES;
433    if message.len() != expected_message_len {
434        return Err(length_error!(
435            crate::ErrorContext::Message,
436            message.len(),
437            exact expected_message_len
438        ));
439    }
440
441    let (mac, ciphertext) = ciphertext.split_at(CRYPTO_BOX_MACBYTES);
442    crypto_box_open_detached_afternm(message, ByteArray::as_array(mac), ciphertext, nonce, key)
443}
444
445/// Detached variant of [`crypto_box_open_easy`].
446///
447/// Compatible with libsodium's `crypto_box_open_detached`.
448///
449/// # Errors
450///
451/// Returns an error if `ciphertext` is too long, `recipient_public_key` is
452/// unacceptable, `message` is shorter than `ciphertext`, or authentication
453/// fails.
454pub fn crypto_box_open_detached(
455    message: &mut [u8],
456    mac: &Mac,
457    ciphertext: &[u8],
458    nonce: &Nonce,
459    recipient_public_key: &PublicKey,
460    sender_secret_key: &SecretKey,
461) -> Result<(), Error> {
462    let key = Zeroizing::new(crypto_box_beforenm(
463        recipient_public_key,
464        sender_secret_key,
465    )?);
466
467    crypto_box_open_detached_afternm(message, mac, ciphertext, nonce, &key)?;
468
469    Ok(())
470}
471
472/// In-place variant of [`crypto_box_open_detached`].
473///
474/// # Errors
475///
476/// Returns an error if `recipient_public_key` is unacceptable or
477/// authentication fails.
478pub fn crypto_box_open_detached_inplace(
479    data: &mut [u8],
480    mac: &Mac,
481    nonce: &Nonce,
482    recipient_public_key: &PublicKey,
483    sender_secret_key: &SecretKey,
484) -> Result<(), Error> {
485    let key = Zeroizing::new(crypto_box_beforenm(
486        recipient_public_key,
487        sender_secret_key,
488    )?);
489
490    crypto_box_open_detached_afternm_inplace(data, mac, nonce, &key)?;
491
492    Ok(())
493}
494
495/// Decrypts `ciphertext` with recipient's secret key `recipient_secret_key` and
496/// sender's public key `sender_public_key` using `nonce`.
497///
498/// Compatible with libsodium's `crypto_box_open_easy`.
499///
500/// # Errors
501///
502/// Returns an error if `ciphertext` is shorter than an authentication tag,
503/// `message` has the wrong length, `sender_public_key` is unacceptable, or
504/// authentication fails.
505pub fn crypto_box_open_easy(
506    message: &mut [u8],
507    ciphertext: &[u8],
508    nonce: &Nonce,
509    sender_public_key: &PublicKey,
510    recipient_secret_key: &SecretKey,
511) -> Result<(), Error> {
512    if ciphertext.len() < CRYPTO_BOX_MACBYTES {
513        Err(
514            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_BOX_MACBYTES),
515        )
516    } else if message.len() != ciphertext.len() - CRYPTO_BOX_MACBYTES {
517        Err(length_error!(
518            crate::ErrorContext::Message,
519            message.len(),
520            exact ciphertext.len() - CRYPTO_BOX_MACBYTES
521        ))
522    } else {
523        let (mac, ciphertext) = ciphertext.split_at(CRYPTO_BOX_MACBYTES);
524        let mac = ByteArray::as_array(mac);
525
526        crypto_box_open_detached(
527            message,
528            mac,
529            ciphertext,
530            nonce,
531            sender_public_key,
532            recipient_secret_key,
533        )
534    }
535}
536
537/// Decrypts a sealed box.
538///
539/// Decrypts a sealed box from `ciphertext` with recipient's secret key
540/// `recipient_secret_key`, placing the result into `message`. The nonce and
541/// public key are derived from `ciphertext`. `message` length should equal
542/// the length of `ciphertext` minus [`CRYPTO_BOX_SEALBYTES`] bytes for the
543/// message tag and ephemeral public key.
544///
545/// Compatible with libsodium's `crypto_box_seal_open`.
546///
547/// # Errors
548///
549/// Returns an error if `ciphertext` is too short, `message` has the wrong
550/// length, the ephemeral public key is unacceptable, or authentication fails.
551pub fn crypto_box_seal_open(
552    message: &mut [u8],
553    ciphertext: &[u8],
554    recipient_public_key: &PublicKey,
555    recipient_secret_key: &SecretKey,
556) -> Result<(), Error> {
557    if ciphertext.len() < CRYPTO_BOX_SEALBYTES {
558        Err(
559            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_BOX_SEALBYTES),
560        )
561    } else if message.len() != ciphertext.len() - CRYPTO_BOX_SEALBYTES {
562        Err(length_error!(
563            crate::ErrorContext::Message,
564            message.len(),
565            exact ciphertext.len() - CRYPTO_BOX_SEALBYTES
566        ))
567    } else {
568        let mut nonce = Nonce::new_byte_array();
569        let mut epk = PublicKey::new_byte_array();
570        epk.copy_from_slice(&ciphertext[..CRYPTO_BOX_PUBLICKEYBYTES]);
571
572        crypto_box_seal_nonce(&mut nonce, &epk, recipient_public_key);
573
574        crypto_box_open_easy(
575            message,
576            &ciphertext[CRYPTO_BOX_PUBLICKEYBYTES..],
577            &nonce,
578            &epk,
579            recipient_secret_key,
580        )
581    }
582}
583
584/// Decrypts a sealed box in-place.
585///
586/// Decrypts `ciphertext` with recipient's secret key `recipient_secret_key` and
587/// sender's public key `sender_public_key` with `nonce` in-place in `data`,
588/// without allocating additional memory for the message.
589///
590/// The caller of this function is responsible for allocating `data` such that
591/// there's enough capacity for the message plus the additional
592/// [`CRYPTO_BOX_MACBYTES`] bytes for the authentication tag.
593///
594/// After opening the box, the last [`CRYPTO_BOX_MACBYTES`] bytes can be
595/// discarded or ignored at the caller's preference.
596///
597/// # Errors
598///
599/// Returns an error if `data` is shorter than an authentication tag,
600/// `sender_public_key` is unacceptable, or authentication fails.
601pub fn crypto_box_open_easy_inplace(
602    data: &mut [u8],
603    nonce: &Nonce,
604    sender_public_key: &PublicKey,
605    recipient_secret_key: &SecretKey,
606) -> Result<(), Error> {
607    if data.len() < CRYPTO_BOX_MACBYTES {
608        Err(length_error!(crate::ErrorContext::Data, data.len(), min CRYPTO_BOX_MACBYTES))
609    } else {
610        let (mac, d) = data.split_at_mut(CRYPTO_BOX_MACBYTES);
611        let mac = ByteArray::as_array(mac);
612
613        crypto_box_open_detached_inplace(d, mac, nonce, sender_public_key, recipient_secret_key)?;
614
615        data.rotate_left(CRYPTO_BOX_MACBYTES);
616
617        Ok(())
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624    use crate::rng::*;
625
626    #[test]
627    fn test_crypto_box_easy_invalid() {
628        for _ in 0..20 {
629            let (sender_pk, _sender_sk) = crypto_box_keypair();
630            let (_recipient_pk, recipient_sk) = crypto_box_keypair();
631            let nonce = Nonce::generate();
632
633            let mut ciphertext: Vec<u8> = vec![];
634            let message: Vec<u8> = vec![];
635
636            crypto_box_open_easy(&mut ciphertext, &message, &nonce, &sender_pk, &recipient_sk)
637                .expect_err("expected an error");
638        }
639    }
640
641    #[test]
642    fn test_crypto_box_rejects_mismatched_buffers_without_mutation() {
643        let (sender_pk, sender_sk) = crypto_box_keypair();
644        let (recipient_pk, recipient_sk) = crypto_box_keypair();
645        let nonce = Nonce::default();
646        let message = b"buffer length validation";
647
648        for output_len in [
649            message.len() + CRYPTO_BOX_MACBYTES - 1,
650            message.len() + CRYPTO_BOX_MACBYTES + 1,
651        ] {
652            let mut output = vec![0xa5; output_len];
653            let original = output.clone();
654            assert!(
655                crypto_box_easy(&mut output, message, &nonce, &recipient_pk, &sender_sk,).is_err()
656            );
657            assert_eq!(output, original);
658        }
659
660        let mut ciphertext = vec![0u8; message.len() + CRYPTO_BOX_MACBYTES];
661        crypto_box_easy(&mut ciphertext, message, &nonce, &recipient_pk, &sender_sk)
662            .expect("encrypt failed");
663
664        for output_len in [message.len() - 1, message.len() + 1] {
665            let mut output = vec![0xa5; output_len];
666            let original = output.clone();
667            assert!(
668                crypto_box_open_easy(&mut output, &ciphertext, &nonce, &sender_pk, &recipient_sk,)
669                    .is_err()
670            );
671            assert_eq!(output, original);
672        }
673    }
674
675    #[test]
676    fn test_crypto_box_easy_afternm_roundtrip_and_failure_atomicity() {
677        let (sender_public_key, sender_secret_key) = crypto_box_keypair();
678        let (recipient_public_key, recipient_secret_key) = crypto_box_keypair();
679        let nonce = Nonce::generate();
680        let message = b"precomputed crypto box";
681        let sender_key = crypto_box_beforenm(&recipient_public_key, &sender_secret_key)
682            .expect("sender precalculation failed");
683        let recipient_key = crypto_box_beforenm(&sender_public_key, &recipient_secret_key)
684            .expect("recipient precalculation failed");
685        assert_eq!(sender_key, recipient_key);
686
687        let mut ciphertext = vec![0u8; message.len() + CRYPTO_BOX_MACBYTES];
688        crypto_box_easy_afternm(&mut ciphertext, message, &nonce, &sender_key)
689            .expect("encryption failed");
690        let mut direct_ciphertext = vec![0u8; ciphertext.len()];
691        crypto_box_easy(
692            &mut direct_ciphertext,
693            message,
694            &nonce,
695            &recipient_public_key,
696            &sender_secret_key,
697        )
698        .expect("direct encryption failed");
699        assert_eq!(ciphertext, direct_ciphertext);
700
701        let mut decrypted = vec![0u8; message.len()];
702        crypto_box_open_easy_afternm(&mut decrypted, &ciphertext, &nonce, &recipient_key)
703            .expect("decryption failed");
704        assert_eq!(decrypted, message);
705
706        ciphertext[0] ^= 1;
707        decrypted.fill(0xa5);
708        let original_decrypted = decrypted.clone();
709        assert!(
710            crypto_box_open_easy_afternm(&mut decrypted, &ciphertext, &nonce, &recipient_key)
711                .is_err()
712        );
713        assert_eq!(decrypted, original_decrypted);
714    }
715
716    #[test]
717    fn test_crypto_box_seal_rejects_mismatched_buffers() {
718        let (recipient_public_key, recipient_secret_key) = crypto_box_keypair();
719        let message = b"sealed box buffer validation";
720
721        assert!(matches!(
722            crypto_box_seal_ciphertext_len(usize::MAX),
723            Err(Error::ArithmeticOverflow {
724                context: crate::ErrorContext::SealedBox,
725            })
726        ));
727
728        let mut short_ciphertext = vec![0u8; message.len() + CRYPTO_BOX_SEALBYTES - 1];
729        assert!(matches!(
730            crypto_box_seal(&mut short_ciphertext, message, &recipient_public_key),
731            Err(Error::InvalidLength {
732                context: crate::ErrorContext::Ciphertext,
733                actual,
734                constraint: crate::LengthConstraint::Exact(expected),
735            }) if actual == short_ciphertext.len()
736                && expected == message.len() + CRYPTO_BOX_SEALBYTES
737        ));
738
739        let short_sealed_box = vec![0u8; CRYPTO_BOX_SEALBYTES - 1];
740        assert!(matches!(
741            crypto_box_seal_open(
742                &mut [],
743                &short_sealed_box,
744                &recipient_public_key,
745                &recipient_secret_key,
746            ),
747            Err(Error::InvalidLength {
748                context: crate::ErrorContext::Ciphertext,
749                actual,
750                constraint: crate::LengthConstraint::AtLeast(CRYPTO_BOX_SEALBYTES),
751            }) if actual == short_sealed_box.len()
752        ));
753
754        let sealed_box = vec![0u8; CRYPTO_BOX_SEALBYTES + 1];
755        assert!(matches!(
756            crypto_box_seal_open(
757                &mut [],
758                &sealed_box,
759                &recipient_public_key,
760                &recipient_secret_key,
761            ),
762            Err(Error::InvalidLength {
763                context: crate::ErrorContext::Message,
764                actual: 0,
765                constraint: crate::LengthConstraint::Exact(1),
766            })
767        ));
768    }
769
770    #[test]
771    fn test_crypto_box_rejects_low_order_public_keys() {
772        let (_, secret_key) = crypto_box_keypair();
773        let nonce = Nonce::default();
774        let message = b"message";
775        let mut ciphertext = [0u8; 7];
776        let mut mac = Mac::default();
777        let mut one = PublicKey::default();
778        one[0] = 1;
779
780        for public_key in [PublicKey::default(), one] {
781            assert!(crypto_box_beforenm(&public_key, &secret_key).is_err());
782            assert!(
783                crypto_box_detached(
784                    &mut ciphertext,
785                    &mut mac,
786                    message,
787                    &nonce,
788                    &public_key,
789                    &secret_key,
790                )
791                .is_err()
792            );
793
794            let mut data = b"message with tag storage\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_vec();
795            let original_data = data.clone();
796            assert!(crypto_box_easy_inplace(&mut data, &nonce, &public_key, &secret_key).is_err());
797            assert_eq!(data, original_data);
798        }
799    }
800    #[test]
801    fn test_crypto_box_easy_inplace_invalid() {
802        for _ in 0..20 {
803            use base64::Engine as _;
804            use base64::engine::general_purpose;
805
806            let (sender_pk, _sender_sk) = crypto_box_keypair();
807            let (_recipient_pk, recipient_sk) = crypto_box_keypair();
808            let nonce = Nonce::generate();
809
810            let mut ciphertext: Vec<u8> = vec![];
811
812            crypto_box_open_easy_inplace(&mut ciphertext, &nonce, &sender_pk, &recipient_sk)
813                .expect_err("expected an error");
814
815            ciphertext.resize(1024, 0);
816            copy_randombytes(ciphertext.as_mut_slice());
817            let ciphertext_copy = ciphertext.clone();
818
819            crypto_box_open_easy_inplace(&mut ciphertext, &nonce, &sender_pk, &recipient_sk)
820                .expect_err("expected an error");
821
822            assert_eq!(ciphertext.len(), ciphertext_copy.len());
823            assert_eq!(
824                general_purpose::STANDARD_NO_PAD.encode(&ciphertext[0..CRYPTO_BOX_MACBYTES]),
825                general_purpose::STANDARD_NO_PAD.encode(&ciphertext_copy[0..CRYPTO_BOX_MACBYTES])
826            );
827        }
828    }
829
830    #[cfg(dryoc_native_tests)]
831    mod native_tests {
832        use super::*;
833
834        #[test]
835        fn test_crypto_box_beforenm_low_order_compatibility() {
836            let (_, secret_key) = crypto_box_keypair();
837            let mut one = PublicKey::default();
838            one[0] = 1;
839
840            for public_key in [PublicKey::default(), one] {
841                let mut sodium_key = Key::default();
842                let sodium_result = unsafe {
843                    libsodium_sys::crypto_box_curve25519xsalsa20poly1305_beforenm(
844                        sodium_key.as_mut_ptr(),
845                        public_key.as_ptr(),
846                        secret_key.as_ptr(),
847                    )
848                };
849
850                assert!(crypto_box_beforenm(&public_key, &secret_key).is_err());
851                assert_eq!(sodium_result, -1);
852            }
853        }
854
855        #[test]
856        fn test_crypto_box_easy() {
857            for i in 0..20 {
858                use base64::Engine as _;
859                use base64::engine::general_purpose;
860                use sodiumoxide::crypto::box_;
861                use sodiumoxide::crypto::box_::{Nonce as SONonce, PublicKey, SecretKey};
862
863                let (sender_pk, sender_sk) = crypto_box_keypair();
864                let (recipient_pk, recipient_sk) = crypto_box_keypair();
865                let nonce = Nonce::generate();
866                let words = vec!["hello1".to_string(); i];
867                let message = words.join(" :D ");
868                let mut ciphertext = vec![0u8; message.len() + CRYPTO_BOX_MACBYTES];
869                crypto_box_easy(
870                    &mut ciphertext,
871                    message.as_bytes(),
872                    &nonce,
873                    &recipient_pk,
874                    &sender_sk,
875                )
876                .expect("encrypt failed");
877
878                let so_ciphertext = box_::seal(
879                    message.as_bytes(),
880                    &SONonce::from_slice(&nonce).unwrap(),
881                    &PublicKey::from_slice(&recipient_pk).unwrap(),
882                    &SecretKey::from_slice(&sender_sk).unwrap(),
883                );
884
885                assert_eq!(
886                    general_purpose::STANDARD_NO_PAD.encode(&ciphertext),
887                    general_purpose::STANDARD_NO_PAD.encode(&so_ciphertext)
888                );
889
890                let mut m = vec![0u8; ciphertext.len() - CRYPTO_BOX_MACBYTES];
891                crypto_box_open_easy(
892                    &mut m,
893                    ciphertext.as_slice(),
894                    &nonce,
895                    &sender_pk,
896                    &recipient_sk,
897                )
898                .expect("decrypt failed");
899                let so_m = box_::open(
900                    ciphertext.as_slice(),
901                    &SONonce::from_slice(&nonce).unwrap(),
902                    &PublicKey::from_slice(&recipient_pk).unwrap(),
903                    &SecretKey::from_slice(&sender_sk).unwrap(),
904                )
905                .unwrap();
906
907                assert_eq!(m, message.as_bytes());
908                assert_eq!(m, so_m);
909            }
910        }
911
912        #[test]
913        fn test_crypto_box_easy_inplace() {
914            for i in 0..20 {
915                use base64::Engine as _;
916                use base64::engine::general_purpose;
917                use sodiumoxide::crypto::box_;
918                use sodiumoxide::crypto::box_::{Nonce as SONonce, PublicKey, SecretKey};
919
920                let (sender_pk, sender_sk) = crypto_box_keypair();
921                let (recipient_pk, recipient_sk) = crypto_box_keypair();
922                let nonce = Nonce::generate();
923                let words = vec!["hello1".to_string(); i];
924                let message: Vec<u8> = words.join(" :D ").as_bytes().to_vec();
925                let message_copy = message.clone();
926
927                let mut ciphertext = message.clone();
928                ciphertext.resize(message.len() + CRYPTO_BOX_MACBYTES, 0);
929                crypto_box_easy_inplace(&mut ciphertext, &nonce, &recipient_pk, &sender_sk)
930                    .expect("encrypt failed");
931                let so_ciphertext = box_::seal(
932                    message_copy.as_slice(),
933                    &SONonce::from_slice(&nonce).unwrap(),
934                    &PublicKey::from_slice(&recipient_pk).unwrap(),
935                    &SecretKey::from_slice(&sender_sk).unwrap(),
936                );
937
938                assert_eq!(
939                    general_purpose::STANDARD_NO_PAD.encode(&ciphertext),
940                    general_purpose::STANDARD_NO_PAD.encode(&so_ciphertext)
941                );
942
943                let mut ciphertext_clone = ciphertext.clone();
944                crypto_box_open_easy_inplace(
945                    &mut ciphertext_clone,
946                    &nonce,
947                    &sender_pk,
948                    &recipient_sk,
949                )
950                .expect("decrypt failed");
951                ciphertext_clone.resize(message.len(), 0);
952
953                let so_m = box_::open(
954                    ciphertext.as_slice(),
955                    &SONonce::from_slice(&nonce).unwrap(),
956                    &PublicKey::from_slice(&recipient_pk).unwrap(),
957                    &SecretKey::from_slice(&sender_sk).unwrap(),
958                )
959                .expect("decrypt failed");
960
961                assert_eq!(
962                    general_purpose::STANDARD_NO_PAD.encode(&ciphertext_clone),
963                    general_purpose::STANDARD_NO_PAD.encode(&message_copy)
964                );
965                assert_eq!(
966                    general_purpose::STANDARD_NO_PAD.encode(&so_m),
967                    general_purpose::STANDARD_NO_PAD.encode(&message_copy)
968                );
969            }
970        }
971
972        #[test]
973        fn test_crypto_box_seed_keypair() {
974            use base64::Engine as _;
975            use base64::engine::general_purpose;
976            use sodiumoxide::crypto::box_::{Seed, keypair_from_seed};
977
978            for _ in 0..10 {
979                let seed: [u8; CRYPTO_BOX_SEEDBYTES] = randombytes_buf(CRYPTO_BOX_SEEDBYTES)
980                    .try_into()
981                    .expect("seed length");
982
983                let (pk, sk) = crypto_box_seed_keypair(&seed);
984                let (so_pk, so_sk) = keypair_from_seed(&Seed::from_slice(&seed).unwrap());
985
986                assert_eq!(
987                    general_purpose::STANDARD_NO_PAD.encode(pk),
988                    general_purpose::STANDARD_NO_PAD.encode(so_pk.as_ref())
989                );
990                assert_eq!(
991                    general_purpose::STANDARD_NO_PAD.encode(sk),
992                    general_purpose::STANDARD_NO_PAD.encode(so_sk.as_ref())
993                );
994            }
995        }
996
997        #[test]
998        fn test_crypto_box_seal() {
999            for i in 0..20 {
1000                use sodiumoxide::crypto::box_::{PublicKey, SecretKey};
1001                use sodiumoxide::crypto::sealedbox::curve25519blake2bxsalsa20poly1305;
1002
1003                let (recipient_pk, recipient_sk) = crypto_box_keypair();
1004                let words = vec!["hello1".to_string(); i];
1005                let message = words.join(" :D ");
1006                let mut ciphertext = vec![0u8; message.len() + CRYPTO_BOX_SEALBYTES];
1007                crypto_box_seal(&mut ciphertext, message.as_bytes(), &recipient_pk)
1008                    .expect("encrypt failed");
1009
1010                let mut m = vec![0u8; ciphertext.len() - CRYPTO_BOX_SEALBYTES];
1011                crypto_box_seal_open(&mut m, ciphertext.as_slice(), &recipient_pk, &recipient_sk)
1012                    .expect("decrypt failed");
1013                let so_m = curve25519blake2bxsalsa20poly1305::open(
1014                    ciphertext.as_slice(),
1015                    &PublicKey::from_slice(&recipient_pk).unwrap(),
1016                    &SecretKey::from_slice(&recipient_sk).unwrap(),
1017                )
1018                .unwrap();
1019
1020                assert_eq!(m, message.as_bytes());
1021                assert_eq!(m, so_m);
1022            }
1023        }
1024
1025        #[test]
1026        fn test_crypto_box_seal_open() {
1027            for i in 0..20 {
1028                use sodiumoxide::crypto::box_::{PublicKey, SecretKey};
1029                use sodiumoxide::crypto::sealedbox::curve25519blake2bxsalsa20poly1305;
1030
1031                let (recipient_pk, recipient_sk) = crypto_box_keypair();
1032                let words = vec!["hello1".to_string(); i];
1033                let message = words.join(" :D ");
1034                let so_ciphertext = curve25519blake2bxsalsa20poly1305::seal(
1035                    message.as_bytes(),
1036                    &PublicKey::from_slice(&recipient_pk).unwrap(),
1037                );
1038
1039                let mut m = vec![0u8; so_ciphertext.len() - CRYPTO_BOX_SEALBYTES];
1040                crypto_box_seal_open(
1041                    &mut m,
1042                    so_ciphertext.as_slice(),
1043                    &recipient_pk,
1044                    &recipient_sk,
1045                )
1046                .expect("decrypt failed");
1047                let so_m = curve25519blake2bxsalsa20poly1305::open(
1048                    so_ciphertext.as_slice(),
1049                    &PublicKey::from_slice(&recipient_pk).unwrap(),
1050                    &SecretKey::from_slice(&recipient_sk).unwrap(),
1051                )
1052                .unwrap();
1053
1054                assert_eq!(m, message.as_bytes());
1055                assert_eq!(m, so_m);
1056            }
1057        }
1058    }
1059}