sos_login/
identity_folder.rs

1//! Login identity folder management.
2//!
3//! Provides access to an identity folder containing
4//! delegated passwords used to decrypt folders managed
5//! by an account.
6use crate::{
7    device::{DeviceManager, DeviceSigner},
8    DelegatedAccess, Error, PrivateIdentity, Result, UrnLookup,
9};
10use async_trait::async_trait;
11use secrecy::{ExposeSecret, SecretBox, SecretString};
12use sos_backend::{
13    database::entity::{AccountEntity, AccountRow, FolderEntity, FolderRow},
14    AccessPoint, BackendTarget, Folder, FolderEventLog,
15};
16use sos_core::{
17    constants::LOGIN_AGE_KEY_URN, crypto::AccessKey, encode,
18    events::EventLogType, AccountId, AuthenticationError, VaultFlags,
19    VaultId,
20};
21use sos_filesystem::write_exclusive;
22use sos_vault::Summary;
23use sos_vault::{
24    secret::{Secret, SecretId, SecretMeta, SecretRow, SecretSigner},
25    BuilderCredentials, SecretAccess, Vault, VaultBuilder,
26};
27use std::sync::Arc;
28use tokio::sync::RwLock;
29use urn::Urn;
30
31/// Identity folder stores delegated passwords and an
32/// asymmetric encryption key.
33pub struct IdentityFolder {
34    /// Folder storage.
35    #[doc(hidden)]
36    pub folder: Folder,
37    /// Lookup table.
38    #[doc(hidden)]
39    pub index: UrnLookup,
40    /// Backend target.
41    target: BackendTarget,
42    /// Private identity.
43    private_identity: PrivateIdentity,
44    /// Device manager.
45    pub(super) devices: Option<crate::device::DeviceManager>,
46}
47
48impl IdentityFolder {
49    /// Create a new identity folder.
50    pub async fn new(
51        target: BackendTarget,
52        name: String,
53        password: SecretString,
54        account_id: Option<AccountId>,
55    ) -> Result<Self> {
56        let account_id = account_id.unwrap_or_else(AccountId::random);
57        let target = target.with_account_id(&account_id);
58        tracing::debug!(
59            account_id = %account_id,
60            backend_target = %target,
61            "new_identity_folder");
62
63        let vault =
64            Self::create_identity_vault(name.clone(), &password).await?;
65
66        let mut folder = match &target {
67            BackendTarget::FileSystem(paths) => {
68                let buffer = encode(&vault).await?;
69                write_exclusive(paths.identity_vault(), buffer).await?;
70                Folder::from_path(
71                    paths.identity_vault(),
72                    &account_id,
73                    EventLogType::Identity,
74                )
75                .await?
76            }
77            BackendTarget::Database(_, client) => {
78                let account_row = AccountRow::new_insert(&account_id, name)?;
79                let folder_row = FolderRow::new_insert(&vault).await?;
80                client
81                    .conn_mut(move |conn| {
82                        let tx = conn.transaction()?;
83                        let account = AccountEntity::new(&tx);
84                        let folder = FolderEntity::new(&tx);
85
86                        let account_id = account.insert(&account_row)?;
87                        let folder_id =
88                            folder.insert_folder(account_id, &folder_row)?;
89                        account.insert_login_folder(account_id, folder_id)?;
90
91                        tx.commit()?;
92                        Ok(())
93                    })
94                    .await
95                    .map_err(sos_backend::database::Error::from)?;
96                Folder::new(target.clone(), &account_id, vault.id()).await?
97            }
98        };
99
100        let key: AccessKey = password.into();
101        folder.unlock(&key).await?;
102
103        let mut index: UrnLookup = Default::default();
104        let private_identity =
105            Self::create_age_identity(&account_id, &mut folder, &mut index)
106                .await?;
107
108        Ok(Self {
109            folder,
110            index,
111            target,
112            private_identity,
113            devices: None,
114        })
115    }
116
117    /// Private identity.
118    pub fn private_identity(&self) -> &PrivateIdentity {
119        &self.private_identity
120    }
121
122    /// Account address.
123    pub fn account_id(&self) -> &AccountId {
124        self.private_identity.account_id()
125    }
126
127    /// Folder summary.
128    pub async fn summary(&self) -> Summary {
129        let access_point = self.folder.access_point();
130        let access_point = access_point.lock().await;
131        access_point.vault().summary().clone()
132    }
133
134    /// Reference to the folder.
135    pub fn folder(&self) -> &Folder {
136        &self.folder
137    }
138
139    /// Folder event log.
140    pub fn event_log(&self) -> Arc<RwLock<FolderEventLog>> {
141        self.folder.event_log()
142    }
143
144    /// Verify the access key for this account.
145    pub async fn verify(&self, key: &AccessKey) -> bool {
146        let access_point = self.folder.access_point();
147        let access_point = access_point.lock().await;
148        access_point.verify(key).await.ok().is_some()
149    }
150
151    /// Signing key for this device.
152    ///
153    /// # Panics
154    ///
155    /// If the device manager has not been initialized.
156    pub fn device(&self) -> &DeviceSigner {
157        self.devices.as_ref().unwrap().signer()
158    }
159
160    /// Device manager.
161    pub fn devices(&self) -> Result<&DeviceManager> {
162        Ok(self
163            .devices
164            .as_ref()
165            .ok_or(AuthenticationError::NotAuthenticated)?)
166    }
167
168    /// Rename this identity vault.
169    pub async fn rename(&mut self, account_name: String) -> Result<()> {
170        let access_point = self.folder.access_point();
171        let mut access_point = access_point.lock().await;
172        access_point.set_vault_name(account_name.clone()).await?;
173
174        // Database backend targets store the primary account
175        // name in a column whereas previously for the file system
176        // backend we used the name of the identity login folder
177        // so for database backends update in both places
178        match &self.target {
179            BackendTarget::Database(_, client) => {
180                let account_id = *self.private_identity.account_id();
181                client
182                    .conn_and_then(move |conn| {
183                        let account_entity = AccountEntity::new(&conn);
184                        let account = account_entity.find_one(&account_id)?;
185                        account_entity
186                            .rename_account(account.row_id, &account_name)?;
187                        Ok::<_, Error>(())
188                    })
189                    .await?;
190            }
191            _ => {}
192        }
193
194        Ok(())
195    }
196
197    /// Ensure that the account has a vault for storing device specific
198    /// information such as the private key used to identify a machine.
199    pub(super) async fn ensure_device_vault(
200        &mut self,
201        target: BackendTarget,
202    ) -> Result<()> {
203        let account_id = self.private_identity.account_id();
204        let device_vault = target.read_device_vault(account_id).await?;
205        let device_manager = if let Some(vault) = device_vault {
206            let device_password = self
207                .find_folder_password(vault.id())
208                .await?
209                .ok_or(Error::NoFolderPassword(*vault.id()))?;
210            let key: AccessKey = device_password.into();
211
212            let (device_manager, id) =
213                DeviceManager::open_vault(target, vault, &key).await?;
214
215            // Add to the URN lookup index
216            let urn = DeviceManager::device_urn()?;
217            self.index
218                .insert((*device_manager.access_point().id(), urn), id);
219
220            device_manager
221        } else {
222            self.new_device_manager(&target, DeviceSigner::random())
223                .await?
224        };
225        self.devices = Some(device_manager);
226        Ok(())
227    }
228
229    /// Create a device manager from a signer.
230    pub async fn new_device_manager(
231        &mut self,
232        target: &BackendTarget,
233        signer: DeviceSigner,
234    ) -> Result<DeviceManager> {
235        let account_id = self.private_identity.account_id();
236        let password = self.generate_folder_password()?;
237        let key = password.into();
238
239        let mut device_manager =
240            DeviceManager::new(target, account_id, signer.clone(), &key)
241                .await?;
242
243        let folder_id = *device_manager.access_point().id();
244        let device_key_urn = DeviceManager::device_urn()?;
245
246        tracing::debug!(
247            urn = %device_key_urn,
248            "create_device_manager");
249
250        self.save_folder_password(&folder_id, key.clone()).await?;
251
252        // let key: AccessKey = device_password.into();
253        device_manager.access_point_mut().unlock(&key).await?;
254
255        let secret = Secret::Signer {
256            private_key: SecretSigner::SinglePartyEd25519(SecretBox::new(
257                signer.signing_key().to_bytes().into(),
258            )),
259            user_data: Default::default(),
260        };
261        let mut meta =
262            SecretMeta::new("Device Key".to_string(), secret.kind());
263        meta.set_urn(Some(device_key_urn.clone()));
264
265        let id = SecretId::new_v4();
266        let secret_data = SecretRow::new(id, meta, secret);
267        device_manager
268            .access_point_mut()
269            .create_secret(&secret_data)
270            .await?;
271
272        {
273            self.index.insert((folder_id, device_key_urn), id);
274        }
275
276        Ok(device_manager)
277    }
278
279    /// Rebuild the index lookup for folder passwords.
280    pub async fn rebuild_lookup_index(&mut self) -> Result<()> {
281        let access_point = self.folder.access_point();
282        let access_point = access_point.lock().await;
283        let (index, _) =
284            Self::lookup_identity_secrets(&*access_point).await?;
285        self.index = index;
286        Ok(())
287    }
288
289    #[cfg(feature = "files")]
290    pub(crate) async fn create_file_encryption_password(
291        &mut self,
292    ) -> Result<()> {
293        use sos_core::constants::FILE_PASSWORD_URN;
294        use sos_vault::secret::UserData;
295        let file_passphrase = self.generate_folder_password()?;
296        let secret = Secret::Password {
297            password: file_passphrase,
298            name: None,
299            user_data: UserData::new_comment(self.account_id().to_string()),
300        };
301        let mut meta =
302            SecretMeta::new("File Encryption".to_string(), secret.kind());
303        let urn: Urn = FILE_PASSWORD_URN.parse()?;
304        meta.set_urn(Some(urn.clone()));
305
306        let secret_id = SecretId::new_v4();
307        let secret_data = SecretRow::new(secret_id, meta, secret);
308        self.folder.create_secret(&secret_data).await?;
309        self.index.insert((self.folder.id().await, urn), secret_id);
310
311        Ok(())
312    }
313
314    /// Find the password used for symmetric file encryption (AGE).
315    #[cfg(feature = "files")]
316    pub(crate) async fn find_file_encryption_password(
317        &self,
318    ) -> Result<SecretString> {
319        use sos_core::constants::FILE_PASSWORD_URN;
320        let urn: Urn = FILE_PASSWORD_URN.parse()?;
321
322        let id = self
323            .index
324            .get(&(self.folder.id().await, urn.clone()))
325            .ok_or_else(|| Error::NoFileEncryptionPassword)?;
326
327        let password =
328            if let Some((_, Secret::Password { password, .. }, _)) =
329                self.folder.read_secret(id).await?
330            {
331                password
332            } else {
333                return Err(Error::VaultEntryKind(urn.to_string()));
334            };
335        Ok(password)
336    }
337
338    /// Sign out the identity vault.
339    ///
340    /// Locks the identity vault and device vault.
341    pub async fn sign_out(&mut self) -> Result<()> {
342        // Lock the identity vault
343        self.folder.lock().await;
344        self.index = Default::default();
345
346        // Lock the devices vault
347        if let Some(devices) = self.devices.as_mut() {
348            devices.sign_out();
349        }
350
351        Ok(())
352    }
353
354    /// Lookup secrets in the identity folder and prepare
355    /// the URN lookup index which maps URNs to the
356    /// corresponding secret identifiers.
357    async fn lookup_identity_secrets(
358        keeper: &AccessPoint,
359    ) -> Result<(UrnLookup, Option<Secret>)> {
360        let mut index: UrnLookup = Default::default();
361
362        // let signer_urn: Urn = LOGIN_SIGNING_KEY_URN.parse()?;
363        let identity_urn: Urn = LOGIN_AGE_KEY_URN.parse()?;
364
365        // let mut signer_secret: Option<Secret> = None;
366        let mut identity_secret: Option<Secret> = None;
367
368        for secret_id in keeper.vault().keys() {
369            if let Some((meta, secret, _)) =
370                keeper.read_secret(secret_id).await?
371            {
372                if let Some(urn) = meta.urn() {
373                    if urn == &identity_urn {
374                        identity_secret = Some(secret);
375                    }
376
377                    // Add to the URN lookup index
378                    index.insert((*keeper.id(), urn.clone()), *secret_id);
379                }
380            }
381        }
382        Ok((index, identity_secret))
383    }
384
385    async fn login_private_identity(
386        account_id: AccountId,
387        keeper: &AccessPoint,
388    ) -> Result<(UrnLookup, PrivateIdentity)> {
389        let (index, identity_secret) =
390            Self::lookup_identity_secrets(keeper).await?;
391
392        let identity = identity_secret.ok_or(Error::NoIdentityKey)?;
393
394        // Identity key extraction
395        let identity = if let Secret::Age { key, .. } = identity {
396            let identity: age::x25519::Identity =
397                key.expose_secret().parse().map_err(|s: &'static str| {
398                    Error::AgeIdentityParse(s.to_string())
399                })?;
400            Some(identity)
401        } else {
402            None
403        };
404        let shared = identity.ok_or(Error::NoIdentityKey)?;
405
406        let private_identity = PrivateIdentity {
407            account_id,
408            shared_public: shared.to_public(),
409            shared_private: shared,
410        };
411
412        Ok((index, private_identity))
413    }
414
415    /// Identifier of the folder.
416    pub async fn folder_id(&self) -> VaultId {
417        self.folder.id().await
418    }
419
420    /// Create an identity vault.
421    async fn create_identity_vault(
422        name: String,
423        password: &SecretString,
424    ) -> Result<Vault> {
425        Ok(VaultBuilder::new()
426            .public_name(name)
427            .flags(VaultFlags::IDENTITY)
428            .build(BuilderCredentials::Password(password.clone(), None))
429            .await?)
430    }
431
432    async fn create_age_identity(
433        account_id: &AccountId,
434        folder: &mut Folder,
435        index: &mut UrnLookup,
436    ) -> Result<PrivateIdentity> {
437        // Store the AGE identity
438        let identity_id = SecretId::new_v4();
439        let shared = age::x25519::Identity::generate();
440        let age_secret = Secret::Age {
441            version: Default::default(),
442            key: shared.to_string(),
443            user_data: Default::default(),
444        };
445        let identity_urn: Urn = LOGIN_AGE_KEY_URN.parse()?;
446        let mut age_meta = SecretMeta::new(
447            identity_urn.as_str().to_owned(),
448            age_secret.kind(),
449        );
450        age_meta.set_urn(Some(identity_urn.clone()));
451
452        let secret_data = SecretRow::new(identity_id, age_meta, age_secret);
453        folder.create_secret(&secret_data).await?;
454
455        let private_identity = PrivateIdentity {
456            account_id: *account_id,
457            shared_public: shared.to_public(),
458            shared_private: shared,
459        };
460
461        index.insert((folder.id().await, identity_urn), identity_id);
462
463        Ok(private_identity)
464    }
465
466    /// Login to an identity folder.
467    pub async fn login(
468        target: &BackendTarget,
469        account_id: &AccountId,
470        key: &AccessKey,
471    ) -> Result<Self> {
472        let target = target.clone().with_account_id(account_id);
473        let mut folder = match &target {
474            BackendTarget::FileSystem(paths) => {
475                Folder::from_path(
476                    paths.identity_vault(),
477                    account_id,
478                    EventLogType::Identity,
479                )
480                .await?
481            }
482            BackendTarget::Database(_, client) => {
483                let (_, login_folder) =
484                    AccountEntity::find_account_with_login(
485                        client, account_id,
486                    )
487                    .await?;
488
489                Folder::new(
490                    target.clone(),
491                    account_id,
492                    login_folder.summary.id(),
493                )
494                .await?
495            }
496        };
497
498        {
499            let access_point = folder.access_point();
500            let access_point = access_point.lock().await;
501            if !access_point.vault().flags().contains(VaultFlags::IDENTITY) {
502                return Err(Error::NotIdentityFolder);
503            }
504        }
505
506        folder.unlock(key).await?;
507
508        let access_point = folder.access_point();
509        let access_point = access_point.lock().await;
510        let (index, private_identity) =
511            Self::login_private_identity(*account_id, &*access_point).await?;
512
513        Ok(Self {
514            folder,
515            index,
516            target,
517            private_identity,
518            devices: None,
519        })
520    }
521}
522
523impl From<IdentityFolder> for Vault {
524    fn from(value: IdentityFolder) -> Self {
525        value.folder.into()
526    }
527}
528
529#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
530#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
531impl DelegatedAccess for IdentityFolder {
532    type Error = Error;
533
534    async fn find_folder_password(
535        &self,
536        folder_id: &VaultId,
537    ) -> Result<Option<AccessKey>> {
538        let urn = Vault::vault_urn(folder_id)?;
539        let login_folder_id = self.folder.id().await;
540
541        tracing::debug!(
542            login_folder_id = %login_folder_id,
543            folder_id = %folder_id,
544            urn = %urn,
545            "find_folder_password");
546
547        if let Some(id) = self.index.get(&(login_folder_id, urn.clone())) {
548            let (_, secret, _) = self
549                .folder
550                .read_secret(id)
551                .await?
552                .ok_or_else(|| Error::NoSecretId(login_folder_id, *id))?;
553
554            let key = match secret {
555                Secret::Password { password, .. } => {
556                    AccessKey::Password(password)
557                }
558                Secret::Age { key, .. } => {
559                    AccessKey::Identity(key.expose_secret().parse().map_err(
560                        |s: &str| Error::InvalidX25519Identity(s.to_owned()),
561                    )?)
562                }
563                _ => {
564                    return Err(Error::VaultEntryKind(urn.to_string()));
565                }
566            };
567            Ok(Some(key))
568        } else {
569            Ok(None)
570        }
571    }
572
573    async fn remove_folder_password(
574        &mut self,
575        folder_id: &VaultId,
576    ) -> Result<()> {
577        tracing::debug!(folder = %folder_id, "remove_folder_password");
578
579        let (id, index_key) = {
580            let urn = Vault::vault_urn(folder_id)?;
581            let index_key = (self.folder.id().await, urn);
582            let id = self.index.get(&index_key);
583            (id, index_key)
584        };
585
586        if let Some(id) = &id {
587            self.folder.delete_secret(&id).await?;
588        } else {
589            tracing::warn!("remove_folder_password::secret_id_not_found");
590        }
591        self.index.remove(&index_key);
592
593        Ok(())
594    }
595
596    async fn save_folder_password(
597        &mut self,
598        folder_id: &VaultId,
599        key: AccessKey,
600    ) -> Result<()> {
601        let urn = Vault::vault_urn(folder_id)?;
602        tracing::debug!(
603            folder = %folder_id,
604            urn = %urn,
605            "save_folder_password",
606        );
607
608        let secret = match key {
609            AccessKey::Password(vault_passphrase) => Secret::Password {
610                name: None,
611                password: vault_passphrase,
612                user_data: Default::default(),
613            },
614            AccessKey::Identity(id) => Secret::Age {
615                version: Default::default(),
616                key: id.to_string(),
617                user_data: Default::default(),
618            },
619        };
620
621        let mut meta =
622            SecretMeta::new(urn.as_str().to_owned(), secret.kind());
623        meta.set_urn(Some(urn.clone()));
624
625        let id = SecretId::new_v4();
626
627        let secret_data = SecretRow::new(id, meta, secret);
628        self.folder.create_secret(&secret_data).await?;
629
630        self.index.insert((self.folder.id().await, urn), id);
631
632        Ok(())
633    }
634}