1use 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#[derive(Debug, Serialize, Deserialize)]
22pub struct CipherComparison {
23 pub cipher: Cipher,
25 pub kdf: KeyDerivation,
27 pub identity: Option<Summary>,
29 pub folders: Vec<Summary>,
31}
32
33impl CipherComparison {
34 pub fn is_empty(&self) -> bool {
36 self.identity.is_none() && self.folders.is_empty()
37 }
38}
39
40impl LocalAccount {
41 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 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}