use std::cell::RefCell;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use cookie::Key;
use serde::Serialize;
use serde::de::DeserializeOwned;
use super::{DbValue, ToDbValue};
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Encrypted<T>(T);
impl<T> Encrypted<T> {
pub fn new(value: T) -> Self {
Self(value)
}
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> From<T> for Encrypted<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T> Deref for Encrypted<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for Encrypted<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> fmt::Debug for Encrypted<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Encrypted(..)")
}
}
impl<T: Serialize> Serialize for Encrypted<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Encrypted<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
T::deserialize(deserializer).map(Self)
}
}
impl<T: Serialize> ToDbValue for Encrypted<T> {
fn to_db_value(&self) -> DbValue {
DbValue::Encrypted(Unsealed(serde_json::to_string(&self.0).unwrap_or_default()))
}
}
#[derive(Clone, PartialEq)]
pub struct Unsealed(pub(crate) String);
impl fmt::Debug for Unsealed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("..")
}
}
const COLUMN: &str = "renox.column";
pub(crate) fn seal(key: &Key, plain: &str) -> String {
let mut jar = cookie::CookieJar::new();
jar.private_mut(key)
.add(cookie::Cookie::new(COLUMN, plain.to_owned()));
jar.get(COLUMN)
.map(|sealed| sealed.value().to_owned())
.unwrap_or_default()
}
fn open(key: &Key, sealed: &str) -> Option<String> {
cookie::CookieJar::new()
.private(key)
.decrypt(cookie::Cookie::new(COLUMN, sealed.to_owned()))
.map(|plain| plain.value().to_owned())
}
thread_local! {
static READING: RefCell<Option<Arc<Key>>> = const { RefCell::new(None) };
}
pub(crate) fn reading<R>(key: Option<&Arc<Key>>, decode: impl FnOnce() -> R) -> R {
let previous = READING.with(|current| current.replace(key.cloned()));
let result = decode();
READING.with(|current| *current.borrow_mut() = previous);
result
}
fn decode_sealed<T: DeserializeOwned>(
sealed: &str,
) -> Result<Encrypted<T>, sqlx::error::BoxDynError> {
let key = READING
.with(|current| current.borrow().clone())
.ok_or("an Encrypted column was read without a key: read it through the app's Db")?;
let plain = open(&key, sealed).ok_or(
"an Encrypted column can't be decrypted with this APP_KEY (changed, or another key)",
)?;
Ok(Encrypted(serde_json::from_str(&plain)?))
}
macro_rules! encrypted_column {
($db:ty) => {
impl<T> sqlx::Type<$db> for Encrypted<T> {
fn type_info() -> <$db as sqlx::Database>::TypeInfo {
<String as sqlx::Type<$db>>::type_info()
}
fn compatible(ty: &<$db as sqlx::Database>::TypeInfo) -> bool {
<String as sqlx::Type<$db>>::compatible(ty)
}
}
impl<'r, T: DeserializeOwned> sqlx::Decode<'r, $db> for Encrypted<T> {
fn decode(
value: <$db as sqlx::Database>::ValueRef<'r>,
) -> Result<Self, sqlx::error::BoxDynError> {
let sealed = <String as sqlx::Decode<$db>>::decode(value)?;
decode_sealed(&sealed)
}
}
};
}
encrypted_column!(sqlx::sqlite::Sqlite);
#[cfg(feature = "postgres")]
encrypted_column!(sqlx::postgres::Postgres);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sealed_values_open_with_their_key_only() {
let key = Key::generate();
let sealed = seal(&key, "\"BCA 123\"");
assert!(!sealed.contains("BCA"));
assert_ne!(sealed, seal(&key, "\"BCA 123\""), "a fresh nonce each time");
let read = reading(Some(&Arc::new(key)), || decode_sealed::<String>(&sealed));
assert_eq!(read.unwrap().into_inner(), "BCA 123");
let other = reading(Some(&Arc::new(Key::generate())), || {
decode_sealed::<String>(&sealed)
});
assert!(other.is_err());
assert!(reading(None, || decode_sealed::<String>(&sealed)).is_err());
assert_eq!(format!("{:?}", Encrypted::new("secret")), "Encrypted(..)");
}
}