private_box/
private_box.rs

1use core::mem::size_of;
2use ssb_crypto::{Keypair, PublicKey};
3use zerocopy::{AsBytes, FromBytes, LayoutVerified};
4
5// TODO: turns out these things are used for more than just the handshake,
6// so the ssb_crypto submodule should probably be renamed.
7use ssb_crypto::ephemeral::{
8    derive_shared_secret_pk, derive_shared_secret_sk, generate_ephemeral_keypair, EphPublicKey,
9};
10use ssb_crypto::secretbox::{Hmac, Key, Nonce};
11
12const MAX_RECIPIENTS: usize = 8;
13
14/// libsodium must be initialised before calling `encrypt` or `decrypt`.
15/// If you're using other libsodium based libraries that already initialise libsodium, you can omit
16/// the call to `init`.
17#[cfg(feature = "sodium")]
18pub fn init() {
19    ssb_crypto::sodium::init();
20}
21
22#[derive(AsBytes, FromBytes)]
23#[repr(C, packed)]
24pub struct MsgKey {
25    recp_count: u8,
26    key: Key,
27}
28impl MsgKey {
29    fn zeroed() -> MsgKey {
30        MsgKey {
31            recp_count: 0,
32            key: Key([0; 32]),
33        }
34    }
35    pub fn as_array(&self) -> [u8; 33] {
36        let mut out = [0; 33];
37        out.copy_from_slice(self.as_bytes());
38        out
39    }
40}
41
42#[derive(AsBytes, FromBytes)]
43#[repr(C, packed)]
44struct BoxedKey {
45    hmac: Hmac,
46    msg_key: [u8; 33],
47}
48
49/// == 72 + recps.len() * 49 + text.len()
50pub fn encrypted_size(text: &[u8], recps: &[PublicKey]) -> usize {
51    size_of::<Nonce>()                        //   24
52        + size_of::<EphPublicKey>()           // + 32
53        + recps.len() * size_of::<BoxedKey>() // + recps.len() * 49
54        + size_of::<Hmac>()                   // + 16
55        + text.len()
56}
57
58fn set_prefix<'a>(buf: &'a mut [u8], prefix: &[u8]) -> &'a mut [u8] {
59    let (p, rest) = buf.split_at_mut(prefix.len());
60    p.copy_from_slice(prefix);
61    rest
62}
63
64/// Takes the message you want to encrypt, and an array of recipient public keys.
65/// Returns a message that is encrypted to all recipients and openable by them
66/// with `private_box::decrypt`. The number of recipients must be between 1 and 32.
67///
68/// The encrypted length will be 56 + (recipients.len() * 33) + plaintext.len().
69///
70/// # Example
71/// ```
72/// use private_box::{encrypt, decrypt};
73/// use ssb_crypto::Keypair;
74///
75/// fn main() {
76///     let msg = "hello!".as_bytes();
77///
78///     let alice = Keypair::generate();
79///     let bob = Keypair::generate();
80///
81///     let recps = [alice.public, bob.public];
82///     let cypher = encrypt(msg, &recps);
83///
84///     let alice_result = decrypt(&cypher, &alice);
85///     let bob_result = decrypt(&cypher, &bob);
86///
87///     assert_eq!(alice_result.unwrap(), msg);
88///     assert_eq!(bob_result.unwrap(), msg);
89/// }
90/// ```
91pub fn encrypt(plaintext: &[u8], recipients: &[PublicKey]) -> Vec<u8> {
92    let mut out = vec![0; encrypted_size(plaintext, recipients)];
93    encrypt_into(plaintext, recipients, &mut out);
94    out
95}
96
97pub fn encrypt_into(plaintext: &[u8], recipients: &[PublicKey], mut out: &mut [u8]) {
98    if recipients.len() > MAX_RECIPIENTS || recipients.len() == 0 {
99        panic!(
100            "Number of recipients must be less than {}, greater than 0",
101            MAX_RECIPIENTS
102        );
103    }
104    assert!(out.len() >= encrypted_size(plaintext, recipients));
105
106    let nonce = Nonce::generate();
107    let (eph_pk, eph_sk) = generate_ephemeral_keypair();
108
109    let mkey = MsgKey {
110        recp_count: recipients.len() as u8,
111        key: Key::generate(),
112    };
113
114    let mut rest = set_prefix(&mut out, nonce.as_bytes());
115    let rest = set_prefix(&mut rest, eph_pk.as_bytes());
116    let (keys, rest) = rest.split_at_mut(recipients.len() * size_of::<BoxedKey>());
117    let mut keychunks = keys.chunks_mut(size_of::<BoxedKey>());
118
119    for pk in recipients {
120        let kkey = Key(derive_shared_secret_pk(&eph_sk, pk).unwrap().0);
121        let mut msg_key = mkey.as_array();
122        let hmac = kkey.seal(&mut msg_key, &nonce);
123        keychunks
124            .next()
125            .unwrap()
126            .copy_from_slice(BoxedKey { hmac, msg_key }.as_bytes());
127    }
128
129    let (hmac_buf, text) = rest.split_at_mut(Hmac::SIZE);
130    text.copy_from_slice(plaintext);
131
132    let hmac = mkey.key.seal(text, &nonce);
133    hmac_buf.copy_from_slice(hmac.as_bytes());
134}
135
136const BOXED_KEY_SIZE_BYTES: usize = 32 + 1 + 16;
137
138/// Attempt to decrypt a private-box message, using your secret key.
139/// If you were an intended recipient then the decrypted message is
140/// returned as `Some(Vec<u8>)`. If it was not for you, then `None`
141/// will be returned.
142///
143/// # Example
144/// ```
145/// use private_box::{encrypt, decrypt};
146/// use ssb_crypto::Keypair;
147///
148/// fn main() {
149///     let msg = "hello!".as_bytes();
150///
151///     let alice = Keypair::generate();
152///     let bob = Keypair::generate();
153///
154///     let recps = [alice.public, bob.public];
155///     let cypher = encrypt(msg, &recps);
156///
157///     let alice_result = decrypt(&cypher, &alice);
158///     let bob_result = decrypt(&cypher, &bob);
159///
160///     assert_eq!(&alice_result.unwrap(), &msg);
161///     assert_eq!(&bob_result.unwrap(), &msg);
162/// }
163///```
164pub fn decrypt(cyphertext: &[u8], keypair: &Keypair) -> Option<Vec<u8>> {
165    let msg_key = decrypt_key(cyphertext, keypair)?;
166    decrypt_body(cyphertext, &msg_key)
167}
168
169// exposed for ssb-neon-keys
170pub fn decrypt_key(cyphertext: &[u8], keypair: &Keypair) -> Option<MsgKey> {
171    let nonce = Nonce::from_slice(&cyphertext[0..24])?;
172    let eph_pk = EphPublicKey::from_slice(&cyphertext[24..56])?;
173
174    let key_key = Key(derive_shared_secret_sk(&keypair.secret, &eph_pk)?.0);
175    let mut msg_key = MsgKey::zeroed();
176
177    &cyphertext[56..]
178        .chunks_exact(BOXED_KEY_SIZE_BYTES)
179        .take(MAX_RECIPIENTS)
180        .find(|b| key_key.open_attached_into(b, &nonce, msg_key.as_bytes_mut()))?;
181
182    Some(msg_key)
183}
184
185// exposed for ssb-neon-keys
186pub fn decrypt_body(cyphertext: &[u8], msg_key: &MsgKey) -> Option<Vec<u8>> {
187    let nonce = Nonce::from_slice(&cyphertext[0..24])?;
188    let boxed_msg = &cyphertext[(56 + BOXED_KEY_SIZE_BYTES * msg_key.recp_count as usize)..];
189    let mut out = vec![0; boxed_msg.len() - Hmac::SIZE];
190    if msg_key.key.open_attached_into(&boxed_msg, &nonce, &mut out) {
191        Some(out)
192    } else {
193        None
194    }
195}
196
197/// Panics if `msg_key` len is not 33 bytes.
198pub fn decrypt_body_with_key_bytes(cyphertext: &[u8], msg_key: &[u8]) -> Option<Vec<u8>> {
199    let key = LayoutVerified::<&[u8], MsgKey>::new(msg_key)
200        .unwrap()
201        .into_ref();
202    decrypt_body(cyphertext, key)
203}
204
205#[cfg(test)]
206mod tests {
207    use crate::*;
208    use base64::decode;
209    use serde_derive::{Deserialize, Serialize};
210    use serde_json;
211
212    use std::error::Error;
213    use std::fs::File;
214    use std::path::Path;
215
216    use ssb_crypto::Keypair;
217
218    #[derive(Serialize, Deserialize)]
219    struct Key {
220        secret: String,
221        public: String,
222    }
223
224    #[derive(Serialize, Deserialize)]
225    struct TestData {
226        cypher_text: String,
227        msg: String,
228        keys: Vec<Key>,
229    }
230
231    fn read_test_data_from_file<P: AsRef<Path>>(path: P) -> Result<TestData, Box<dyn Error>> {
232        let file = File::open(path)?;
233        let t = serde_json::from_reader(file)?;
234        Ok(t)
235    }
236
237    #[test]
238    fn simple() {
239        let msg: [u8; 3] = [0, 1, 2];
240
241        // init();
242        let alice = Keypair::generate();
243        let bob = Keypair::generate();
244
245        let recps = [alice.public, bob.public];
246        let cypher = encrypt(&msg, &recps);
247
248        let alice_result = decrypt(&cypher, &alice);
249        let bob_result = decrypt(&cypher, &bob);
250
251        assert_eq!(alice_result.unwrap(), msg);
252        assert_eq!(bob_result.unwrap(), msg);
253    }
254
255    #[test]
256    fn is_js_compatible() {
257        let test_data = read_test_data_from_file("./test/simple.json").unwrap();
258
259        let cypher = decode(&test_data.cypher_text).unwrap();
260        let keys: Vec<Keypair> = test_data
261            .keys
262            .iter()
263            .map(|key| Keypair::from_base64(&key.secret).unwrap())
264            .collect();
265
266        let alice = &keys[0];
267        let bob = &keys[1];
268
269        // init();
270        assert_eq!(decrypt(&cypher, &alice).unwrap(), test_data.msg.as_bytes());
271        assert_eq!(decrypt(&cypher, &bob).unwrap(), test_data.msg.as_bytes());
272    }
273    #[test]
274    #[should_panic]
275    fn passing_too_many_recipients_panics() {
276        let msg: [u8; 3] = [0, 1, 2];
277
278        // init();
279        let alice = Keypair::generate();
280        let recps = vec![alice.public; 9];
281        let _ = encrypt(&msg, &recps);
282    }
283    #[test]
284    #[should_panic]
285    fn passing_zero_recipients_panics() {
286        let msg: [u8; 3] = [0, 1, 2];
287
288        // init();
289
290        let recps: [PublicKey; 0] = [];
291        let _ = encrypt(&msg, &recps);
292    }
293}