sos_core/
lib.rs

1#![deny(missing_docs)]
2#![forbid(unsafe_code)]
3#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
4//! Core types and constants for the [Save Our Secrets](https://saveoursecrets.com) SDK.
5
6mod account;
7pub mod commit;
8pub mod constants;
9pub mod crypto;
10mod date_time;
11pub mod device;
12pub mod encoding;
13mod error;
14pub mod events;
15mod file;
16pub mod file_identity;
17mod identity;
18mod origin;
19mod paths;
20
21pub use account::AccountId;
22// pub use crypto::*;
23pub use date_time::UtcDateTime;
24// pub use device::{DevicePublicKey, TrustedDevice};
25pub use encoding::{decode, encode};
26pub use error::{AuthenticationError, Error, ErrorExt, StorageError};
27pub use file::{ExternalFile, ExternalFileName};
28pub use identity::{AccountRef, PublicIdentity};
29pub use origin::{Origin, RemoteOrigins};
30pub use paths::Paths;
31pub use rs_merkle as merkle;
32
33/// Result type for the library.
34pub(crate) type Result<T> = std::result::Result<T, Error>;
35
36use bitflags::bitflags;
37use rand::{rngs::OsRng, CryptoRng, Rng};
38use serde::{Deserialize, Serialize};
39use std::{fmt, path::Path, str::FromStr};
40use uuid::Uuid;
41
42/// Exposes the default cryptographically secure RNG.
43pub fn csprng() -> impl CryptoRng + Rng {
44    OsRng
45}
46
47/// Identifier for a vault.
48pub type VaultId = Uuid;
49
50/// Identifier for a secret.
51pub type SecretId = Uuid;
52
53/// Secret as an encrypted pair of meta and secret data.
54#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
55pub struct VaultEntry(pub crypto::AeadPack, pub crypto::AeadPack);
56
57/// Encrypted secret with an associated commit hash.
58#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
59pub struct VaultCommit(pub commit::CommitHash, pub VaultEntry);
60
61/// Path to a secret.
62#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
63pub struct SecretPath(pub VaultId, pub SecretId);
64
65impl SecretPath {
66    /// Folder identifier.
67    pub fn folder_id(&self) -> &VaultId {
68        &self.0
69    }
70
71    /// Secret identifier.
72    pub fn secret_id(&self) -> &SecretId {
73        &self.1
74    }
75}
76
77/// Infallibly compute the base file name from a path.
78///
79/// If no file name is available the returned value is the
80/// empty string.
81pub fn basename(path: impl AsRef<Path>) -> String {
82    path.as_ref()
83        .file_name()
84        .unwrap_or_default()
85        .to_string_lossy()
86        .into_owned()
87}
88
89/// Guess the MIME type of a path.
90///
91/// This implementation supports some more types
92/// that are not in the the mime_guess library that
93/// we also want to recognize.
94pub fn guess_mime(path: impl AsRef<Path>) -> Result<String> {
95    if let Some(extension) = path.as_ref().extension() {
96        let fixed = match extension.to_string_lossy().as_ref() {
97            "heic" => Some("image/heic".to_string()),
98            "heif" => Some("image/heif".to_string()),
99            "avif" => Some("image/avif".to_string()),
100            _ => None,
101        };
102
103        if let Some(fixed) = fixed {
104            return Ok(fixed);
105        }
106    }
107    let mime = mime_guess::from_path(&path)
108        .first_or(mime_guess::mime::APPLICATION_OCTET_STREAM)
109        .to_string();
110    Ok(mime)
111}
112
113bitflags! {
114    /// Bit flags for a vault.
115    #[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
116    #[serde(transparent)]
117    pub struct VaultFlags: u64 {
118        /// Indicates this vault should be treated as
119        /// the default folder.
120        const DEFAULT           =        0b0000000000000001;
121        /// Indicates this vault is an identity vault used
122        /// to authenticate a user and store delegated folder passwords.
123        const IDENTITY          =        0b0000000000000010;
124        /// Indicates this vault is to be used as an archive.
125        const ARCHIVE           =        0b0000000000000100;
126        /// Indicates this vault is to be used for
127        /// two-factor authentication.
128        const AUTHENTICATOR     =        0b0000000000001000;
129        /// Indicates this vault is to be used to store contacts.
130        const CONTACT           =        0b0000000000010000;
131        /// Indicates this vault is a system vault and should
132        /// not be presented to the account holder when listing
133        /// available vaults.
134        const SYSTEM            =        0b0000000000100000;
135        /// Indicates this vault is to be used to store device
136        /// specific information such as key shares or device
137        /// specific private keys.
138        ///
139        /// Typically these vaults should also be assigned the
140        /// NO_SYNC flag.
141        const DEVICE            =        0b0000000001000000;
142        /// Indicates this vault should not be synced with
143        /// devices owned by the account holder.
144        ///
145        /// This is useful for storing device specific keys.
146        const NO_SYNC           =        0b0000000010000000;
147        /// Indicates the folder is intended to be local first.
148        const LOCAL             =        0b0000000100000000;
149        /// Indicates this vault is shared using asymmetric
150        /// encryption.
151        const SHARED            =        0b0000001000000000;
152    }
153}
154
155impl VaultFlags {
156    /// Determine if this vault is a default vault.
157    pub fn is_default(&self) -> bool {
158        self.contains(VaultFlags::DEFAULT)
159    }
160
161    /// Determine if this vault is an identity vault.
162    pub fn is_identity(&self) -> bool {
163        self.contains(VaultFlags::IDENTITY)
164    }
165
166    /// Determine if this vault is an archive vault.
167    pub fn is_archive(&self) -> bool {
168        self.contains(VaultFlags::ARCHIVE)
169    }
170
171    /// Determine if this vault is an authenticator vault.
172    pub fn is_authenticator(&self) -> bool {
173        self.contains(VaultFlags::AUTHENTICATOR)
174    }
175
176    /// Determine if this vault is for contacts.
177    pub fn is_contact(&self) -> bool {
178        self.contains(VaultFlags::CONTACT)
179    }
180
181    /// Determine if this vault is for system specific information.
182    pub fn is_system(&self) -> bool {
183        self.contains(VaultFlags::SYSTEM)
184    }
185
186    /// Determine if this vault is for device specific information.
187    pub fn is_device(&self) -> bool {
188        self.contains(VaultFlags::DEVICE)
189    }
190
191    /// Determine if this vault is set to ignore sync
192    /// with other devices owned by the account holder.
193    pub fn is_sync_disabled(&self) -> bool {
194        self.contains(VaultFlags::NO_SYNC)
195    }
196
197    /// Determine if this vault is local first.
198    pub fn is_local(&self) -> bool {
199        self.contains(VaultFlags::LOCAL)
200    }
201
202    /// Determine if this vault is shared.
203    pub fn is_shared(&self) -> bool {
204        self.contains(VaultFlags::SHARED)
205    }
206}
207
208/// Manifest version for backup archives.
209#[repr(u8)]
210#[derive(Debug, Copy, Clone, PartialEq, Eq)]
211pub enum ArchiveManifestVersion {
212    /// Version 1 backup archives correspond to the
213    /// v1 file system storage but do not include some
214    /// additional event logs which were added later
215    /// and are optional.
216    ///
217    /// A single backup archive includes only one account.
218    V1 = 1,
219
220    /// Version 2 backup archives correspond to the
221    /// v1 file system storage and include all event
222    /// logs, preferences and remote origins.
223    ///
224    /// A single backup archive includes only one account.
225    V2 = 2,
226
227    /// Version 3 backup archives include the SQLite
228    /// database and external file blobs and may contain
229    /// multiple accounts.
230    V3 = 3,
231}
232
233impl Default for ArchiveManifestVersion {
234    // Backwards compatible before we added tracking
235    // of backup archive manifest versions.
236    //
237    // For version 2 and version 3 the version is
238    // explicitly added to each manifest file.
239    fn default() -> Self {
240        Self::V1
241    }
242}
243
244// Implement serialization manually
245impl Serialize for ArchiveManifestVersion {
246    fn serialize<S>(
247        &self,
248        serializer: S,
249    ) -> std::result::Result<S::Ok, S::Error>
250    where
251        S: serde::Serializer,
252    {
253        serializer.serialize_u8(*self as u8)
254    }
255}
256
257// Implement deserialization manually
258impl<'de> Deserialize<'de> for ArchiveManifestVersion {
259    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
260    where
261        D: serde::Deserializer<'de>,
262    {
263        let value = u8::deserialize(deserializer)?;
264        match value {
265            1 => Ok(ArchiveManifestVersion::V1),
266            2 => Ok(ArchiveManifestVersion::V2),
267            3 => Ok(ArchiveManifestVersion::V3),
268            _ => Err(serde::de::Error::custom(
269                "invalid archive manifest version",
270            )),
271        }
272    }
273}
274
275/// Reference to a folder using an id or a named label.
276#[derive(Debug, Clone)]
277pub enum FolderRef {
278    /// Vault identifier.
279    Id(VaultId),
280    /// Vault label.
281    Name(String),
282}
283
284impl fmt::Display for FolderRef {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        match self {
287            Self::Id(id) => write!(f, "{}", id),
288            Self::Name(name) => write!(f, "{}", name),
289        }
290    }
291}
292
293impl FromStr for FolderRef {
294    type Err = Error;
295    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
296        if let Ok(id) = Uuid::parse_str(s) {
297            Ok(Self::Id(id))
298        } else {
299            Ok(Self::Name(s.to_string()))
300        }
301    }
302}
303
304impl From<VaultId> for FolderRef {
305    fn from(value: VaultId) -> Self {
306        Self::Id(value)
307    }
308}