sync15/
enc_payload.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::error;
6use crate::key_bundle::KeyBundle;
7use lazy_static::lazy_static;
8use serde::{Deserialize, Serialize};
9
10/// A representation of an encrypted payload. Used as the payload in EncryptedBso and
11/// also anywhere else the sync keys might be used to encrypt/decrypt, such as send-tab payloads.
12#[derive(Deserialize, Serialize, Clone, Debug)]
13pub struct EncryptedPayload {
14    #[serde(rename = "IV")]
15    pub iv: String,
16    pub hmac: String,
17    pub ciphertext: String,
18}
19
20impl EncryptedPayload {
21    #[inline]
22    pub fn serialized_len(&self) -> usize {
23        (*EMPTY_ENCRYPTED_PAYLOAD_SIZE) + self.ciphertext.len() + self.hmac.len() + self.iv.len()
24    }
25
26    pub fn decrypt(&self, key: &KeyBundle) -> error::Result<String> {
27        key.decrypt(&self.ciphertext, &self.iv, &self.hmac)
28    }
29
30    pub fn decrypt_into<T>(&self, key: &KeyBundle) -> error::Result<T>
31    where
32        for<'a> T: Deserialize<'a>,
33    {
34        Ok(serde_json::from_str(&self.decrypt(key)?)?)
35    }
36
37    pub fn from_cleartext(key: &KeyBundle, cleartext: String) -> error::Result<Self> {
38        let (enc_base64, iv_base64, hmac_base16) =
39            key.encrypt_bytes_rand_iv(cleartext.as_bytes())?;
40        Ok(EncryptedPayload {
41            iv: iv_base64,
42            hmac: hmac_base16,
43            ciphertext: enc_base64,
44        })
45    }
46
47    pub fn from_cleartext_payload<T: Serialize>(
48        key: &KeyBundle,
49        cleartext_payload: &T,
50    ) -> error::Result<Self> {
51        Self::from_cleartext(key, serde_json::to_string(cleartext_payload)?)
52    }
53}
54
55// Our "postqueue", which chunks records for upload, needs to know this value.
56// It's tricky to determine at compile time, so do it once at at runtime.
57lazy_static! {
58    // The number of bytes taken up by padding in a EncryptedPayload.
59    static ref EMPTY_ENCRYPTED_PAYLOAD_SIZE: usize = serde_json::to_string(
60        &EncryptedPayload { iv: "".into(), hmac: "".into(), ciphertext: "".into() }
61    ).unwrap().len();
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use serde_json::json;
68
69    #[derive(Serialize, Deserialize, Debug)]
70    struct TestStruct {
71        id: String,
72        age: u32,
73        meta: String,
74    }
75
76    #[test]
77    fn test_roundtrip_crypt_record() {
78        let key = KeyBundle::new_random().unwrap();
79        let payload_json = json!({ "id": "aaaaaaaaaaaa", "age": 105, "meta": "data" });
80        let payload =
81            EncryptedPayload::from_cleartext(&key, serde_json::to_string(&payload_json).unwrap())
82                .unwrap();
83
84        let record = payload.decrypt_into::<TestStruct>(&key).unwrap();
85        assert_eq!(record.id, "aaaaaaaaaaaa");
86        assert_eq!(record.age, 105);
87        assert_eq!(record.meta, "data");
88
89        // While we're here, check on EncryptedPayload::serialized_len
90        let val_rec = serde_json::to_string(&serde_json::to_value(&payload).unwrap()).unwrap();
91        assert_eq!(payload.serialized_len(), val_rec.len());
92    }
93
94    #[test]
95    fn test_record_bad_hmac() {
96        let key1 = KeyBundle::new_random().unwrap();
97        let json = json!({ "id": "aaaaaaaaaaaa", "deleted": true, });
98
99        let payload =
100            EncryptedPayload::from_cleartext(&key1, serde_json::to_string(&json).unwrap()).unwrap();
101
102        let key2 = KeyBundle::new_random().unwrap();
103        let e = payload
104            .decrypt(&key2)
105            .expect_err("Should fail because wrong keybundle");
106
107        // Note: ErrorKind isn't PartialEq, so.
108        assert!(matches!(e, error::Error::CryptoError(_)));
109    }
110}