ouisync/repository/
metadata.rs

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