encrypted_json_kv/
crypto.rs

1/********************************************************************************
2 *   Encrypted KV store for json blobs based on sled                            *
3 *   Copyright (C) 2020 Famedly GmbH                                            *
4 *                                                                              *
5 *   This program is free software: you can redistribute it and/or modify       *
6 *   it under the terms of the GNU Affero General Public License as             *
7 *   published by the Free Software Foundation, either version 3 of the         *
8 *   License, or (at your option) any later version.                            *
9 *                                                                              *
10 *   This program is distributed in the hope that it will be useful,            *
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the               *
13 *   GNU Affero General Public License for more details.                        *
14 *                                                                              *
15 *   You should have received a copy of the GNU Affero General Public License   *
16 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.     *
17 ********************************************************************************/
18use serde::{
19    de::{self, Deserialize, Deserializer, Visitor},
20    ser::{Serialize, Serializer},
21};
22use serde_json::Value;
23
24use sodiumoxide::crypto::{pwhash, secretbox};
25
26use thiserror::Error;
27
28use std::fmt;
29
30/// Uses the key derivation function from sodiumoxide to derive a key from a potentially weak
31/// passphrase and a salt
32pub(crate) fn derive_key(passphrase: &[u8], salt: pwhash::Salt) -> secretbox::Key {
33    let mut key = secretbox::Key([0; secretbox::KEYBYTES]);
34    {
35        let secretbox::Key(ref mut key_bytes) = key;
36        pwhash::derive_key(
37            key_bytes,
38            passphrase,
39            &salt,
40            pwhash::OPSLIMIT_INTERACTIVE,
41            pwhash::MEMLIMIT_INTERACTIVE,
42        )
43        .unwrap();
44    }
45    key
46}
47
48#[derive(Debug, Clone)]
49/// EncryptionWrapper around serde_json::Value, using sodiumoxide's secretbox. Serializes into a
50/// string using base64 encoding.
51pub struct EncryptedValue {
52    nonce: secretbox::Nonce,
53    ciphertext: Vec<u8>,
54}
55
56impl Serialize for EncryptedValue {
57    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58    where
59        S: Serializer,
60    {
61        serializer.serialize_str(&format!(
62            "{}_{}",
63            base64::encode(self.nonce.as_ref()),
64            base64::encode(&self.ciphertext)
65        ))
66    }
67}
68struct EncryptedValueVisitor;
69
70impl<'de> Visitor<'de> for EncryptedValueVisitor {
71    type Value = EncryptedValue;
72
73    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
74        formatter.write_str("a string with two base64 encoded values separated with an underscore")
75    }
76
77    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
78    where
79        E: de::Error,
80    {
81        let parts: Vec<&str> = s.split("_").collect();
82        if parts.len() != 2 {
83            return Err(E::custom(format!(
84                "expected two base64 encoded values separated with an underscore, got {}",
85                parts.len()
86            )));
87        }
88        let (nonce, ciphertext) = match (base64::decode(parts[0]), base64::decode(parts[1])) {
89            (Ok(nonce), Ok(ciphertext)) => {
90                let nonce = secretbox::Nonce::from_slice(&nonce)
91                    .ok_or_else(|| E::custom(format!("nonce part was not 24 bits long")))?;
92                (nonce, ciphertext)
93            }
94            _ => Err(E::custom(format!("couldn't decode one of the bas64 parts")))?,
95        };
96        Ok(EncryptedValue { nonce, ciphertext })
97    }
98}
99
100impl<'de> Deserialize<'de> for EncryptedValue {
101    fn deserialize<D>(deserializer: D) -> Result<EncryptedValue, D::Error>
102    where
103        D: Deserializer<'de>,
104    {
105        deserializer.deserialize_str(EncryptedValueVisitor)
106    }
107}
108
109impl EncryptedValue {
110    /// Serialize the passed value into a Vec<u8>, encrypt it and then wrap the ciphertext and
111    /// nonce into a new instance of Self
112    pub fn encrypt(value: &Value, key: &secretbox::Key) -> Self {
113        // unwrapping the json encoding here is pretty safe, considering we're trying to turn
114        // serde_json::Value into a json String. We could return a result here, but it feels
115        // unnecessary
116        let plaintext = serde_json::to_vec(value).unwrap();
117        let nonce = secretbox::gen_nonce();
118        Self {
119            nonce,
120            ciphertext: secretbox::seal(&plaintext, &nonce, key),
121        }
122    }
123
124    /// Decrypt the ciphertext with libsodium's secretbox and then deserialize the plaintext.
125    pub fn decrypt(&self, key: &secretbox::Key) -> Result<Value, EncryptionError> {
126        let plaintext = secretbox::open(&self.ciphertext, &self.nonce, key)
127            .map_err(|_| EncryptionError::WrongKey)?;
128        Ok(serde_json::from_slice(&plaintext)?)
129    }
130}
131
132#[derive(Error, Debug)]
133pub enum EncryptionError {
134    #[error("can't decrypt the ciphertext with the given key")]
135    WrongKey,
136    #[error("couldn't deserialize the value")]
137    Serde(#[from] serde_json::Error),
138}
139