ouisync/repository/
metadata.rs

1use crate::{
2    access_control::{Access, AccessSecrets, LocalSecret, SetLocalSecret, WriteSecrets},
3    crypto::{
4        Hash, Password, PasswordSalt,
5        cipher::{self, Nonce},
6        sign,
7    },
8    db::{self, DatabaseId},
9    device_id::DeviceId,
10    protocol::RepositoryId,
11    store::Error as StoreError,
12};
13use rand::{Rng, rngs::OsRng};
14use sqlx::Row;
15use std::{borrow::Cow, fmt, time::Duration};
16use tracing::instrument;
17use zeroize::Zeroize;
18
19// Metadata keys
20const REPOSITORY_ID: &[u8] = b"repository_id";
21// Note that we don't do anything with these salts other than storing them for the user so they can
22// use them for SecretKey derivation. Storing them here (as opposed to having the user store it
23// outside of this repository database) ensures that when the database is moved to another device,
24// the same password can still unlock it.
25const READ_PASSWORD_SALT: &[u8] = b"read_password_salt";
26const WRITE_PASSWORD_SALT: &[u8] = b"write_password_salt";
27const WRITER_ID: &[u8] = b"writer_id";
28const READ_KEY: &[u8] = b"read_key";
29const WRITE_KEY: &[u8] = b"write_key";
30const DATABASE_ID: &[u8] = b"database_id";
31
32const DEVICE_ID: &[u8] = b"device_id";
33const READ_KEY_VALIDATOR: &[u8] = b"read_key_validator";
34
35const QUOTA: &[u8] = b"quota";
36const BLOCK_EXPIRATION: &[u8] = b"block_expiration";
37
38// Support for data migrations.
39const DATA_VERSION: &[u8] = b"data_version";
40
41// We used to have only the ACCESS_KEY which would be either the read key or the write key or a
42// dummy (random) array of bytes with the length of the writer key. But that wasn't satisfactory
43// because:
44//
45// 1. The length of the key would leak some information.
46// 2. User can't have a separate password for reading and writing, and thus can't plausibly claim
47//    they're only a reader if they also have the write access.
48// 3. If the ACCESS_KEY was a valid write key, but the repository was in the blind mode, accepting
49//    the read token would disable the write access.
50const DEPRECATED_ACCESS_KEY: &[u8] = b"access_key"; // read key or write key
51
52// We used to have a single salt that was used when the user used a password to open a repository.
53// We no longer deal with passwords in ouisync_lib but leave the password hashing to the library
54// user (we provide them with functions to do it) and instead of a single salt we can store two for
55// them: one for the read password and one for the write password.
56const DEPRECATED_PASSWORD_SALT: &[u8] = b"password_salt";
57
58// -------------------------------------------------------------------
59// Accessor for user-defined metadata
60// -------------------------------------------------------------------
61pub struct Metadata {
62    db: db::Pool,
63}
64
65impl Metadata {
66    pub(crate) fn new(db: db::Pool) -> Self {
67        Self { db }
68    }
69
70    #[instrument(skip(self), fields(value))]
71    pub async fn get<T>(&self, name: &str) -> Result<Option<T>, StoreError>
72    where
73        T: MetadataGet + fmt::Debug,
74    {
75        let mut conn = self.db.acquire().await?;
76        get_public(&mut conn, name.as_bytes()).await
77    }
78
79    pub async fn set<'a, T>(&self, name: &'a str, value: T) -> Result<(), StoreError>
80    where
81        T: MetadataSet<'a> + fmt::Debug,
82    {
83        let mut tx = self.write().await?;
84        tx.set(name, value).await?;
85        tx.commit().await?;
86
87        Ok(())
88    }
89
90    pub async fn remove(&self, name: &str) -> Result<(), StoreError> {
91        let mut tx = self.write().await?;
92        tx.remove(name).await?;
93        tx.commit().await?;
94
95        Ok(())
96    }
97
98    pub async fn write(&self) -> Result<MetadataWriter, StoreError> {
99        Ok(MetadataWriter {
100            tx: self.db.begin_write().await?,
101        })
102    }
103}
104
105pub struct MetadataWriter {
106    tx: db::WriteTransaction,
107}
108
109impl MetadataWriter {
110    pub async fn get<T>(&mut self, name: &str) -> Result<Option<T>, StoreError>
111    where
112        T: MetadataGet + fmt::Debug,
113    {
114        get_public(&mut self.tx, name.as_bytes()).await
115    }
116
117    pub async fn set<'a, T>(&mut self, name: &'a str, value: T) -> Result<(), StoreError>
118    where
119        T: MetadataSet<'a> + fmt::Debug,
120    {
121        set_public(&mut self.tx, name.as_bytes(), value).await
122    }
123
124    pub async fn remove(&mut self, name: &str) -> Result<(), StoreError> {
125        remove_public(&mut self.tx, name.as_bytes()).await
126    }
127
128    pub async fn commit(self) -> Result<(), StoreError> {
129        self.tx.commit().await?;
130        Ok(())
131    }
132}
133
134// -------------------------------------------------------------------
135// Password
136// -------------------------------------------------------------------
137pub(crate) enum KeyType {
138    Read,
139    Write,
140}
141
142pub(crate) async fn password_to_key(
143    tx: &mut db::WriteTransaction,
144    key_type: KeyType,
145    password: &Password,
146) -> Result<cipher::SecretKey, StoreError> {
147    let salt = get_password_salt(tx, key_type).await?;
148
149    Ok(cipher::SecretKey::derive_from_password(
150        password.as_ref(),
151        &salt,
152    ))
153}
154
155async fn secret_to_key<'a>(
156    tx: &mut db::WriteTransaction,
157    key_type: KeyType,
158    secret: &'a LocalSecret,
159) -> Result<Cow<'a, cipher::SecretKey>, StoreError> {
160    match secret {
161        LocalSecret::Password(password) => password_to_key(tx, key_type, password)
162            .await
163            .map(Cow::Owned),
164        LocalSecret::SecretKey(key) => Ok(Cow::Borrowed(key)),
165    }
166}
167
168pub(crate) fn secret_to_key_and_salt(
169    secret: &'_ SetLocalSecret,
170) -> (Cow<'_, cipher::SecretKey>, Cow<'_, PasswordSalt>) {
171    match secret {
172        SetLocalSecret::Password(password) => {
173            let salt = PasswordSalt::random();
174            let key = cipher::SecretKey::derive_from_password(password.as_ref(), &salt);
175            (Cow::Owned(key), Cow::Owned(salt))
176        }
177        SetLocalSecret::KeyAndSalt { key, salt } => (Cow::Borrowed(key), Cow::Borrowed(salt)),
178    }
179}
180
181// -------------------------------------------------------------------
182// Database ID
183// -------------------------------------------------------------------
184pub(crate) async fn get_or_generate_database_id(db: &db::Pool) -> Result<DatabaseId, StoreError> {
185    let mut tx = db.begin_write().await?;
186    let database_id = match get_public_blob(&mut tx, DATABASE_ID).await {
187        Ok(Some(database_id)) => database_id,
188        Ok(None) => {
189            let database_id: DatabaseId = OsRng.r#gen();
190            set_public_blob(&mut tx, DATABASE_ID, &database_id).await?;
191            tx.commit().await?;
192            database_id
193        }
194        Err(error) => return Err(error),
195    };
196
197    Ok(database_id)
198}
199
200// -------------------------------------------------------------------
201// Writer Id
202// -------------------------------------------------------------------
203pub(crate) async fn get_writer_id(
204    conn: &mut db::Connection,
205    local_key: Option<&cipher::SecretKey>,
206) -> Result<Option<sign::PublicKey>, StoreError> {
207    get_blob(conn, WRITER_ID, local_key).await
208}
209
210pub(crate) async fn set_writer_id(
211    tx: &mut db::WriteTransaction,
212    writer_id: &sign::PublicKey,
213    local_key: Option<&cipher::SecretKey>,
214) -> Result<(), StoreError> {
215    set_blob(tx, WRITER_ID, writer_id, local_key).await?;
216    Ok(())
217}
218
219// TODO: Writer IDs are currently practically just UUIDs with no real security (any replica with a
220// write access may impersonate any other replica).
221pub(crate) fn generate_writer_id() -> sign::PublicKey {
222    sign::Keypair::random().public_key()
223}
224
225pub(crate) async fn get_or_generate_writer_id(
226    tx: &mut db::WriteTransaction,
227    local_key: Option<&cipher::SecretKey>,
228) -> Result<sign::PublicKey, StoreError> {
229    let writer_id = if let Some(writer_id) = get_writer_id(tx, local_key).await? {
230        writer_id
231    } else {
232        let writer_id = generate_writer_id();
233        set_writer_id(tx, &writer_id, local_key).await?;
234        writer_id
235    };
236
237    Ok(writer_id)
238}
239
240// -------------------------------------------------------------------
241// Device id
242// -------------------------------------------------------------------
243
244// Checks whether the stored device id is the same as the specified one.
245pub(crate) async fn check_device_id(
246    conn: &mut db::Connection,
247    device_id: &DeviceId,
248) -> Result<bool, StoreError> {
249    let old_device_id: Option<DeviceId> = get_public_blob(conn, DEVICE_ID).await?;
250    Ok(old_device_id.as_ref() == Some(device_id))
251}
252
253pub(crate) async fn set_device_id(
254    tx: &mut db::WriteTransaction,
255    device_id: &DeviceId,
256) -> Result<(), StoreError> {
257    set_public_blob(tx, DEVICE_ID, device_id).await?;
258    Ok(())
259}
260
261// -------------------------------------------------------------------
262// Access secrets
263// -------------------------------------------------------------------
264pub(super) async fn get_repository_id(
265    conn: &mut db::Connection,
266) -> Result<RepositoryId, StoreError> {
267    // Repository id should always exist. If not indicates a corrupted db.
268    get_public_blob(conn, REPOSITORY_ID)
269        .await?
270        .ok_or(StoreError::MalformedData)
271}
272
273async fn set_public_read_key(
274    tx: &mut db::WriteTransaction,
275    read_key: &cipher::SecretKey,
276) -> Result<(), StoreError> {
277    set_public_blob(tx, READ_KEY, read_key).await
278}
279
280async fn set_secret_read_key(
281    tx: &mut db::WriteTransaction,
282    id: &RepositoryId,
283    read_key: &cipher::SecretKey,
284    local_secret_key: &cipher::SecretKey,
285    local_salt: &PasswordSalt,
286) -> Result<(), StoreError> {
287    set_secret_blob(tx, READ_KEY, read_key, local_secret_key).await?;
288    set_secret_blob(tx, READ_KEY_VALIDATOR, read_key_validator(id), read_key).await?;
289    set_password_salt(tx, KeyType::Read, local_salt).await
290}
291
292pub(crate) async fn set_read_key(
293    tx: &mut db::WriteTransaction,
294    id: &RepositoryId,
295    read_key: &cipher::SecretKey,
296    local: Option<(&cipher::SecretKey, &PasswordSalt)>,
297) -> Result<(), StoreError> {
298    if let Some((local_secret_key, local_salt)) = local {
299        remove_public_read_key(tx).await?;
300        set_secret_read_key(tx, id, read_key, local_secret_key, local_salt).await
301    } else {
302        set_public_read_key(tx, read_key).await?;
303        obfuscate_secret_read_key(tx).await
304    }
305}
306
307async fn remove_public_read_key(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
308    remove_public(tx, READ_KEY).await
309}
310
311async fn obfuscate_secret_read_key(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
312    let dummy_id = RepositoryId::from(sign::Keypair::random().public_key());
313    let dummy_local_key = cipher::SecretKey::random();
314    let dummy_read_key = cipher::SecretKey::random();
315
316    set_secret_blob(tx, READ_KEY, &dummy_read_key, &dummy_local_key).await?;
317    set_secret_blob(
318        tx,
319        READ_KEY_VALIDATOR,
320        read_key_validator(&dummy_id),
321        &dummy_read_key,
322    )
323    .await?;
324
325    obfuscate_read_password_salt(tx).await?;
326
327    Ok(())
328}
329
330pub(crate) async fn remove_read_key(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
331    remove_public_read_key(tx).await?;
332    obfuscate_secret_read_key(tx).await
333}
334
335// ------------------------------
336
337async fn set_public_write_key(
338    tx: &mut db::WriteTransaction,
339    secrets: &WriteSecrets,
340) -> Result<(), StoreError> {
341    set_public_blob(tx, WRITE_KEY, secrets.write_keys.to_bytes()).await
342}
343
344async fn set_secret_write_key(
345    tx: &mut db::WriteTransaction,
346    secrets: &WriteSecrets,
347    local_secret_key: &cipher::SecretKey,
348    local_salt: &PasswordSalt,
349) -> Result<(), StoreError> {
350    set_secret_blob(
351        tx,
352        WRITE_KEY,
353        secrets.write_keys.to_bytes(),
354        local_secret_key,
355    )
356    .await?;
357    set_password_salt(tx, KeyType::Write, local_salt).await
358}
359
360pub(crate) async fn set_write_key(
361    tx: &mut db::WriteTransaction,
362    secrets: &WriteSecrets,
363    local: Option<(&cipher::SecretKey, &PasswordSalt)>,
364) -> Result<(), StoreError> {
365    if let Some((local_secret_key, local_salt)) = local {
366        remove_public_write_key(tx).await?;
367        set_secret_write_key(tx, secrets, local_secret_key, local_salt).await
368    } else {
369        set_public_write_key(tx, secrets).await?;
370        obfuscate_secret_write_key(tx).await
371    }
372}
373
374async fn remove_public_write_key(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
375    remove_public(tx, WRITE_KEY).await
376}
377
378async fn obfuscate_secret_write_key(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
379    let dummy_local_key = cipher::SecretKey::random();
380    let dummy_write_key = sign::Keypair::random().to_bytes();
381    set_secret_blob(tx, WRITE_KEY, &dummy_write_key, &dummy_local_key).await?;
382    obfuscate_write_password_salt(tx).await
383}
384
385pub(crate) async fn remove_write_key(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
386    remove_public_write_key(tx).await?;
387    obfuscate_secret_write_key(tx).await
388}
389
390// ------------------------------
391
392pub(crate) async fn get_password_salt(
393    tx: &mut db::WriteTransaction,
394    key_type: KeyType,
395) -> Result<PasswordSalt, StoreError> {
396    migrate_to_separate_password_salts(tx).await?;
397
398    match key_type {
399        KeyType::Read => get_public_blob(tx, READ_PASSWORD_SALT).await?,
400        KeyType::Write => get_public_blob(tx, WRITE_PASSWORD_SALT).await?,
401    }
402    // Salts should always be present
403    .ok_or(StoreError::MalformedData)
404}
405
406async fn set_password_salt(
407    tx: &mut db::WriteTransaction,
408    key_type: KeyType,
409    salt: &PasswordSalt,
410) -> Result<(), StoreError> {
411    migrate_to_separate_password_salts(tx).await?;
412    match key_type {
413        KeyType::Read => set_public_blob(tx, READ_PASSWORD_SALT, salt).await,
414        KeyType::Write => set_public_blob(tx, WRITE_PASSWORD_SALT, salt).await,
415    }
416}
417
418async fn obfuscate_read_password_salt(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
419    migrate_to_separate_password_salts(tx).await?;
420    let dummy_salt = PasswordSalt::random();
421    set_public_blob(tx, READ_PASSWORD_SALT, &dummy_salt).await
422}
423
424async fn obfuscate_write_password_salt(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
425    migrate_to_separate_password_salts(tx).await?;
426    let dummy_salt = PasswordSalt::random();
427    set_public_blob(tx, WRITE_PASSWORD_SALT, &dummy_salt).await
428}
429
430async fn migrate_to_separate_password_salts(
431    tx: &mut db::WriteTransaction,
432) -> Result<(), StoreError> {
433    let single_salt: PasswordSalt = match get_public_blob(tx, DEPRECATED_PASSWORD_SALT).await {
434        Ok(Some(salt)) => salt,
435        Ok(None) => {
436            // Single salt has already been migrated.
437            return Ok(());
438        }
439        Err(error) => return Err(error),
440    };
441
442    set_public_blob(tx, READ_PASSWORD_SALT, &single_salt).await?;
443    set_public_blob(tx, WRITE_PASSWORD_SALT, &single_salt).await?;
444    remove_public(tx, DEPRECATED_PASSWORD_SALT).await
445}
446
447// ------------------------------
448
449pub(crate) async fn requires_local_secret_for_reading(
450    conn: &mut db::Connection,
451) -> Result<bool, StoreError> {
452    match get_public_blob::<cipher::SecretKey>(conn, READ_KEY).await {
453        Ok(Some(_)) => return Ok(false),
454        Ok(None) => (),
455        Err(err) => return Err(err),
456    }
457
458    match get_public_blob::<sign::Keypair>(conn, WRITE_KEY).await {
459        Ok(Some(_)) => Ok(false),
460        Ok(None) => Ok(true),
461        Err(err) => Err(err),
462    }
463}
464
465pub(crate) async fn requires_local_secret_for_writing(
466    conn: &mut db::Connection,
467) -> Result<bool, StoreError> {
468    match get_public_blob::<sign::Keypair>(conn, WRITE_KEY).await {
469        Ok(Some(_)) => Ok(false),
470        Ok(None) => Ok(true),
471        Err(err) => Err(err),
472    }
473}
474
475pub(crate) async fn initialize_access_secrets<'a>(
476    tx: &mut db::WriteTransaction,
477    access: &'a Access,
478) -> Result<LocalKeys<'a>, StoreError> {
479    set_public_blob(tx, REPOSITORY_ID, access.id()).await?;
480    set_access(tx, access).await
481}
482
483pub(crate) async fn set_access<'a>(
484    tx: &mut db::WriteTransaction,
485    access: &'a Access,
486) -> Result<LocalKeys<'a>, StoreError> {
487    match access {
488        Access::Blind { .. } => {
489            remove_public_read_key(tx).await?;
490            obfuscate_secret_read_key(tx).await?;
491            remove_public_write_key(tx).await?;
492            obfuscate_secret_write_key(tx).await?;
493
494            Ok(LocalKeys {
495                read: None,
496                write: None,
497            })
498        }
499        Access::ReadUnlocked { id: _, read_key } => {
500            set_public_read_key(tx, read_key).await?;
501            obfuscate_secret_read_key(tx).await?;
502            remove_public_write_key(tx).await?;
503            obfuscate_secret_write_key(tx).await?;
504
505            Ok(LocalKeys {
506                read: None,
507                write: None,
508            })
509        }
510        Access::ReadLocked {
511            id,
512            local_secret,
513            read_key,
514        } => {
515            let (local_secret_key, local_salt) = secret_to_key_and_salt(local_secret);
516
517            remove_public_read_key(tx).await?;
518            set_secret_read_key(tx, id, read_key, &local_secret_key, &local_salt).await?;
519            remove_public_write_key(tx).await?;
520            obfuscate_secret_write_key(tx).await?;
521
522            Ok(LocalKeys {
523                read: Some(local_secret_key),
524                write: None,
525            })
526        }
527        Access::WriteUnlocked { secrets } => {
528            set_public_read_key(tx, &secrets.read_key).await?;
529            obfuscate_secret_read_key(tx).await?;
530            set_public_write_key(tx, secrets).await?;
531            obfuscate_secret_write_key(tx).await?;
532
533            Ok(LocalKeys {
534                read: None,
535                write: None,
536            })
537        }
538        Access::WriteLocked {
539            local_read_secret,
540            local_write_secret,
541            secrets,
542        } => {
543            let (local_read_key, local_read_salt) = secret_to_key_and_salt(local_read_secret);
544            let (local_write_key, local_write_salt) = secret_to_key_and_salt(local_write_secret);
545
546            remove_public_read_key(tx).await?;
547            set_secret_read_key(
548                tx,
549                &secrets.id,
550                &secrets.read_key,
551                &local_read_key,
552                &local_read_salt,
553            )
554            .await?;
555            remove_public_write_key(tx).await?;
556            set_secret_write_key(tx, secrets, &local_write_key, &local_write_salt).await?;
557
558            Ok(LocalKeys {
559                read: Some(local_read_key),
560                write: Some(local_write_key),
561            })
562        }
563        Access::WriteLockedReadUnlocked {
564            local_write_secret,
565            secrets,
566        } => {
567            let (local_write_key, local_write_salt) = secret_to_key_and_salt(local_write_secret);
568
569            set_public_read_key(tx, &secrets.read_key).await?;
570            obfuscate_secret_read_key(tx).await?;
571            remove_public_write_key(tx).await?;
572            set_secret_write_key(tx, secrets, &local_write_key, &local_write_salt).await?;
573
574            Ok(LocalKeys {
575                read: None,
576                write: Some(local_write_key),
577            })
578        }
579    }
580}
581
582pub(crate) struct LocalKeys<'a> {
583    #[allow(unused)]
584    pub read: Option<Cow<'a, cipher::SecretKey>>,
585    pub write: Option<Cow<'a, cipher::SecretKey>>,
586}
587
588pub(crate) async fn get_access_secrets<'a>(
589    tx: &mut db::WriteTransaction,
590    local_secret: Option<&'a LocalSecret>,
591) -> Result<(AccessSecrets, Option<Cow<'a, cipher::SecretKey>>), StoreError> {
592    let id = get_repository_id(tx).await?;
593
594    let local_write_key = match &local_secret {
595        Some(local_secret) => Some(secret_to_key(tx, KeyType::Write, local_secret).await?),
596        None => None,
597    };
598
599    match get_write_key(tx, local_write_key.as_deref(), &id).await {
600        Ok(Some(write_keys)) => {
601            let access = AccessSecrets::Write(WriteSecrets::from(write_keys));
602            return Ok((access, local_write_key));
603        }
604        Ok(None) => (),
605        Err(e) => return Err(e),
606    }
607
608    let local_read_key = match &local_secret {
609        Some(local_secret) => Some(secret_to_key(tx, KeyType::Read, local_secret).await?),
610        None => None,
611    };
612
613    // No match. Maybe there's the read key?
614    match get_read_key(tx, local_read_key.as_deref(), &id).await {
615        Ok(Some(read_key)) => {
616            let access = AccessSecrets::Read { id, read_key };
617            return Ok((access, local_write_key));
618        }
619        Ok(None) => (),
620        Err(e) => return Err(e),
621    }
622
623    // No read key either, repository shall be open in blind mode.
624    Ok((AccessSecrets::Blind { id }, None))
625}
626
627/// Returns Ok(None) when the key is there but isn't valid.
628async fn get_write_key(
629    conn: &mut db::Connection,
630    local_key: Option<&cipher::SecretKey>,
631    id: &RepositoryId,
632) -> Result<Option<sign::Keypair>, StoreError> {
633    // Try to interpret it first as the write keys.
634    let write_keys: Option<sign::Keypair> = match get_blob(conn, WRITE_KEY, local_key).await? {
635        Some(write_keys) => Some(write_keys),
636        None => {
637            // Let's be backward compatible.
638            get_blob(conn, DEPRECATED_ACCESS_KEY, local_key).await?
639        }
640    };
641
642    let Some(write_keys) = write_keys else {
643        return Ok(None);
644    };
645
646    let derived_id = RepositoryId::from(write_keys.public_key());
647
648    if &derived_id == id {
649        Ok(Some(write_keys))
650    } else {
651        Ok(None)
652    }
653}
654
655async fn get_read_key(
656    conn: &mut db::Connection,
657    local_key: Option<&cipher::SecretKey>,
658    id: &RepositoryId,
659) -> Result<Option<cipher::SecretKey>, StoreError> {
660    let read_key: cipher::SecretKey = match get_blob(conn, READ_KEY, local_key).await {
661        Ok(Some(read_key)) => read_key,
662        Ok(None) => {
663            // Let's be backward compatible.
664            if let Some(key) = get_blob(conn, DEPRECATED_ACCESS_KEY, local_key).await? {
665                key
666            } else {
667                return Ok(None);
668            }
669        }
670        Err(error) => return Err(error),
671    };
672
673    if local_key.is_none() {
674        return Ok(Some(read_key));
675    }
676
677    let key_validator_expected = read_key_validator(id);
678    let key_validator_actual: Option<Hash> =
679        get_secret_blob(conn, READ_KEY_VALIDATOR, &read_key).await?;
680
681    if key_validator_actual == Some(key_validator_expected) {
682        // Match - we have read access.
683        Ok(Some(read_key))
684    } else {
685        Ok(None)
686    }
687}
688
689// -------------------------------------------------------------------
690// Storage quota
691// -------------------------------------------------------------------
692pub(crate) mod quota {
693    use super::*;
694    use crate::protocol::StorageSize;
695
696    pub(crate) async fn get(conn: &mut db::Connection) -> Result<Option<StorageSize>, StoreError> {
697        Ok(get_public(conn, QUOTA).await?.map(StorageSize::from_bytes))
698    }
699
700    pub(crate) async fn set(tx: &mut db::WriteTransaction, value: u64) -> Result<(), StoreError> {
701        set_public(tx, QUOTA, value).await
702    }
703
704    pub(crate) async fn remove(tx: &mut db::WriteTransaction) -> Result<(), StoreError> {
705        remove_public(tx, QUOTA).await
706    }
707}
708
709// -------------------------------------------------------------------
710// Storage block expiration
711// -------------------------------------------------------------------
712pub(crate) mod block_expiration {
713    use super::*;
714
715    pub(crate) async fn get(conn: &mut db::Connection) -> Result<Option<Duration>, StoreError> {
716        Ok(get_public(conn, BLOCK_EXPIRATION)
717            .await?
718            .map(Duration::from_millis))
719    }
720
721    pub(crate) async fn set(
722        tx: &mut db::WriteTransaction,
723        value: Option<Duration>,
724    ) -> Result<(), StoreError> {
725        if let Some(duration) = value {
726            set_public(
727                tx,
728                BLOCK_EXPIRATION,
729                u64::try_from(duration.as_millis()).unwrap_or(u64::MAX),
730            )
731            .await
732        } else {
733            remove_public(tx, BLOCK_EXPIRATION).await
734        }
735    }
736}
737
738// -------------------------------------------------------------------
739// Data version
740// -------------------------------------------------------------------
741pub(crate) mod data_version {
742    use super::*;
743
744    pub(crate) async fn get(conn: &mut db::Connection) -> Result<u64, StoreError> {
745        Ok(get_public(conn, DATA_VERSION).await?.unwrap_or(0))
746    }
747
748    pub(crate) async fn set(tx: &mut db::WriteTransaction, value: u64) -> Result<(), StoreError> {
749        set_public(tx, DATA_VERSION, value).await
750    }
751}
752
753// -------------------------------------------------------------------
754// Public values
755// -------------------------------------------------------------------
756async fn get_public_blob<T>(conn: &mut db::Connection, id: &[u8]) -> Result<Option<T>, StoreError>
757where
758    T: for<'a> TryFrom<&'a [u8]>,
759{
760    let row = sqlx::query("SELECT value FROM metadata_public WHERE name = ?")
761        .bind(id)
762        .fetch_optional(conn)
763        .await?;
764
765    if let Some(row) = row {
766        let bytes: &[u8] = row.get(0);
767        let bytes = bytes.try_into().map_err(|_| StoreError::MalformedData)?;
768        Ok(Some(bytes))
769    } else {
770        Ok(None)
771    }
772}
773
774async fn set_public_blob<T>(
775    tx: &mut db::WriteTransaction,
776    id: &[u8],
777    blob: T,
778) -> Result<(), StoreError>
779where
780    T: AsRef<[u8]>,
781{
782    sqlx::query("INSERT OR REPLACE INTO metadata_public(name, value) VALUES (?, ?)")
783        .bind(id)
784        .bind(blob.as_ref())
785        .execute(tx)
786        .await?;
787
788    Ok(())
789}
790
791async fn get_public<T>(conn: &mut db::Connection, id: &[u8]) -> Result<Option<T>, StoreError>
792where
793    T: MetadataGet,
794{
795    let row = sqlx::query("SELECT value FROM metadata_public WHERE name = ?")
796        .bind(id)
797        .fetch_optional(conn)
798        .await?;
799    if let Some(row) = row {
800        let value = T::get(&row).map_err(|_| StoreError::MalformedData)?;
801        Ok(Some(value))
802    } else {
803        Ok(None)
804    }
805}
806
807async fn set_public<'a, T>(
808    tx: &mut db::WriteTransaction,
809    id: &'a [u8],
810    value: T,
811) -> Result<(), StoreError>
812where
813    T: MetadataSet<'a>,
814{
815    let query = sqlx::query("INSERT OR REPLACE INTO metadata_public(name, value) VALUES (?, ?)");
816    let query = query.bind(id);
817    let query = value.bind(query);
818    query.execute(tx).await?;
819
820    Ok(())
821}
822
823async fn remove_public(tx: &mut db::WriteTransaction, id: &[u8]) -> Result<(), StoreError> {
824    sqlx::query("DELETE FROM metadata_public WHERE name = ?")
825        .bind(id)
826        .execute(tx)
827        .await?;
828    Ok(())
829}
830
831pub trait MetadataGet: detail::Get {}
832pub trait MetadataSet<'a>: detail::Set<'a> {}
833
834impl<T> MetadataGet for T where T: detail::Get {}
835impl<'a, T> MetadataSet<'a> for T where T: detail::Set<'a> {}
836
837// Use the sealed trait pattern to avoid exposing implementation details outside of this crate.
838mod detail {
839    use crate::db;
840    use sqlx::{
841        Row, Sqlite,
842        sqlite::{SqliteArguments, SqliteRow},
843    };
844
845    type Query<'q> = sqlx::query::Query<'q, Sqlite, SqliteArguments<'q>>;
846
847    pub trait Get: Sized {
848        fn get(row: &SqliteRow) -> Result<Self, sqlx::Error>;
849    }
850
851    pub trait Set<'a>
852    where
853        Self: 'a,
854    {
855        fn bind(self, query: Query<'a>) -> Query<'a>;
856    }
857
858    impl Get for bool {
859        fn get(row: &SqliteRow) -> Result<Self, sqlx::Error> {
860            row.try_get(0)
861        }
862    }
863
864    impl<'a> Set<'a> for bool {
865        fn bind(self, query: Query<'a>) -> Query<'a> {
866            query.bind(self)
867        }
868    }
869
870    impl Get for u64 {
871        fn get(row: &SqliteRow) -> Result<Self, sqlx::Error> {
872            row.try_get(0).map(db::decode_u64)
873        }
874    }
875
876    impl<'a> Set<'a> for u64 {
877        fn bind(self, query: Query<'a>) -> Query<'a> {
878            query.bind(db::encode_u64(self))
879        }
880    }
881
882    impl Get for String {
883        fn get(row: &SqliteRow) -> Result<Self, sqlx::Error> {
884            row.try_get(0)
885        }
886    }
887
888    impl<'a> Set<'a> for String {
889        fn bind(self, query: Query<'a>) -> Query<'a> {
890            query.bind(self)
891        }
892    }
893
894    impl<'a> Set<'a> for &'a str {
895        fn bind(self, query: Query<'a>) -> Query<'a> {
896            query.bind(self)
897        }
898    }
899}
900
901// -------------------------------------------------------------------
902// Secret values
903// -------------------------------------------------------------------
904async fn get_secret_blob<T>(
905    conn: &mut db::Connection,
906    id: &[u8],
907    local_key: &cipher::SecretKey,
908) -> Result<Option<T>, StoreError>
909where
910    for<'a> T: TryFrom<&'a [u8]>,
911{
912    let row = sqlx::query("SELECT nonce, value FROM metadata_secret WHERE name = ?")
913        .bind(id)
914        .fetch_optional(conn)
915        .await?;
916
917    let Some(row) = row else {
918        return Ok(None);
919    };
920
921    let nonce: &[u8] = row.get(0);
922    let nonce = Nonce::try_from(nonce).map_err(|_| StoreError::MalformedData)?;
923
924    let mut buffer: Vec<_> = row.get(1);
925
926    local_key.decrypt_no_aead(&nonce, &mut buffer);
927
928    let secret = T::try_from(&buffer).map_err(|_| StoreError::MalformedData)?;
929    buffer.zeroize();
930
931    Ok(Some(secret))
932}
933
934async fn set_secret_blob<T>(
935    tx: &mut db::WriteTransaction,
936    id: &[u8],
937    blob: T,
938    local_key: &cipher::SecretKey,
939) -> Result<(), StoreError>
940where
941    T: AsRef<[u8]>,
942{
943    let nonce = make_nonce();
944
945    let mut cypher = blob.as_ref().to_vec();
946    local_key.encrypt_no_aead(&nonce, &mut cypher);
947
948    sqlx::query(
949        "INSERT OR REPLACE INTO metadata_secret(name, nonce, value)
950            VALUES (?, ?, ?)",
951    )
952    .bind(id)
953    .bind(&nonce[..])
954    .bind(&cypher)
955    .execute(tx)
956    .await?;
957
958    Ok(())
959}
960
961fn make_nonce() -> Nonce {
962    // Random nonces should be OK given that we're not generating too many of them.
963    // But maybe consider using the mixed approach from this SO post?
964    // https://crypto.stackexchange.com/a/77986
965    rand::random()
966}
967
968// String used to validate the read key
969fn read_key_validator(id: &RepositoryId) -> Hash {
970    id.salted_hash(b"ouisync read key validator")
971}
972
973// -------------------------------------------------------------------
974async fn get_blob<T>(
975    conn: &mut db::Connection,
976    id: &[u8],
977    local_key: Option<&cipher::SecretKey>,
978) -> Result<Option<T>, StoreError>
979where
980    for<'a> T: TryFrom<&'a [u8]>,
981{
982    match local_key {
983        Some(local_key) => get_secret_blob(conn, id, local_key).await,
984        None => get_public_blob(conn, id).await,
985    }
986}
987
988async fn set_blob<T>(
989    tx: &mut db::WriteTransaction,
990    id: &[u8],
991    blob: T,
992    local_key: Option<&cipher::SecretKey>,
993) -> Result<(), StoreError>
994where
995    T: AsRef<[u8]>,
996{
997    match local_key {
998        Some(local_key) => set_secret_blob(tx, id, blob, local_key).await,
999        None => set_public_blob(tx, id, blob).await,
1000    }
1001}
1002
1003// -------------------------------------------------------------------
1004
1005#[cfg(test)]
1006mod tests {
1007    use super::*;
1008    use crate::db;
1009    use tempfile::TempDir;
1010
1011    async fn setup() -> (TempDir, db::Pool) {
1012        db::create_temp().await.unwrap()
1013    }
1014
1015    #[tokio::test(flavor = "multi_thread")]
1016    async fn store_plaintext() {
1017        let (_base_dir, pool) = setup().await;
1018        let mut tx = pool.begin_write().await.unwrap();
1019
1020        set_public_blob(&mut tx, b"hello", b"world").await.unwrap();
1021
1022        let v: [u8; 5] = get_public_blob(&mut tx, b"hello").await.unwrap().unwrap();
1023
1024        assert_eq!(b"world", &v);
1025    }
1026
1027    #[tokio::test(flavor = "multi_thread")]
1028    async fn store_cyphertext() {
1029        let (_base_dir, pool) = setup().await;
1030        let mut tx = pool.begin_write().await.unwrap();
1031
1032        let key = cipher::SecretKey::random();
1033
1034        set_secret_blob(&mut tx, b"hello", b"world", &key)
1035            .await
1036            .unwrap();
1037
1038        let v: [u8; 5] = get_secret_blob(&mut tx, b"hello", &key)
1039            .await
1040            .unwrap()
1041            .unwrap();
1042
1043        assert_eq!(b"world", &v);
1044    }
1045
1046    // Using a bad key should not decrypt properly, but also should not cause an error. This is to
1047    // let user claim plausible deniability in not knowing the real secret key/password.
1048    #[tokio::test(flavor = "multi_thread")]
1049    async fn bad_key_is_not_error() {
1050        let (_base_dir, pool) = setup().await;
1051        let mut tx = pool.begin_write().await.unwrap();
1052
1053        let good_key = cipher::SecretKey::random();
1054        let bad_key = cipher::SecretKey::random();
1055
1056        set_secret_blob(&mut tx, b"hello", b"world", &good_key)
1057            .await
1058            .unwrap();
1059
1060        let v: [u8; 5] = get_secret_blob(&mut tx, b"hello", &bad_key)
1061            .await
1062            .unwrap()
1063            .unwrap();
1064
1065        assert_ne!(b"world", &v);
1066    }
1067
1068    #[tokio::test(flavor = "multi_thread")]
1069    async fn store_restore() {
1070        let accesses = [
1071            Access::Blind {
1072                id: RepositoryId::random(),
1073            },
1074            Access::ReadUnlocked {
1075                id: RepositoryId::random(),
1076                read_key: cipher::SecretKey::random(),
1077            },
1078            Access::ReadLocked {
1079                id: RepositoryId::random(),
1080                local_secret: SetLocalSecret::random(),
1081                read_key: cipher::SecretKey::random(),
1082            },
1083            Access::WriteUnlocked {
1084                secrets: WriteSecrets::random(),
1085            },
1086            Access::WriteLocked {
1087                local_read_secret: SetLocalSecret::random(),
1088                local_write_secret: SetLocalSecret::random(),
1089                secrets: WriteSecrets::random(),
1090            },
1091            Access::WriteLockedReadUnlocked {
1092                local_write_secret: SetLocalSecret::random(),
1093                secrets: WriteSecrets::random(),
1094            },
1095        ];
1096
1097        for access in accesses {
1098            let (_base_dir, pool) = db::create_temp().await.unwrap();
1099
1100            let mut tx = pool.begin_write().await.unwrap();
1101            let local_keys = initialize_access_secrets(&mut tx, &access).await.unwrap();
1102            tx.commit().await.unwrap();
1103
1104            let local_key = local_keys
1105                .write
1106                .as_deref()
1107                .or(local_keys.read.as_deref())
1108                .cloned();
1109
1110            let mut tx = pool.begin_write().await.unwrap();
1111
1112            let local_secret = local_key.clone().map(LocalSecret::SecretKey);
1113
1114            let access_secrets = get_access_secrets(&mut tx, local_secret.as_ref())
1115                .await
1116                .unwrap();
1117
1118            assert_eq!(
1119                (access.secrets(), local_key.as_ref()),
1120                (access_secrets.0, access_secrets.1.as_deref())
1121            );
1122        }
1123    }
1124}