sos_account/
convert.rs

1//! Convert account data.
2use crate::{Account, Error, LocalAccount, Result};
3use serde::{Deserialize, Serialize};
4use sos_backend::AccessPoint;
5use sos_client_storage::ClientFolderStorage;
6use sos_core::{
7    crypto::{AccessKey, Cipher, KeyDerivation},
8    encode,
9};
10use sos_login::DelegatedAccess;
11use sos_vault::{
12    secret::SecretRow, BuilderCredentials, SecretAccess, Summary, Vault,
13    VaultBuilder,
14};
15
16/// Comparison between an existing cipher and a
17/// target cipher.
18///
19/// Used to determine which folders to modify when
20/// changing the cipher for an account.
21#[derive(Debug, Serialize, Deserialize)]
22pub struct CipherComparison {
23    /// Cipher to convert to.
24    pub cipher: Cipher,
25    /// Key derivation function.
26    pub kdf: KeyDerivation,
27    /// Identity folder conversion info.
28    pub identity: Option<Summary>,
29    /// User folders conversion info.
30    pub folders: Vec<Summary>,
31}
32
33impl CipherComparison {
34    /// Determine if this cipher conversion is empty.
35    pub fn is_empty(&self) -> bool {
36        self.identity.is_none() && self.folders.is_empty()
37    }
38}
39
40impl LocalAccount {
41    /// Build list of files to convert.
42    pub(super) async fn compare_cipher(
43        &self,
44        cipher: &Cipher,
45        kdf: Option<KeyDerivation>,
46    ) -> Result<CipherComparison> {
47        let kdf = kdf.unwrap_or_default();
48        let identity = self.login_folder_summary().await?;
49
50        let folders = self
51            .storage
52            .list_folders()
53            .into_iter()
54            .filter(|s| s.cipher() != cipher || s.kdf() != &kdf)
55            .map(|s| s.clone())
56            .collect::<Vec<_>>();
57
58        let identity =
59            if cipher != identity.cipher() || &kdf != identity.kdf() {
60                Some(identity.clone())
61            } else {
62                None
63            };
64
65        Ok(CipherComparison {
66            cipher: *cipher,
67            kdf,
68            identity,
69            folders,
70        })
71    }
72
73    /// Build list of files to convert.
74    pub(super) async fn convert_cipher(
75        &mut self,
76        conversion: &CipherComparison,
77        account_key: &AccessKey,
78    ) -> Result<()> {
79        for folder in &conversion.folders {
80            let key = self
81                .find_folder_password(folder.id())
82                .await?
83                .ok_or(Error::NoFolderPassword(*folder.id()))?;
84            let vault = self
85                .convert_folder_cipher(
86                    &conversion.cipher,
87                    &conversion.kdf,
88                    folder,
89                    &key,
90                    true,
91                )
92                .await?;
93
94            let buffer = encode(&vault).await?;
95            self.import_folder_buffer(buffer, key, true).await?;
96        }
97
98        if let Some(identity) = &conversion.identity {
99            let vault = self
100                .convert_folder_cipher(
101                    &conversion.cipher,
102                    &conversion.kdf,
103                    identity,
104                    account_key,
105                    false,
106                )
107                .await?;
108
109            self.import_login_folder(vault).await?;
110        };
111
112        Ok(())
113    }
114
115    async fn convert_folder_cipher(
116        &mut self,
117        cipher: &Cipher,
118        kdf: &KeyDerivation,
119        folder: &Summary,
120        key: &AccessKey,
121        is_folder: bool,
122    ) -> Result<Vault> {
123        let id = folder.id();
124        tracing::debug!(
125            from = %folder.cipher(),
126            to = %cipher,
127            is_identity = %!is_folder,
128            id = %id,
129            "convert cipher");
130
131        let vault = if is_folder {
132            self.storage.read_vault(id).await?
133        } else {
134            self.storage.read_login_vault().await?
135        };
136
137        let seed = vault.seed().cloned();
138        let name = vault.name().to_owned();
139        let mut input = AccessPoint::from_vault(vault);
140        input.unlock(key).await?;
141        let meta = input.vault_meta().await?;
142
143        let builder = VaultBuilder::new()
144            .id(*input.id())
145            .public_name(name)
146            .description(meta.description().to_owned())
147            .flags(folder.flags().clone())
148            .kdf(kdf.clone())
149            .cipher(*cipher);
150
151        let output_vault = match key {
152            AccessKey::Password(password) => {
153                builder
154                    .build(BuilderCredentials::Password(
155                        password.clone(),
156                        seed,
157                    ))
158                    .await?
159            }
160            _ => {
161                todo!("handle asymmetric shared folders when changing cipher")
162            }
163        };
164
165        let mut output = AccessPoint::from_vault(output_vault);
166        output.unlock(key).await?;
167
168        for key in input.vault().keys() {
169            let (meta, secret, _) = input.read_secret(key).await?.unwrap();
170            let secret_data = SecretRow::new(*key, meta, secret);
171            output.create_secret(&secret_data).await?;
172        }
173
174        Ok(output.into())
175    }
176}