encrypted_json_kv/
database.rs1use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use sled;
21use sodiumoxide::crypto::{pwhash, secretbox};
22
23use thiserror::Error;
24
25use std::{fs, path::PathBuf};
26
27use crate::{crypto::derive_key, EncryptedValue, EncryptionError};
28
29pub(crate) const CURRENT_DATABASE_VERSION: usize = 0;
30
31pub struct Database {
33 path: Option<PathBuf>,
34 db: sled::Db,
35 key: secretbox::Key,
36}
37
38#[derive(Error, Debug)]
39pub enum DatabaseOpenError {
40 #[error("file io broke")]
41 Io(#[from] std::io::Error),
42 #[error("the database isn't configured properly")]
43 DbConfig(#[from] DbConfigError),
44 #[error("the version on disk is not supported by the current version")]
45 UnsupportedVersion,
46 #[error("error from sled")]
47 Sled(#[from] sled::Error),
48 #[error("internal config file couldn't be parsed")]
49 TomlRead(#[from] toml::de::Error),
50 #[error("couldn't serialize config file")]
51 TomlWrite(#[from] toml::ser::Error),
52}
53
54#[derive(Error, Debug)]
55pub enum DatabaseError {
56 #[error("error from sled")]
57 Sled(#[from] sled::Error),
58 #[error("")]
59 Encryption(#[from] EncryptionError),
60 #[error("")]
61 Serde(#[from] serde_json::Error),
62}
63
64#[derive(Error, Debug)]
65pub enum DatabaseSetPassphraseError {
66 #[error("toml")]
67 Toml(#[from] toml::ser::Error),
68 #[error("io")]
69 Io(#[from] std::io::Error),
70 #[error("database is temporary")]
71 Temporary,
72}
73
74impl Database {
75 pub fn new(path: PathBuf, passphrase: &[u8]) -> Result<Self, DatabaseOpenError> {
77 fs::create_dir_all(&path)?;
79
80 let mut is_new_db = false;
81
82 let mut db_config_path = path.clone();
84 db_config_path.push("encrypted_json_kv.toml");
85 let db_config = match fs::read_to_string(&db_config_path) {
86 Ok(db_config_string) => toml::from_str(&db_config_string)?,
87 Err(error) => match error.kind() {
88 std::io::ErrorKind::NotFound => {
90 let config = DbConfig::new(passphrase, None);
91 fs::write(db_config_path, toml::to_string(&config)?)?;
92 is_new_db = true;
93 config
94 }
95 _ => Err(error)?,
97 },
98 };
99
100 if db_config.version != CURRENT_DATABASE_VERSION {
102 return Err(DatabaseOpenError::UnsupportedVersion);
103 }
104
105 let key = db_config.get_key(passphrase)?;
107
108 let mut db_path = path.clone();
110 db_path.push("sled");
111 let db = sled::Config::new()
112 .path(db_path)
113 .create_new(is_new_db)
114 .open()?;
115
116 Ok(Database { path: Some(path), db, key })
118 }
119
120 pub fn temporary() -> Result<Self, DatabaseOpenError> {
122 let key = secretbox::gen_key();
123 let db = sled::Config::new()
124 .temporary(true)
125 .open()?;
126 Ok(Database { path: None, db, key })
127 }
128
129 pub fn set_passphrase(&self, passphrase: &[u8]) -> Result<(), DatabaseSetPassphraseError> {
130 match &self.path {
131 Some(path) => {
132 let mut db_config_path = path.clone();
133 db_config_path.push("encrypted_json_kv.toml");
134 let config = DbConfig::new(passphrase, None);
135 fs::write(db_config_path, toml::to_string(&config)?)?;
136 Ok(())
137 },
138 None => Err(DatabaseSetPassphraseError::Temporary)
139 }
140 }
141
142 pub fn get(&self, key: &str) -> Result<Option<Value>, DatabaseError> {
146 match self.db.get(key)? {
147 Some(value) => Ok(Some(serde_json::from_slice::<EncryptedValue>(&value)?.decrypt(&self.key)?)),
148 None => Ok(None),
149 }
150 }
151
152 pub fn insert(&self, key: &str, value: &Value) -> Result<(), DatabaseError> {
156 let value = EncryptedValue::encrypt(value, &self.key);
157 let value = serde_json::to_vec(&value).unwrap();
158 self.db.insert(key, value)?;
159 Ok(())
160 }
161
162 pub fn remove(&self, key: &str) -> Result<Option<Value>, DatabaseError> {
165 match self.db.remove(key)? {
166 Some(value) => Ok(Some(serde_json::from_slice::<EncryptedValue>(&value)?.decrypt(&self.key)?)),
167 None => Ok(None),
168 }
169 }
170
171 pub fn keys(&self) -> impl DoubleEndedIterator<Item = sled::Result<sled::IVec>> {
173 self.db.iter().keys()
174 }
175
176 pub fn encryption_key(&self) -> &secretbox::Key {
178 &self.key
179 }
180}
181
182#[derive(Serialize, Deserialize)]
183pub(crate) struct DbConfig {
184 version: usize,
185 salt: pwhash::Salt,
186 encrypted_key: EncryptedValue,
187}
188
189impl DbConfig {
190 pub(crate) fn new(passphrase: &[u8], key: Option<secretbox::Key>) -> DbConfig {
191 let salt = pwhash::gen_salt();
192 let outer_key = derive_key(passphrase, salt);
193 let inner_key = key.unwrap_or_else(|| secretbox::gen_key());
194 let encrypted_key =
195 EncryptedValue::encrypt(&Value::String(base64::encode(inner_key)), &outer_key);
196 DbConfig {
197 version: CURRENT_DATABASE_VERSION,
198 salt,
199 encrypted_key,
200 }
201 }
202
203 pub(crate) fn get_key(&self, passphrase: &[u8]) -> Result<secretbox::Key, DbConfigError> {
204 let outer_key = derive_key(passphrase, self.salt);
205 let inner_key_value = self.encrypted_key.decrypt(&outer_key)?;
206 let inner_key_encoded = inner_key_value.as_str().ok_or_else(|| DbConfigError::WrongKeyType)?;
207 let inner_key_bytes = base64::decode(inner_key_encoded)?;
208 Ok(secretbox::Key::from_slice(&inner_key_bytes).ok_or_else(|| DbConfigError::WrongKeySize)?)
209 }
210}
211
212#[derive(Error, Debug)]
213pub enum DbConfigError {
214 #[error("couldn't decrypt database key")]
215 Encryption(#[from] EncryptionError),
216 #[error("base64 decoding of the key didn't work")]
217 Encoding(#[from] base64::DecodeError),
218 #[error("key was not a string after decrypting and parsing")]
219 WrongKeyType,
220 #[error("key has the wrong size")]
221 WrongKeySize,
222}