1#![allow(clippy::await_holding_lock)] #![allow(deprecated)]
3use super::{
7 KeyPair, SeaError, SessionState, SessionStorage, User, WorkOptions, certify, decrypt,
8 decrypt_symmetric, encrypt, encrypt_symmetric, generate_pair, is_pubkey_certified, secret,
9 sign_value, verify_certificate, work,
10};
11use crate::{Node, Value as BeamValue};
12use aes_gcm::{
13 Aes256Gcm, Nonce,
14 aead::{Aead, KeyInit},
15};
16use rand::RngCore;
17use serde_json::{Value as JsonValue, json};
18
19impl User {
20 pub async fn create(alias: &str, pass: &str, db: &mut Node) -> Result<Self, SeaError> {
23 let pair = generate_pair().await?;
24
25 let proof_input = format!("{}{}", alias, pass);
27 let mut salt_bytes = [0u8; 9];
29 rand::rng().fill_bytes(&mut salt_bytes);
30
31 let proof = work(
32 proof_input.as_bytes(),
33 Some(&salt_bytes),
34 WorkOptions::default(),
35 )
36 .await?;
37 let auth_data = json!({
39 "pub": pair.pub_key,
40 "priv": pair.priv_key,
41 "epub": pair.epub_key,
42 "epriv": pair.epriv_key,
43 });
44
45 let encrypted_auth = encrypt_pass(&auth_data, &proof).await?;
47
48 let alias_payload = json!({
50 "pub": pair.pub_key,
51 "epub": pair.epub_key,
52 "auth": encrypted_auth,
53 "salt": BASE64_URL_SAFE_NO_PAD.encode(salt_bytes),
54 });
55 let mut alias_node = db.get("~@").get(alias);
56 let _ = alias_node
57 .put(BeamValue::Text(alias_payload.to_string()))
58 .await;
59
60 let state = SessionState {
61 pair,
62 alias: Some(alias.to_string()),
63 is_authenticated: true,
64 };
65 Ok(User::from_state(state))
66 }
67
68 pub async fn auth(alias: &str, pass: &str, db: &mut Node) -> Result<Self, SeaError> {
70 let mut alias_node = db.get("~@").get(alias);
71 let value = alias_node.once(None).await.ok_or(SeaError::AuthFailed)?;
72
73 let text = match value {
74 BeamValue::Text(t) => t,
75 _ => return Err(SeaError::AuthFailed),
76 };
77
78 let alias_data: JsonValue =
79 serde_json::from_str(&text).map_err(|_| SeaError::AuthFailed)?;
80
81 let encrypted_auth = alias_data.get("auth").ok_or(SeaError::AuthFailed)?;
82
83 let salt_decoded = if let Some(salt_b64) = alias_data.get("salt").and_then(|v| v.as_str()) {
84 BASE64_URL_SAFE_NO_PAD
85 .decode(salt_b64)
86 .unwrap_or_else(|_| alias.as_bytes().to_vec())
87 } else {
88 alias.as_bytes().to_vec()
89 };
90
91 let proof_input = format!("{}{}", alias, pass);
92 let proof = work(
93 proof_input.as_bytes(),
94 Some(&salt_decoded),
95 WorkOptions::default(),
96 )
97 .await?;
98 let decrypted = decrypt_pass(encrypted_auth, &proof).await?;
99
100 let pair = KeyPair {
101 pub_key: decrypted
102 .get("pub")
103 .and_then(|v| v.as_str())
104 .unwrap_or_default()
105 .to_string(),
106 priv_key: decrypted
107 .get("priv")
108 .and_then(|v| v.as_str())
109 .unwrap_or_default()
110 .to_string(),
111 epub_key: decrypted
112 .get("epub")
113 .and_then(|v| v.as_str())
114 .map(|s| s.to_string()),
115 epriv_key: decrypted
116 .get("epriv")
117 .and_then(|v| v.as_str())
118 .map(|s| s.to_string()),
119 };
120
121 let state = SessionState {
122 pair,
123 alias: Some(alias.to_string()),
124 is_authenticated: true,
125 };
126 Ok(User::from_state(state))
127 }
128
129 pub fn from_pair(pair: KeyPair, alias: Option<&str>) -> Self {
131 let state = SessionState {
132 pair,
133 alias: alias.map(|s| s.to_string()),
134 is_authenticated: true,
135 };
136 User::from_state(state)
137 }
138
139 pub async fn recall(alias: &str, storage: &dyn SessionStorage) -> Result<Self, SeaError> {
141 let pair = storage
142 .load(alias)
143 .await
144 .map_err(|e| SeaError::SessionStorage(format!("{}", e)))?;
145
146 if let Some(pair) = pair {
147 let state = SessionState {
148 pair,
149 alias: Some(alias.to_string()),
150 is_authenticated: true,
151 };
152 Ok(User::from_state(state))
153 } else {
154 Err(SeaError::AuthFailed)
155 }
156 }
157
158 pub async fn save_to(&self, storage: &dyn SessionStorage) -> Result<(), SeaError> {
167 let (alias, pair) = {
168 let inner = self
169 .inner
170 .read()
171 .map_err(|_| SeaError::SessionStorage("lock poisoned".to_string()))?;
172 if !inner.is_authenticated {
173 return Err(SeaError::NotAuthenticated);
174 }
175 let alias = inner.alias.clone().ok_or(SeaError::NotAuthenticated)?;
176 let pair = inner.pair.clone();
177 (alias, pair)
178 }; storage.save(&alias, &pair).await
180 }
181}
182
183async fn encrypt_pass(data: &JsonValue, passphrase: &str) -> Result<JsonValue, SeaError> {
186 let data = data.clone();
187 let passphrase = passphrase.to_string();
188
189 tokio::task::spawn_blocking(move || {
190 let msg = serde_json::to_string(&data)
191 .map_err(|e| SeaError::Encryption(format!("serialization: {}", e)))?;
192
193 let mut salt = [0u8; 9];
194 let mut nonce = [0u8; 12];
195 rand::rng().fill_bytes(&mut salt);
196 rand::rng().fill_bytes(&mut nonce);
197
198 let aes_key = derive_key_sync(&passphrase, &salt)?;
199
200 let cipher = Aes256Gcm::new_from_slice(&aes_key)
201 .map_err(|e| SeaError::Encryption(format!("cipher: {}", e)))?;
202
203 let ciphertext = cipher
204 .encrypt(Nonce::from_slice(&nonce), msg.as_bytes())
205 .map_err(|e| SeaError::Encryption(format!("encrypt: {}", e)))?;
206
207 Ok(json!({
208 "ct": BASE64_URL_SAFE_NO_PAD.encode(&ciphertext),
209 "iv": BASE64_URL_SAFE_NO_PAD.encode(nonce),
210 "s": BASE64_URL_SAFE_NO_PAD.encode(salt),
211 }))
212 })
213 .await
214 .map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?
215}
216
217async fn decrypt_pass(encrypted: &JsonValue, passphrase: &str) -> Result<JsonValue, SeaError> {
218 let encrypted = encrypted.clone();
219 let passphrase = passphrase.to_string();
220
221 tokio::task::spawn_blocking(move || {
222 let ct = encrypted
223 .get("ct")
224 .and_then(|v| v.as_str())
225 .ok_or_else(|| SeaError::Decryption("missing ct".to_string()))?;
226 let iv = encrypted
227 .get("iv")
228 .and_then(|v| v.as_str())
229 .ok_or_else(|| SeaError::Decryption("missing iv".to_string()))?;
230 let s = encrypted
231 .get("s")
232 .and_then(|v| v.as_str())
233 .ok_or_else(|| SeaError::Decryption("missing s".to_string()))?;
234
235 let ciphertext = BASE64_URL_SAFE_NO_PAD
236 .decode(ct)
237 .map_err(|_| SeaError::Decryption("bad ct".to_string()))?;
238 let nonce = BASE64_URL_SAFE_NO_PAD
239 .decode(iv)
240 .map_err(|_| SeaError::Decryption("bad iv".to_string()))?;
241 let salt = BASE64_URL_SAFE_NO_PAD
242 .decode(s)
243 .map_err(|_| SeaError::Decryption("bad s".to_string()))?;
244
245 let aes_key = derive_key_sync(&passphrase, &salt)?;
246
247 let cipher = Aes256Gcm::new_from_slice(&aes_key)
248 .map_err(|e| SeaError::Decryption(format!("cipher: {}", e)))?;
249
250 let plaintext = cipher
251 .decrypt(Nonce::from_slice(&nonce), ciphertext.as_ref())
252 .map_err(|_| SeaError::Decryption("bad passphrase or tampered".to_string()))?;
253
254 let text = String::from_utf8(plaintext)
255 .map_err(|_| SeaError::Decryption("bad utf8".to_string()))?;
256
257 serde_json::from_str(&text).map_err(|e| SeaError::Decryption(format!("bad json: {}", e)))
258 })
259 .await
260 .map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?
261}
262
263use super::encrypt::derive_aes_key_sync as derive_key_sync;
266use base64::prelude::*;
267
268pub struct UserBuilder<'a> {
270 node: &'a mut Node,
271}
272
273impl Node {
274 pub fn user(&mut self) -> UserBuilder<'_> {
276 UserBuilder { node: self }
277 }
278}
279
280impl<'a> UserBuilder<'a> {
281 pub async fn create(self, alias: &str, pass: &str) -> Result<User, SeaError> {
283 User::create(alias, pass, self.node).await
284 }
285
286 pub async fn auth(self, alias: &str, pass: &str) -> Result<User, SeaError> {
288 User::auth(alias, pass, self.node).await
289 }
290}
291
292fn encode_path(path: &str) -> String {
297 path.replace('/', "__")
298}
299
300impl User {
301 pub async fn trust(
304 &self,
305 recipient_pubkey: &str,
306 path: Option<&str>,
307 db: &mut Node,
308 ) -> Result<(), SeaError> {
309 let inner = self
310 .inner
311 .read()
312 .map_err(|_| SeaError::SessionStorage("lock poisoned".to_string()))?;
313 if !inner.is_authenticated {
314 return Err(SeaError::NotAuthenticated);
315 }
316
317 let certificants = vec![recipient_pubkey.to_string()];
318 let policies = path.map(|p| json!({"w": p}));
319 let signed = certify(&certificants, policies.as_ref(), &inner.pair).await?;
320
321 let path_key = path
322 .map(encode_path)
323 .unwrap_or_else(|| "global".to_string());
324 let mut trust_node = db
325 .get(&format!("~{}", inner.pair.pub_key))
326 .get("trust")
327 .get(&path_key);
328 let _ = trust_node.put(BeamValue::Text(signed.to_string())).await;
329
330 Ok(())
331 }
332
333 pub async fn grant(
336 &self,
337 recipient_pubkey: &str,
338 recipient_epub: &str,
339 data_path: &str,
340 db: &mut Node,
341 ) -> Result<(), SeaError> {
342 let inner = self
343 .inner
344 .read()
345 .map_err(|_| SeaError::SessionStorage("lock poisoned".to_string()))?;
346 if !inner.is_authenticated {
347 return Err(SeaError::NotAuthenticated);
348 }
349 let pair = &inner.pair;
350 let path_key = encode_path(data_path);
351
352 let sec = {
354 let mut secret_node = db
355 .get(&format!("~{}", pair.pub_key))
356 .get("secrets")
357 .get(&path_key);
358 match secret_node.once(None).await {
359 Some(BeamValue::Text(enc_text)) => {
360 let outer: JsonValue = serde_json::from_str(&enc_text)
361 .map_err(|e| SeaError::Decryption(format!("bad secret json: {}", e)))?;
362 let enc = if outer.get("m").is_some() && outer.get("s").is_some() {
363 let msg = outer["m"]
364 .as_str()
365 .ok_or_else(|| SeaError::Decryption("m not string".into()))?;
366 serde_json::from_str(msg)
367 .map_err(|e| SeaError::Decryption(format!("bad m: {}", e)))?
368 } else {
369 outer
370 };
371 decrypt(&enc, pair, None)
372 .await?
373 .as_str()
374 .ok_or_else(|| SeaError::Decryption("secret not string".into()))?
375 .to_string()
376 }
377 _ => {
378 let mut bytes = [0u8; 16];
379 rand::rng().fill_bytes(&mut bytes);
380 let new_sec = BASE64_URL_SAFE_NO_PAD.encode(bytes);
381 let enc = encrypt(&json!(new_sec), pair, None).await?;
382 let signed = sign_value(&enc, pair).await?;
383 let _ = secret_node.put(signed).await;
384 new_sec
385 }
386 }
387 };
388
389 let dh = secret(recipient_epub, pair).await?;
391 let dh_bytes = BASE64_URL_SAFE_NO_PAD
392 .decode(&dh)
393 .map_err(|_| SeaError::Crypto("bad dh".into()))?;
394
395 let enc_for_recipient = encrypt_symmetric(&json!(sec), &dh_bytes).await?;
397 let signed_recipient = sign_value(&enc_for_recipient, pair).await?;
398
399 let mut grant_node = db
401 .get(&format!("~{}", pair.pub_key))
402 .get("grant")
403 .get(&path_key)
404 .get(recipient_pubkey);
405 let _ = grant_node.put(signed_recipient).await;
406
407 let enc_for_owner = encrypt(&json!(sec), pair, None).await?;
409 let signed_owner = sign_value(&enc_for_owner, pair).await?;
410 let mut owner_grant_node = db
411 .get(&format!("~{}", pair.pub_key))
412 .get("grant")
413 .get(&path_key)
414 .get(&pair.pub_key);
415 let _ = owner_grant_node.put(signed_owner).await;
416
417 Ok(())
418 }
419
420 pub async fn secret(
424 &self,
425 data: &JsonValue,
426 path: &str,
427 db: &mut Node,
428 ) -> Result<(), SeaError> {
429 let inner = self
430 .inner
431 .read()
432 .map_err(|_| SeaError::SessionStorage("lock poisoned".to_string()))?;
433 if !inner.is_authenticated {
434 return Err(SeaError::NotAuthenticated);
435 }
436 let pair = &inner.pair;
437 let path_key = encode_path(path);
438 let user_root = format!("~{}", pair.pub_key);
439
440 let epub = pair
442 .epub_key
443 .as_ref()
444 .ok_or_else(|| SeaError::Crypto("no epub".into()))?;
445 let dh = secret(epub, pair).await?;
446 let dh_bytes = BASE64_URL_SAFE_NO_PAD
447 .decode(&dh)
448 .map_err(|_| SeaError::Crypto("bad dh".into()))?;
449
450 let enc = encrypt_symmetric(data, &dh_bytes).await?;
452 let signed = sign_value(&enc, pair).await?;
453
454 let mut secret_node = db.get(&user_root).get("secret").get(&path_key);
455 let _ = secret_node.put(signed).await;
456
457 Ok(())
458 }
459}
460
461pub async fn verify_trust(
464 authority_pubkey: &str,
465 writer_pubkey: &str,
466 path: Option<&str>,
467 db: &mut Node,
468) -> Result<bool, SeaError> {
469 let path_key = path
470 .map(encode_path)
471 .unwrap_or_else(|| "global".to_string());
472 let mut trust_node = db
473 .get(&format!("~{}", authority_pubkey))
474 .get("trust")
475 .get(&path_key);
476
477 let cert_text = match trust_node.once(None).await {
478 Some(BeamValue::Text(t)) => t,
479 _ => return Ok(false),
480 };
481
482 let cert: JsonValue =
483 serde_json::from_str(&cert_text).map_err(|_| SeaError::VerificationFailed)?;
484
485 let payload =
486 verify_certificate(&cert, authority_pubkey).map_err(|_| SeaError::VerificationFailed)?;
487
488 Ok(is_pubkey_certified(&payload, writer_pubkey))
489}
490
491pub async fn accept_grant(
493 data_path: &str,
494 owner_pubkey: &str,
495 owner_epub: &str,
496 pair: &KeyPair,
497 db: &mut Node,
498) -> Result<String, SeaError> {
499 let path_key = encode_path(data_path);
500 let mut grant_node = db
501 .get(&format!("~{}", owner_pubkey))
502 .get("grant")
503 .get(&path_key)
504 .get(&pair.pub_key);
505
506 let enc_text = grant_node
507 .once(None)
508 .await
509 .and_then(|v| match v {
510 BeamValue::Text(t) => Some(t),
511 _ => None,
512 })
513 .ok_or_else(|| SeaError::Decryption("no grant found".into()))?;
514
515 let outer: JsonValue = serde_json::from_str(&enc_text)
516 .map_err(|e| SeaError::Decryption(format!("bad grant json: {}", e)))?;
517 let enc_json = if outer.get("m").is_some() && outer.get("s").is_some() {
518 let msg = outer["m"]
519 .as_str()
520 .ok_or_else(|| SeaError::Decryption("m not string".into()))?;
521 serde_json::from_str(msg).map_err(|e| SeaError::Decryption(format!("bad m: {}", e)))?
522 } else {
523 outer
524 };
525
526 let dh = secret(owner_epub, pair).await?;
527 let dh_bytes = BASE64_URL_SAFE_NO_PAD
528 .decode(&dh)
529 .map_err(|_| SeaError::Decryption("bad dh".into()))?;
530
531 let sec_json = decrypt_symmetric(&enc_json, &dh_bytes).await?;
532 let sec = sec_json
533 .as_str()
534 .ok_or_else(|| SeaError::Decryption("secret not string".into()))?;
535 Ok(sec.to_string())
536}