d3xs_protocol/
crypto.rs

1use crate::errors::*;
2use crypto_box::{aead::AeadInPlace, Nonce, Tag};
3pub use crypto_box::{PublicKey, SalsaBox, SecretKey};
4use data_encoding::BASE64;
5
6pub const CRYPTO_TAG_SIZE: usize = 16;
7pub const CRYPTO_NONCE_SIZE: usize = 24;
8pub const CRYPTO_SECRET_KEY_SIZE: usize = 32;
9pub const CRYPTO_PUBLIC_KEY_SIZE: usize = 32;
10
11pub trait Rng {
12    fn getrandom(buf: &mut [u8]);
13}
14
15pub struct Random;
16
17#[cfg(not(target_os = "espidf"))]
18impl Rng for Random {
19    fn getrandom(buf: &mut [u8]) {
20        getrandom::getrandom(buf).unwrap();
21    }
22}
23
24pub fn encrypt<'a, R: Rng>(salsa: &SalsaBox, src: &[u8], dest: &'a mut [u8]) -> Result<&'a [u8]> {
25    let buffer_size = dest.len();
26    if buffer_size < src.len() + CRYPTO_NONCE_SIZE + CRYPTO_TAG_SIZE {
27        return Err(Error::BufferLimit);
28    }
29
30    let length = {
31        let (nonce, cursor) = dest.split_at_mut(CRYPTO_NONCE_SIZE);
32        let nonce = {
33            let mut buf = [0u8; CRYPTO_NONCE_SIZE];
34            R::getrandom(&mut buf);
35            nonce.copy_from_slice(&buf);
36            Nonce::from(buf)
37        };
38
39        let (buf, cursor) = cursor.split_at_mut(src.len());
40        buf.copy_from_slice(src);
41        let tag = salsa.encrypt_in_place_detached(&nonce, &[], buf)?;
42
43        let (buf, cursor) = cursor.split_at_mut(tag.len());
44        buf.copy_from_slice(&tag);
45
46        buffer_size - cursor.len()
47    };
48
49    Ok(&dest[..length])
50}
51
52pub fn decrypt<'a>(salsa: &SalsaBox, src: &[u8], dest: &'a mut [u8]) -> Result<&'a [u8]> {
53    if src.len() < CRYPTO_NONCE_SIZE + CRYPTO_TAG_SIZE {
54        return Err(Error::BufferLimit);
55    }
56
57    let dest = &mut dest[..src.len() - CRYPTO_NONCE_SIZE - CRYPTO_TAG_SIZE];
58
59    let (nonce, cursor) = src.split_at(CRYPTO_NONCE_SIZE);
60    let nonce = {
61        let mut buf = [0u8; CRYPTO_NONCE_SIZE];
62        buf.copy_from_slice(nonce);
63        Nonce::from(buf)
64    };
65
66    let (cursor, tag) = cursor.split_at(cursor.len() - CRYPTO_TAG_SIZE);
67    let tag = {
68        let mut buf = [0u8; CRYPTO_TAG_SIZE];
69        buf.copy_from_slice(tag);
70        Tag::from(buf)
71    };
72
73    dest.copy_from_slice(cursor);
74    salsa.decrypt_in_place_detached(&nonce, &[], dest, &tag)?;
75
76    Ok(dest)
77}
78
79pub fn generate_secret_key<R: Rng>() -> SecretKey {
80    let mut buf = [0u8; crypto_box::KEY_SIZE];
81    R::getrandom(&mut buf);
82    SecretKey::from_bytes(buf)
83}
84
85pub fn secret_key(bytes: &str) -> Result<SecretKey> {
86    let bytes = BASE64.decode(bytes.as_bytes())?;
87    if bytes.len() != CRYPTO_SECRET_KEY_SIZE {
88        return Err(Error::InvalidKeyLength(bytes.len()));
89    }
90
91    let mut buf = [0u8; CRYPTO_SECRET_KEY_SIZE];
92    buf.copy_from_slice(&bytes);
93
94    Ok(SecretKey::from(buf))
95}
96
97pub fn public_key(bytes: &str) -> Result<PublicKey> {
98    let bytes = BASE64.decode(bytes.as_bytes())?;
99    if bytes.len() != CRYPTO_PUBLIC_KEY_SIZE {
100        return Err(Error::InvalidKeyLength(bytes.len()));
101    }
102
103    let mut buf = [0u8; CRYPTO_PUBLIC_KEY_SIZE];
104    buf.copy_from_slice(&bytes);
105
106    Ok(PublicKey::from(buf))
107}
108
109pub fn test_sodium_crypto<R: Rng>() -> Result<()> {
110    //
111    // Encryption
112    //
113
114    // Generate a random secret key.
115    // NOTE: The secret key bytes can be accessed by calling `secret_key.as_bytes()`
116    let alice_secret_key = generate_secret_key::<R>();
117
118    // Get the public key for the secret key we just generated
119    let alice_public_key_bytes = *alice_secret_key.public_key().as_bytes();
120
121    // Obtain your recipient's public key.
122    let bob_public_key = PublicKey::from([
123        0xe8, 0x98, 0xc, 0x86, 0xe0, 0x32, 0xf1, 0xeb, 0x29, 0x75, 0x5, 0x2e, 0x8d, 0x65, 0xbd,
124        0xdd, 0x15, 0xc3, 0xb5, 0x96, 0x41, 0x17, 0x4e, 0xc9, 0x67, 0x8a, 0x53, 0x78, 0x9d, 0x92,
125        0xc7, 0x54,
126    ]);
127
128    // Create a `SalsaBox` by performing Diffie-Hellman key agreement between
129    // the two keys.
130    let alice_box = SalsaBox::new(&bob_public_key, &alice_secret_key);
131
132    // Message to encrypt
133    let plaintext = b"Top secret message we're encrypting";
134
135    // Encrypt the message using the box
136    let mut ciphertext = [0u8; 4096];
137    let ciphertext = encrypt::<R>(&alice_box, plaintext, &mut ciphertext)?;
138
139    //
140    // Decryption
141    //
142
143    // Either side can encrypt or decrypt messages under the Diffie-Hellman key
144    // they agree upon. The example below shows Bob's side.
145    let bob_secret_key = SecretKey::from([
146        0xb5, 0x81, 0xfb, 0x5a, 0xe1, 0x82, 0xa1, 0x6f, 0x60, 0x3f, 0x39, 0x27, 0xd, 0x4e, 0x3b,
147        0x95, 0xbc, 0x0, 0x83, 0x10, 0xb7, 0x27, 0xa1, 0x1d, 0xd4, 0xe7, 0x84, 0xa0, 0x4, 0x4d,
148        0x46, 0x1b,
149    ]);
150
151    // Deserialize Alice's public key from bytes
152    let alice_public_key = PublicKey::from(alice_public_key_bytes);
153
154    // Bob can compute the same `SalsaBox` as Alice by performing the
155    // key agreement operation.
156    let bob_box = SalsaBox::new(&alice_public_key, &bob_secret_key);
157
158    // Decrypt the message, using the same randomly generated nonce
159    let mut decrypted_plaintext = [0u8; 4096];
160    let decrypted_plaintext = decrypt(&bob_box, ciphertext, &mut decrypted_plaintext)?;
161
162    assert_eq!(&plaintext[..], decrypted_plaintext);
163
164    Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_encrypt() -> Result<()> {
173        let alice_secret_key = generate_secret_key::<Random>();
174
175        // Obtain your recipient's public key.
176        let bob_public_key = PublicKey::from([
177            0xe8, 0x98, 0xc, 0x86, 0xe0, 0x32, 0xf1, 0xeb, 0x29, 0x75, 0x5, 0x2e, 0x8d, 0x65, 0xbd,
178            0xdd, 0x15, 0xc3, 0xb5, 0x96, 0x41, 0x17, 0x4e, 0xc9, 0x67, 0x8a, 0x53, 0x78, 0x9d,
179            0x92, 0xc7, 0x54,
180        ]);
181
182        // Create a `SalsaBox` by performing Diffie-Hellman key agreement between
183        // the two keys.
184        let alice_box = SalsaBox::new(&bob_public_key, &alice_secret_key);
185
186        let mut dest = [0u8; 4096];
187        let ciphertext = encrypt::<Random>(&alice_box, b"hello world", &mut dest)?;
188        assert_eq!(ciphertext.len(), 51);
189
190        Ok(())
191    }
192
193    #[test]
194    fn run_test_sodium_crypto() -> Result<()> {
195        test_sodium_crypto::<Random>()?;
196        Ok(())
197    }
198}