ouisync/access_control/
mod.rs

1mod access_mode;
2mod local_secret;
3mod share_token;
4
5pub use self::{
6    access_mode::AccessMode,
7    local_secret::{LocalSecret, SetLocalSecret},
8    share_token::ShareToken,
9};
10
11use crate::{
12    crypto::{cipher, sign},
13    error::Error,
14    protocol::RepositoryId,
15    Result,
16};
17use ouisync_macros::api;
18use rand::{rngs::OsRng, CryptoRng, Rng};
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20use std::{fmt, str::Utf8Error, string::FromUtf8Error, sync::Arc};
21use thiserror::Error;
22
23/// Secrets for access to a repository.
24#[derive(Clone, Serialize, Deserialize)]
25pub enum AccessSecrets {
26    Blind {
27        id: RepositoryId,
28    },
29    Read {
30        id: RepositoryId,
31        read_key: cipher::SecretKey,
32    },
33    Write(WriteSecrets),
34}
35
36impl AccessSecrets {
37    /// Generates random access secrets with write access using the provided RNG.
38    pub fn generate_write<R: Rng + CryptoRng>(rng: &mut R) -> Self {
39        Self::Write(WriteSecrets::generate(rng))
40    }
41
42    /// Generates random access secrets with write access using OsRng.
43    pub fn random_write() -> Self {
44        Self::Write(WriteSecrets::random())
45    }
46
47    /// Change the access mode of this secrets to the given mode. If the given mode is higher than
48    /// self, returns self unchanged.
49    pub fn with_mode(&self, mode: AccessMode) -> Self {
50        match (self, mode) {
51            (Self::Blind { .. }, AccessMode::Blind | AccessMode::Read | AccessMode::Write)
52            | (Self::Read { .. }, AccessMode::Read | AccessMode::Write)
53            | (Self::Write { .. }, AccessMode::Write) => self.clone(),
54            (Self::Read { id, .. } | Self::Write(WriteSecrets { id, .. }), AccessMode::Blind) => {
55                Self::Blind { id: *id }
56            }
57            (Self::Write(WriteSecrets { id, read_key, .. }), AccessMode::Read) => Self::Read {
58                id: *id,
59                read_key: read_key.clone(),
60            },
61        }
62    }
63
64    pub fn access_mode(&self) -> AccessMode {
65        match self {
66            Self::Blind { .. } => AccessMode::Blind,
67            Self::Read { .. } => AccessMode::Read,
68            Self::Write(_) => AccessMode::Write,
69        }
70    }
71
72    pub fn id(&self) -> &RepositoryId {
73        match self {
74            Self::Blind { id } | Self::Read { id, .. } | Self::Write(WriteSecrets { id, .. }) => id,
75        }
76    }
77
78    pub fn can_write(&self) -> bool {
79        matches!(self, Self::Write(_))
80    }
81
82    pub fn can_read(&self) -> bool {
83        matches!(self, Self::Read { .. } | Self::Write(_))
84    }
85
86    pub fn read_key(&self) -> Option<&cipher::SecretKey> {
87        match self {
88            Self::Blind { .. } => None,
89            Self::Read { read_key, .. } => Some(read_key),
90            Self::Write(secrets) => Some(&secrets.read_key),
91        }
92    }
93
94    pub fn write_secrets(&self) -> Option<&WriteSecrets> {
95        match self {
96            Self::Blind { .. } => None,
97            Self::Read { .. } => None,
98            Self::Write(secrets) => Some(secrets),
99        }
100    }
101
102    pub fn into_write_secrets(self) -> Option<WriteSecrets> {
103        match self {
104            Self::Blind { .. } => None,
105            Self::Read { .. } => None,
106            Self::Write(secrets) => Some(secrets),
107        }
108    }
109
110    pub(crate) fn keys(&self) -> Option<AccessKeys> {
111        match self {
112            Self::Blind { .. } => None,
113            Self::Read { read_key, .. } => Some(AccessKeys {
114                read: read_key.clone(),
115                write: None,
116            }),
117            Self::Write(secrets) => Some(AccessKeys {
118                read: secrets.read_key.clone(),
119                write: Some(secrets.write_keys.clone()),
120            }),
121        }
122    }
123}
124
125impl fmt::Debug for AccessSecrets {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        match self {
128            Self::Blind { .. } => f.debug_struct("Blind").finish_non_exhaustive(),
129            Self::Read { .. } => f.debug_struct("Read").finish_non_exhaustive(),
130            Self::Write { .. } => f.debug_struct("Write").finish_non_exhaustive(),
131        }
132    }
133}
134
135impl PartialEq for AccessSecrets {
136    fn eq(&self, other: &Self) -> bool {
137        self.access_mode() == other.access_mode() && self.id() == other.id()
138    }
139}
140
141impl Eq for AccessSecrets {}
142
143/// Secrets for write access.
144#[derive(Clone)]
145pub struct WriteSecrets {
146    pub id: RepositoryId,
147    pub read_key: cipher::SecretKey,
148    pub write_keys: Arc<sign::Keypair>,
149}
150
151impl WriteSecrets {
152    /// Generates random write secrets using the provided RNG.
153    pub fn generate<R: Rng + CryptoRng>(rng: &mut R) -> Self {
154        Self::from(sign::Keypair::generate(rng))
155    }
156
157    /// Generates random write secrets using OsRng.
158    pub fn random() -> Self {
159        Self::generate(&mut OsRng)
160    }
161}
162
163impl PartialEq for WriteSecrets {
164    fn eq(&self, other: &Self) -> bool {
165        self.id == other.id
166    }
167}
168
169impl Eq for WriteSecrets {}
170
171impl From<sign::Keypair> for WriteSecrets {
172    fn from(keys: sign::Keypair) -> Self {
173        let id = keys.public_key().into();
174        let read_key = derive_read_key_from_write_keys(&keys);
175
176        Self {
177            id,
178            read_key,
179            write_keys: Arc::new(keys),
180        }
181    }
182}
183
184impl Serialize for WriteSecrets {
185    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
186    where
187        S: Serializer,
188    {
189        // Serialize only the write keys because all the other fields can be derived from it
190        self.write_keys.serialize(s)
191    }
192}
193
194impl<'de> Deserialize<'de> for WriteSecrets {
195    fn deserialize<D>(d: D) -> Result<Self, D::Error>
196    where
197        D: Deserializer<'de>,
198    {
199        Ok(Self::from(sign::Keypair::deserialize(d)?))
200    }
201}
202
203/// Secret keys for read and optionaly write access.
204#[derive(Clone)]
205pub(crate) struct AccessKeys {
206    read: cipher::SecretKey,
207    write: Option<Arc<sign::Keypair>>,
208}
209
210impl AccessKeys {
211    pub fn read(&self) -> &cipher::SecretKey {
212        &self.read
213    }
214
215    pub fn write(&self) -> Option<&sign::Keypair> {
216        self.write.as_deref()
217    }
218
219    pub fn read_only(self) -> Self {
220        Self {
221            read: self.read,
222            write: None,
223        }
224    }
225}
226
227impl From<WriteSecrets> for AccessKeys {
228    fn from(secrets: WriteSecrets) -> Self {
229        Self {
230            read: secrets.read_key,
231            write: Some(secrets.write_keys),
232        }
233    }
234}
235
236fn derive_read_key_from_write_keys(write_keys: &sign::Keypair) -> cipher::SecretKey {
237    cipher::SecretKey::derive_from_key(&write_keys.to_bytes(), b"ouisync repository read key")
238}
239
240#[derive(Debug, Error)]
241#[error("decode error")]
242pub struct DecodeError;
243
244impl From<base64::DecodeError> for DecodeError {
245    fn from(_: base64::DecodeError) -> Self {
246        Self
247    }
248}
249
250impl From<bincode::Error> for DecodeError {
251    fn from(_: bincode::Error) -> Self {
252        Self
253    }
254}
255
256impl From<FromUtf8Error> for DecodeError {
257    fn from(_: FromUtf8Error) -> Self {
258        Self
259    }
260}
261
262impl From<Utf8Error> for DecodeError {
263    fn from(_: Utf8Error) -> Self {
264        Self
265    }
266}
267
268impl From<sign::SignatureError> for DecodeError {
269    fn from(_: sign::SignatureError) -> Self {
270        Self
271    }
272}
273
274impl From<cipher::SecretKeyLengthError> for DecodeError {
275    fn from(_: cipher::SecretKeyLengthError) -> Self {
276        Self
277    }
278}
279
280impl From<DecodeError> for Error {
281    fn from(_: DecodeError) -> Self {
282        Self::MalformedData
283    }
284}
285
286pub enum Access {
287    // User has no read nor write access, can only sync.
288    Blind {
289        id: RepositoryId,
290    },
291    // User doesn't need a secret to read the repository, there's no write access.
292    ReadUnlocked {
293        id: RepositoryId,
294        read_key: cipher::SecretKey,
295    },
296    // Providing a secret will grant the user read access, there's no write access.
297    ReadLocked {
298        id: RepositoryId,
299        local_secret: SetLocalSecret,
300        read_key: cipher::SecretKey,
301    },
302    // User doesn't need a secret to read nor write.
303    WriteUnlocked {
304        secrets: WriteSecrets,
305    },
306    // Providing a secret user will grant read and write access. The secret may be different for
307    // reading or writing.
308    WriteLocked {
309        local_read_secret: SetLocalSecret,
310        local_write_secret: SetLocalSecret,
311        secrets: WriteSecrets,
312    },
313    // User doesn't need a secret to read, but a secret will grant access to write.
314    WriteLockedReadUnlocked {
315        local_write_secret: SetLocalSecret,
316        secrets: WriteSecrets,
317    },
318}
319
320impl Access {
321    pub fn new(
322        local_read_secret: Option<SetLocalSecret>,
323        local_write_secret: Option<SetLocalSecret>,
324        secrets: AccessSecrets,
325    ) -> Self {
326        match (local_read_secret, local_write_secret, secrets) {
327            (_, _, AccessSecrets::Blind { id }) => Access::Blind { id },
328            (None, _, AccessSecrets::Read { id, read_key }) => {
329                Access::ReadUnlocked { id, read_key }
330            }
331            (Some(local_read_secret), _, AccessSecrets::Read { id, read_key }) => {
332                Access::ReadLocked {
333                    id,
334                    local_secret: local_read_secret,
335                    read_key,
336                }
337            }
338            (None, None, AccessSecrets::Write(secrets)) => Access::WriteUnlocked { secrets },
339            (Some(local_read_secret), None, AccessSecrets::Write(secrets)) => Access::ReadLocked {
340                id: secrets.id,
341                local_secret: local_read_secret,
342                read_key: secrets.read_key,
343            },
344            (None, Some(local_write_secret), AccessSecrets::Write(secrets)) => {
345                Access::WriteLockedReadUnlocked {
346                    local_write_secret,
347                    secrets,
348                }
349            }
350            (Some(local_read_secret), Some(local_write_secret), AccessSecrets::Write(secrets)) => {
351                Access::WriteLocked {
352                    local_read_secret,
353                    local_write_secret,
354                    secrets,
355                }
356            }
357        }
358    }
359
360    pub fn id(&self) -> &RepositoryId {
361        match self {
362            Self::Blind { id } => id,
363            Self::ReadUnlocked { id, .. } => id,
364            Self::ReadLocked { id, .. } => id,
365            Self::WriteUnlocked { secrets } => &secrets.id,
366            Self::WriteLocked { secrets, .. } => &secrets.id,
367            Self::WriteLockedReadUnlocked { secrets, .. } => &secrets.id,
368        }
369    }
370
371    pub fn secrets(self) -> AccessSecrets {
372        match self {
373            Self::Blind { id } => AccessSecrets::Blind { id },
374            Self::ReadUnlocked { id, read_key } => AccessSecrets::Read { id, read_key },
375            Self::ReadLocked { id, read_key, .. } => AccessSecrets::Read { id, read_key },
376            Self::WriteUnlocked { secrets } => AccessSecrets::Write(secrets),
377            Self::WriteLocked { secrets, .. } => AccessSecrets::Write(secrets),
378            Self::WriteLockedReadUnlocked { secrets, .. } => AccessSecrets::Write(secrets),
379        }
380    }
381
382    pub fn local_write_secret(&self) -> Option<&SetLocalSecret> {
383        match self {
384            Self::WriteLocked {
385                local_write_secret, ..
386            } => Some(local_write_secret),
387            Self::WriteLockedReadUnlocked {
388                local_write_secret, ..
389            } => Some(local_write_secret),
390            _ => None,
391        }
392    }
393
394    #[cfg(test)]
395    pub fn highest_local_secret(&self) -> Option<&SetLocalSecret> {
396        match self {
397            Self::Blind { .. } => None,
398            Self::ReadUnlocked { .. } => None,
399            Self::ReadLocked { local_secret, .. } => Some(local_secret),
400            Self::WriteUnlocked { .. } => None,
401            Self::WriteLocked {
402                local_write_secret, ..
403            } => Some(local_write_secret),
404            Self::WriteLockedReadUnlocked {
405                local_write_secret, ..
406            } => Some(local_write_secret),
407        }
408    }
409}
410
411/// How to change access to a repository.
412#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
413#[api]
414pub enum AccessChange {
415    /// Enable read or write access, optionally with local secret
416    Enable(Option<SetLocalSecret>),
417
418    /// Disable access
419    Disable,
420}
421
422#[cfg(test)]
423mod tests {
424    use crate::crypto::PasswordSalt;
425
426    use super::*;
427
428    // Note we don't actually use JSON anywhere in the protocol but this test uses it because it
429    // being human readable makes it easy to verify the values are serialized the way we want them.
430    #[test]
431    fn access_change_serialize_deserialize_json() {
432        for (orig, expected_serialized) in [
433            (
434                AccessChange::Enable(Some(SetLocalSecret::Password("mellon".to_string().into()))),
435                "{\"Enable\":{\"Password\":\"mellon\"}}",
436            ),
437            (AccessChange::Enable(None), "{\"Enable\":null}"),
438            (AccessChange::Disable, "\"Disable\""),
439        ] {
440            let serialized = serde_json::to_string(&orig).unwrap();
441            assert_eq!(serialized, expected_serialized);
442
443            let deserialized: AccessChange = serde_json::from_str(&serialized).unwrap();
444            assert_eq!(deserialized, orig);
445        }
446    }
447
448    // This fails to deserialize the secret key. It works with msgpack, so not a big deal as long
449    // as we don't use json, but curious still.
450    #[ignore]
451    #[test]
452    fn access_change_key_serialize_deserialize_json() {
453        let key = cipher::SecretKey::random();
454        let salt = PasswordSalt::random();
455        let key_serialized = serde_json::to_string(&key).unwrap();
456        let salt_serialized = serde_json::to_string(&salt).unwrap();
457
458        let orig = AccessChange::Enable(Some(SetLocalSecret::KeyAndSalt { key, salt }));
459
460        let expected_serialized = format!(
461            "{{\"enable\":{{\"key_and_salt\":{{\"key\":{}, \"salt\":{}}}}}}}",
462            key_serialized, salt_serialized
463        );
464
465        let serialized = serde_json::to_string(&orig).unwrap();
466        assert_eq!(serialized, expected_serialized);
467
468        let deserialized: AccessChange = serde_json::from_str(&serialized).unwrap();
469        assert_eq!(deserialized, orig);
470    }
471
472    #[test]
473    fn access_change_serialize_deserialize_msgpack() {
474        for (orig, expected_serialized_hex) in [
475            (
476                AccessChange::Enable(Some(SetLocalSecret::Password("mellon".to_string().into()))),
477                "81a6456e61626c6581a850617373776f7264a66d656c6c6f6e",
478            ),
479            (AccessChange::Enable(None), "81a6456e61626c65c0"),
480            (AccessChange::Disable, "a744697361626c65"),
481        ] {
482            let serialized = rmp_serde::to_vec(&orig).unwrap();
483            assert_eq!(hex::encode(&serialized), expected_serialized_hex);
484
485            let deserialized: AccessChange = rmp_serde::from_slice(&serialized).unwrap();
486            assert_eq!(deserialized, orig);
487        }
488    }
489
490    #[test]
491    fn access_change_key_serialize_deserialize_msgpack() {
492        let key = cipher::SecretKey::random();
493        let salt = PasswordSalt::random();
494
495        let orig = AccessChange::Enable(Some(SetLocalSecret::KeyAndSalt { key, salt }));
496        let serialized = rmp_serde::to_vec(&orig).unwrap();
497        let deserialized: AccessChange = rmp_serde::from_slice(&serialized).unwrap();
498        assert_eq!(deserialized, orig);
499    }
500}