renox_core/db/
encrypted.rs1use std::cell::RefCell;
4use std::fmt;
5use std::ops::{Deref, DerefMut};
6use std::sync::Arc;
7
8use cookie::Key;
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11
12use super::{DbValue, ToDbValue};
13
14#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct Encrypted<T>(T);
52
53impl<T> Encrypted<T> {
54 pub fn new(value: T) -> Self {
56 Self(value)
57 }
58
59 pub fn into_inner(self) -> T {
61 self.0
62 }
63}
64
65impl<T> From<T> for Encrypted<T> {
66 fn from(value: T) -> Self {
67 Self(value)
68 }
69}
70
71impl<T> Deref for Encrypted<T> {
72 type Target = T;
73
74 fn deref(&self) -> &T {
75 &self.0
76 }
77}
78
79impl<T> DerefMut for Encrypted<T> {
80 fn deref_mut(&mut self) -> &mut T {
81 &mut self.0
82 }
83}
84
85impl<T> fmt::Debug for Encrypted<T> {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str("Encrypted(..)")
88 }
89}
90
91impl<T: Serialize> Serialize for Encrypted<T> {
92 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93 self.0.serialize(serializer)
94 }
95}
96
97impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Encrypted<T> {
98 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
99 T::deserialize(deserializer).map(Self)
100 }
101}
102
103impl<T: Serialize> ToDbValue for Encrypted<T> {
104 fn to_db_value(&self) -> DbValue {
107 DbValue::Encrypted(Unsealed(serde_json::to_string(&self.0).unwrap_or_default()))
108 }
109}
110
111#[derive(Clone, PartialEq)]
114pub struct Unsealed(pub(crate) String);
115
116impl fmt::Debug for Unsealed {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 f.write_str("..")
119 }
120}
121
122const COLUMN: &str = "renox.column";
125
126pub(crate) fn seal(key: &Key, plain: &str) -> String {
128 let mut jar = cookie::CookieJar::new();
129 jar.private_mut(key)
130 .add(cookie::Cookie::new(COLUMN, plain.to_owned()));
131 jar.get(COLUMN)
132 .map(|sealed| sealed.value().to_owned())
133 .unwrap_or_default()
134}
135
136fn open(key: &Key, sealed: &str) -> Option<String> {
137 cookie::CookieJar::new()
138 .private(key)
139 .decrypt(cookie::Cookie::new(COLUMN, sealed.to_owned()))
140 .map(|plain| plain.value().to_owned())
141}
142
143thread_local! {
144 static READING: RefCell<Option<Arc<Key>>> = const { RefCell::new(None) };
146}
147
148pub(crate) fn reading<R>(key: Option<&Arc<Key>>, decode: impl FnOnce() -> R) -> R {
150 let previous = READING.with(|current| current.replace(key.cloned()));
151 let result = decode();
152 READING.with(|current| *current.borrow_mut() = previous);
153 result
154}
155
156fn decode_sealed<T: DeserializeOwned>(
157 sealed: &str,
158) -> Result<Encrypted<T>, sqlx::error::BoxDynError> {
159 let key = READING
160 .with(|current| current.borrow().clone())
161 .ok_or("an Encrypted column was read without a key: read it through the app's Db")?;
162 let plain = open(&key, sealed).ok_or(
163 "an Encrypted column can't be decrypted with this APP_KEY (changed, or another key)",
164 )?;
165 Ok(Encrypted(serde_json::from_str(&plain)?))
166}
167
168macro_rules! encrypted_column {
169 ($db:ty) => {
170 impl<T> sqlx::Type<$db> for Encrypted<T> {
171 fn type_info() -> <$db as sqlx::Database>::TypeInfo {
172 <String as sqlx::Type<$db>>::type_info()
173 }
174
175 fn compatible(ty: &<$db as sqlx::Database>::TypeInfo) -> bool {
176 <String as sqlx::Type<$db>>::compatible(ty)
177 }
178 }
179
180 impl<'r, T: DeserializeOwned> sqlx::Decode<'r, $db> for Encrypted<T> {
181 fn decode(
182 value: <$db as sqlx::Database>::ValueRef<'r>,
183 ) -> Result<Self, sqlx::error::BoxDynError> {
184 let sealed = <String as sqlx::Decode<$db>>::decode(value)?;
185 decode_sealed(&sealed)
186 }
187 }
188 };
189}
190
191encrypted_column!(sqlx::sqlite::Sqlite);
192#[cfg(feature = "postgres")]
193encrypted_column!(sqlx::postgres::Postgres);
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn sealed_values_open_with_their_key_only() {
201 let key = Key::generate();
202 let sealed = seal(&key, "\"BCA 123\"");
203 assert!(!sealed.contains("BCA"));
204 assert_ne!(sealed, seal(&key, "\"BCA 123\""), "a fresh nonce each time");
205 let read = reading(Some(&Arc::new(key)), || decode_sealed::<String>(&sealed));
206 assert_eq!(read.unwrap().into_inner(), "BCA 123");
207 let other = reading(Some(&Arc::new(Key::generate())), || {
208 decode_sealed::<String>(&sealed)
209 });
210 assert!(other.is_err());
211 assert!(reading(None, || decode_sealed::<String>(&sealed)).is_err());
212 assert_eq!(format!("{:?}", Encrypted::new("secret")), "Encrypted(..)");
213 }
214}