1use std::path::Path;
9
10use base64::{Engine, engine::general_purpose::STANDARD};
11use kernoid_e2e::{Kind, PublicKey, SecretKey, message};
12use serde::Deserialize;
13use serde_json::{Value, json};
14use tokio::io::AsyncWriteExt;
15
16use super::{E2e, OPENPGP, decode, list};
17use crate::{
18 Client, Error, FILENODE, Result,
19 files::{FileNode, Principal, media_type},
20};
21
22const USING: [&str; 2] = [FILENODE, OPENPGP];
23
24fn random_uuid() -> String {
26 let mut bytes = [0u8; 16];
27 getrandom::fill(&mut bytes).expect("the system's random numbers");
28 bytes[6] = (bytes[6] & 0x0f) | 0x40;
29 bytes[8] = (bytes[8] & 0x3f) | 0x80;
30 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
31 format!("{}-{}-{}-{}-{}", &hex[..8], &hex[8..12], &hex[12..16], &hex[16..20], &hex[20..])
32}
33
34fn opaque_name() -> String {
36 format!("k-{}", random_uuid())
37}
38
39#[derive(Deserialize)]
40struct WrappedKey {
41 #[serde(default)]
42 sealed: Option<String>,
43 #[serde(default)]
44 members: Vec<String>,
45}
46
47impl E2e {
48 pub fn vaults_allowed(&self) -> bool {
50 self.available && self.settings.policy.vaults != "forbidden"
51 }
52
53 fn need_unlocked(&self) -> Result<&SecretKey> {
54 self.current.as_ref().ok_or_else(|| Error::E2e("unlock your key first".into()))
55 }
56
57 async fn wrapped(client: &Client, account: &str, vault: &str) -> Result<WrappedKey> {
59 let result = client.call(&USING, "VaultKey/get", json!({ "accountId": account, "ids": [vault] })).await?;
60 list::<WrappedKey>(&result)?.into_iter().next().ok_or_else(|| Error::E2e("no such vault".into()))
61 }
62
63 async fn vault_key(&mut self, client: &Client, account: &str, vault: &str) -> Result<Option<SecretKey>> {
66 if let Some(key) = self.vaults.get(vault) {
67 return Ok(Some(key.clone()));
68 }
69 self.need_unlocked()?;
70 let Some(sealed) = Self::wrapped(client, account, vault).await?.sealed else {
71 return Ok(None);
72 };
73 let Ok(opened) = message::decrypt(&decode(&sealed)?, &self.secrets(), &[]) else {
74 return Ok(None);
75 };
76 let key = SecretKey::import(&opened.data)?;
77 self.vaults.insert(vault.to_owned(), key.clone());
78 Ok(Some(key))
79 }
80
81 async fn need_vault_key(&mut self, client: &Client, account: &str, vault: &str) -> Result<SecretKey> {
82 self.need_unlocked()?;
83 self.vault_key(client, account, vault)
84 .await?
85 .ok_or_else(|| Error::E2e("no member gave you this vault's key yet".into()))
86 }
87
88 pub async fn open_names(&mut self, client: &Client, account: &str, nodes: &mut [FileNode]) {
90 for node in nodes.iter_mut() {
91 let (Some(vault), Some(sealed)) = (node.vault.clone(), node.encrypted_name.clone()) else { continue };
92 node.locked = true;
93 if let Some(name) = self.open_name(client, account, &vault, &sealed).await {
94 node.name = name;
95 node.locked = false;
96 }
97 }
98 }
99
100 pub async fn open_name(&mut self, client: &Client, account: &str, vault: &str, sealed: &str) -> Option<String> {
102 self.current.as_ref()?;
103 let key = self.vault_key(client, account, vault).await.ok().flatten()?;
104 decode(sealed)
105 .ok()
106 .and_then(|bytes| message::decrypt(&bytes, &[&key], &[]).ok())
107 .and_then(|opened| String::from_utf8(opened.data).ok())
108 }
109
110 pub async fn name_in(
113 &mut self,
114 client: &Client,
115 account: &str,
116 vault: &str,
117 name: &str,
118 ) -> Result<(String, String)> {
119 let key = self.need_vault_key(client, account, vault).await?;
120 Ok((opaque_name(), seal(&key.public(), name)?))
121 }
122
123 pub async fn seal_content(
125 &mut self,
126 client: &Client,
127 account: &str,
128 vault: &str,
129 data: Vec<u8>,
130 ) -> Result<Vec<u8>> {
131 let key = self.need_vault_key(client, account, vault).await?;
132 let signer = self.need_unlocked()?.clone();
133 let public = key.public();
134 tokio::task::spawn_blocking(move || message::encrypt(&data, &[&public], Some(&signer), false))
135 .await
136 .map_err(|error| Error::E2e(error.to_string()))?
137 .map_err(Into::into)
138 }
139
140 pub async fn open_content(
142 &mut self,
143 client: &Client,
144 account: &str,
145 vault: &str,
146 sealed: Vec<u8>,
147 ) -> Result<Vec<u8>> {
148 let key = self.need_vault_key(client, account, vault).await?;
149 let opened = tokio::task::spawn_blocking(move || message::decrypt(&sealed, &[&key], &[]))
150 .await
151 .map_err(|error| Error::E2e(error.to_string()))??;
152 Ok(opened.data)
153 }
154
155 pub async fn move_in(
157 &mut self,
158 client: &Client,
159 account: &str,
160 vault: &str,
161 id: &str,
162 parent: &str,
163 name: &str,
164 ) -> Result<()> {
165 let (opaque, sealed) = self.name_in(client, account, vault, name).await?;
166 let update =
167 json!({ "update": { id: { "parentId": parent, "name": opaque, "kernoid:encryptedName": sealed } } });
168 client.set(&USING, "FileNode", account, update, json!({})).await?;
169 Ok(())
170 }
171
172 pub async fn create_vault(
175 &mut self,
176 client: &Client,
177 account: &str,
178 parent: Option<&str>,
179 name: &str,
180 ) -> Result<String> {
181 let own = self.need_unlocked()?.public();
182 let address = format!("vault-{}@vault.invalid", random_uuid());
183 let key = tokio::task::spawn_blocking(move || SecretKey::generate(Kind::Pqc, &[address.as_str()]))
185 .await
186 .map_err(|error| Error::E2e(error.to_string()))??;
187 let public = key.public();
188 let sealed = message::encrypt(key.export()?.as_bytes(), &[&own], None, false)?;
189 let create = json!({ "create": { "v": {
190 "@type": "Directory",
191 "parentId": parent,
192 "name": opaque_name(),
193 "kernoid:encryptedName": seal(&public, name)?,
194 "kernoid:newVault": { "publicKey": STANDARD.encode(public.to_bytes()?), "sealed": STANDARD.encode(sealed) },
195 } } });
196 let result = client.set(&USING, "FileNode", account, create, json!({})).await?;
197 let id = created_id(&result, "v")?;
198 self.vaults.insert(id.clone(), key);
199 Ok(id)
200 }
201
202 pub async fn create_directory_in(
204 &mut self,
205 client: &Client,
206 account: &str,
207 vault: &str,
208 parent: &str,
209 name: &str,
210 ) -> Result<()> {
211 self.make_directory_in(client, account, vault, parent, name).await.map(drop)
212 }
213
214 pub async fn make_directory_in(
216 &mut self,
217 client: &Client,
218 account: &str,
219 vault: &str,
220 parent: &str,
221 name: &str,
222 ) -> Result<String> {
223 let (opaque, sealed) = self.name_in(client, account, vault, name).await?;
224 let create = json!({ "create": { "d": {
225 "@type": "Directory", "parentId": parent, "name": opaque, "kernoid:encryptedName": sealed,
226 } } });
227 let result = client.set(&USING, "FileNode", account, create, json!({})).await?;
228 created_id(&result, "d")
229 }
230
231 pub async fn rename_in(&mut self, client: &Client, account: &str, node: &FileNode, name: &str) -> Result<()> {
233 let vault = node.vault.as_deref().ok_or_else(|| Error::E2e("not in a vault".into()))?;
234 let key = self.need_vault_key(client, account, vault).await?;
235 let update = json!({ "update": { node.id.as_str(): {
236 "name": opaque_name(), "kernoid:encryptedName": seal(&key.public(), name)?,
237 } } });
238 client.set(&USING, "FileNode", account, update, json!({})).await?;
239 Ok(())
240 }
241
242 #[allow(clippy::too_many_arguments)]
245 pub async fn upload_into(
246 &mut self,
247 client: &Client,
248 account: &str,
249 vault: &str,
250 parent: &str,
251 path: &Path,
252 existing: Option<&FileNode>,
253 progress: impl FnMut(u64, u64) + Send + 'static,
254 ) -> Result<()> {
255 let key = self.need_vault_key(client, account, vault).await?;
256 let signer = self.need_unlocked()?.clone();
257 let name = path
258 .file_name()
259 .and_then(|name| name.to_str())
260 .ok_or_else(|| Error::Protocol("the file name is not UTF-8".into()))?
261 .to_owned();
262 let data = tokio::fs::read(path).await?;
263 let modified =
264 tokio::fs::metadata(path).await?.modified().ok().map(|time| {
265 chrono::DateTime::<chrono::Utc>::from(time).to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
266 });
267 let public = key.public();
268 let sealed = tokio::task::spawn_blocking(move || message::encrypt(&data, &[&public], Some(&signer), false))
269 .await
270 .map_err(|error| Error::E2e(error.to_string()))??;
271 let size = sealed.len() as u64;
272 let upload =
273 client.upload(account, "application/octet-stream", std::io::Cursor::new(sealed), size, progress).await?;
274 let kind = media_type(&name);
275 match existing.filter(|node| !node.is_directory()) {
276 Some(node) => {
277 let update = json!({ "update": { node.id.as_str(): {
278 "blobId": upload.blob_id, "mediaType": kind, "modified": modified,
279 } } });
280 client.set(&USING, "FileNode", account, update, json!({})).await?;
281 }
282 None => {
283 let create = json!({ "create": { "f": {
284 "@type": "File", "parentId": parent, "name": opaque_name(),
285 "kernoid:encryptedName": seal(&key.public(), &name)?,
286 "blobId": upload.blob_id, "mediaType": kind, "modified": modified,
287 } } });
288 client.set(&USING, "FileNode", account, create, json!({})).await?;
289 }
290 }
291 Ok(())
292 }
293
294 pub async fn download_from(&mut self, client: &Client, account: &str, node: &FileNode) -> Result<Vec<u8>> {
296 let vault = node.vault.as_deref().ok_or_else(|| Error::E2e("not in a vault".into()))?;
297 let blob = node.blob_id.as_deref().ok_or_else(|| Error::E2e("not a file".into()))?;
298 self.need_vault_key(client, account, vault).await?;
299 let mut sealed = Vec::new();
300 client.download(account, blob, "encrypted", "application/octet-stream", &mut sealed, &mut |_, _| {}).await?;
301 self.open_content(client, account, vault, sealed).await
302 }
303
304 pub async fn save_from(&mut self, client: &Client, account: &str, node: &FileNode, path: &Path) -> Result<()> {
306 let data = self.download_from(client, account, node).await?;
307 let mut file = tokio::fs::File::create(path).await?;
308 file.write_all(&data).await?;
309 file.flush().await?;
310 Ok(())
311 }
312
313 pub async fn give_vault_keys(
316 &mut self,
317 client: &Client,
318 account: &str,
319 vault: &str,
320 people: &[Principal],
321 ) -> Result<Vec<Principal>> {
322 let key = self.need_vault_key(client, account, vault).await?;
323 let members = Self::wrapped(client, account, vault).await?.members;
324 let secret = key.export()?;
325 let mut waiting = Vec::new();
326 let mut update = serde_json::Map::new();
327 for person in people.iter().filter(|person| !members.contains(&person.id)) {
328 let keys: Vec<PublicKey> = match (person.kind.as_str(), &person.email) {
329 ("individual", Some(email)) => {
330 self.lookup(client, std::slice::from_ref(email)).await?.into_values().flatten().collect()
331 }
332 _ => Vec::new(),
333 };
334 if keys.is_empty() {
335 waiting.push(person.clone());
336 continue;
337 }
338 let recipients: Vec<&PublicKey> = keys.iter().collect();
339 let wrapped = message::encrypt(secret.as_bytes(), &recipients, None, false)?;
340 update.insert(format!("members/{}", person.id), Value::String(STANDARD.encode(wrapped)));
341 }
342 if !update.is_empty() {
343 let change = json!({ "update": { vault: update } });
344 client.set(&USING, "VaultKey", account, change, json!({})).await?;
345 }
346 Ok(waiting)
347 }
348}
349
350fn seal(vault: &PublicKey, name: &str) -> Result<String> {
352 Ok(STANDARD.encode(message::encrypt(name.as_bytes(), &[vault], None, false)?))
353}
354
355fn created_id(result: &Value, key: &str) -> Result<String> {
357 result
358 .pointer(&format!("/created/{key}/id"))
359 .and_then(Value::as_str)
360 .map(str::to_owned)
361 .ok_or_else(|| Error::Protocol("the server made nothing".into()))
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn names_in_a_vault_are_random_ids() {
370 let (a, b) = (opaque_name(), opaque_name());
371 assert_ne!(a, b);
372 assert_eq!(a.len(), 2 + 36);
373 assert!(a.starts_with("k-") && a.as_bytes()[2 + 14] == b'4');
374 }
375}