use serde::{
de::{self, Deserialize, Deserializer, Visitor},
ser::{Serialize, Serializer},
};
use serde_json::Value;
use sodiumoxide::crypto::{pwhash, secretbox};
use thiserror::Error;
use std::fmt;
pub(crate) fn derive_key(passphrase: &[u8], salt: pwhash::Salt) -> secretbox::Key {
let mut key = secretbox::Key([0; secretbox::KEYBYTES]);
{
let secretbox::Key(ref mut key_bytes) = key;
pwhash::derive_key(
key_bytes,
passphrase,
&salt,
pwhash::OPSLIMIT_INTERACTIVE,
pwhash::MEMLIMIT_INTERACTIVE,
)
.unwrap();
}
key
}
#[derive(Debug, Clone)]
pub struct EncryptedValue {
nonce: secretbox::Nonce,
ciphertext: Vec<u8>,
}
impl Serialize for EncryptedValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!(
"{}_{}",
base64::encode(self.nonce.as_ref()),
base64::encode(&self.ciphertext)
))
}
}
struct EncryptedValueVisitor;
impl<'de> Visitor<'de> for EncryptedValueVisitor {
type Value = EncryptedValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string with two base64 encoded values separated with an underscore")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let parts: Vec<&str> = s.split("_").collect();
if parts.len() != 2 {
return Err(E::custom(format!(
"expected two base64 encoded values separated with an underscore, got {}",
parts.len()
)));
}
let (nonce, ciphertext) = match (base64::decode(parts[0]), base64::decode(parts[1])) {
(Ok(nonce), Ok(ciphertext)) => {
let nonce = secretbox::Nonce::from_slice(&nonce)
.ok_or_else(|| E::custom(format!("nonce part was not 24 bits long")))?;
(nonce, ciphertext)
}
_ => Err(E::custom(format!("couldn't decode one of the bas64 parts")))?,
};
Ok(EncryptedValue { nonce, ciphertext })
}
}
impl<'de> Deserialize<'de> for EncryptedValue {
fn deserialize<D>(deserializer: D) -> Result<EncryptedValue, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(EncryptedValueVisitor)
}
}
impl EncryptedValue {
pub fn encrypt(value: &Value, key: &secretbox::Key) -> Self {
let plaintext = serde_json::to_vec(value).unwrap();
let nonce = secretbox::gen_nonce();
Self {
nonce,
ciphertext: secretbox::seal(&plaintext, &nonce, key),
}
}
pub fn decrypt(&self, key: &secretbox::Key) -> Result<Value, EncryptionError> {
let plaintext = secretbox::open(&self.ciphertext, &self.nonce, key)
.map_err(|_| EncryptionError::WrongKey)?;
Ok(serde_json::from_slice(&plaintext)?)
}
}
#[derive(Error, Debug)]
pub enum EncryptionError {
#[error("can't decrypt the ciphertext with the given key")]
WrongKey,
#[error("couldn't deserialize the value")]
Serde(#[from] serde_json::Error),
}