1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! Flow for changing a vault password.

use crate::{
    crypto::{AccessKey, KeyDerivation, PrivateKey, Seed},
    encode,
    events::WriteEvent,
    vault::{Vault, VaultAccess, VaultCommit, VaultEntry},
    Error, Result,
};

use std::borrow::Cow;

/// Builder that changes a vault password.
///
/// Generates a new vault derived from the original vault so
/// it is possible for callers to rollback to the original if
/// necessary.
pub struct ChangePassword<'a> {
    /// The in-memory vault.
    vault: &'a Vault,
    /// Existing encryption passphrase.
    current_key: AccessKey,
    /// New encryption passphrase.
    new_key: AccessKey,
    /// Optional seed for the new passphrase.
    seed: Option<Seed>,
}

impl<'a> ChangePassword<'a> {
    /// Create a new change password builder.
    pub fn new(
        vault: &'a Vault,
        current_key: AccessKey,
        new_key: AccessKey,
        seed: Option<Seed>,
    ) -> Self {
        Self {
            vault,
            current_key,
            new_key,
            seed,
        }
    }

    fn current_private_key(&self) -> Result<PrivateKey> {
        let salt = self.vault.salt().ok_or(Error::VaultNotInit)?;
        let salt = KeyDerivation::parse_salt(salt)?;
        self.current_key.clone().into_private(
            self.vault.kdf(),
            &salt,
            self.vault.seed(),
        )

        /*
        let salt = self.vault.salt().ok_or(Error::VaultNotInit)?;
        let salt = KeyDerivation::parse_salt(salt)?;
        let deriver = self.vault.deriver();
        let derived_private_key = deriver.derive(
            &self.current_key,
            &salt,
            self.vault.seed(),
        )?;
        Ok(PrivateKey::Symmetric(derived_private_key))
        */
    }

    fn new_private_key(&self, vault: &Vault) -> Result<PrivateKey> {
        let salt = vault.salt().ok_or(Error::VaultNotInit)?;
        let salt = KeyDerivation::parse_salt(salt)?;
        self.new_key
            .clone()
            .into_private(vault.kdf(), &salt, vault.seed())

        /*
        let salt = vault.salt().ok_or(Error::VaultNotInit)?;
        let salt = KeyDerivation::parse_salt(salt)?;
        let deriver = vault.deriver();
        let derived_private_key =
            deriver.derive(&self.new_key, &salt, vault.seed())?;
        Ok(PrivateKey::Symmetric(derived_private_key))
            */
    }

    /// Build a new vault.
    ///
    /// Yields the encrpytion passphrase for the new vault, the
    /// new computed vault and a collection of events that can
    /// be used to generate a fresh write-ahead log file.
    pub async fn build(
        self,
    ) -> Result<(AccessKey, Vault, Vec<WriteEvent<'static>>)> {
        // Decrypt current vault meta data blob
        let current_private_key = self.current_private_key()?;
        let vault_meta_aead =
            self.vault.header().meta().ok_or(Error::VaultNotInit)?;
        let vault_meta_blob = self
            .vault
            .decrypt(&current_private_key, vault_meta_aead)
            .await?;

        // Create new vault duplicated from the existing
        // vault header with zero secrets, this will inherit
        // the vault name, cipher etc.
        let new_header = self.vault.header().clone();
        let mut new_vault: Vault = new_header.into();

        // Initialize the new vault with the new passphrase
        // so that we create a new salt for the new passphrase.
        //
        // Must clear the existing salt so we can re-initialize.
        new_vault.header_mut().clear_salt();

        match &self.new_key {
            AccessKey::Password(password) => {
                new_vault.symmetric(password.clone(), self.seed).await?;
            }
            AccessKey::Identity(id) => {
                new_vault.asymmetric(id, vec![], true).await?;
            }
        }

        // Get a new secret key after we have initialized the new salt
        let new_private_key = self.new_private_key(&new_vault)?;

        // Encrypt the vault meta data using the new private key
        // and update the new vault header
        let vault_meta_aead = new_vault
            .encrypt(&new_private_key, &vault_meta_blob)
            .await?;
        new_vault.header_mut().set_meta(Some(vault_meta_aead));

        let mut event_log_events = Vec::new();

        let buffer = encode(&new_vault).await?;
        let create_vault = WriteEvent::CreateVault(Cow::Owned(buffer));
        event_log_events.push(create_vault);

        // Iterate the current vault and decrypt the secrets
        // inserting freshly encrypted content into the new vault
        for (id, VaultCommit(_, VaultEntry(meta_aead, secret_aead))) in
            self.vault.iter()
        {
            let meta_blob =
                self.vault.decrypt(&current_private_key, meta_aead).await?;
            let secret_blob = self
                .vault
                .decrypt(&current_private_key, secret_aead)
                .await?;

            let meta_aead =
                new_vault.encrypt(&new_private_key, &meta_blob).await?;
            let secret_aead =
                new_vault.encrypt(&new_private_key, &secret_blob).await?;

            // Need a new commit hash as the contents have changed
            let (commit, _) =
                Vault::commit_hash(&meta_aead, &secret_aead).await?;

            // Insert into the new vault preserving the secret identifiers
            let sync_event = new_vault
                .insert(*id, commit, VaultEntry(meta_aead, secret_aead))
                .await?;

            event_log_events.push(sync_event.into_owned());
        }

        event_log_events.sort();

        Ok((self.new_key, new_vault, event_log_events))
    }
}

#[cfg(test)]
mod test {
    use super::ChangePassword;
    use crate::{
        crypto::AccessKey,
        test_utils::*,
        vault::{Gatekeeper, VaultBuilder},
    };
    use anyhow::Result;

    #[tokio::test]
    async fn change_password() -> Result<()> {
        let (_, _, current_key) = mock_encryption_key()?;
        let mock_vault = VaultBuilder::new()
            .password(current_key.clone(), None)
            .await?;

        let mut keeper = Gatekeeper::new(mock_vault, None);
        keeper.unlock(current_key.clone().into()).await?;

        // Propagate some secrets
        let notes = vec![
            ("label1", "note1"),
            ("label2", "note2"),
            ("label3", "note3"),
        ];
        for item in notes {
            let (secret_meta, secret_value, _, _) =
                mock_secret_note(item.0, item.1).await?;
            keeper.create(secret_meta, secret_value).await?;
        }

        let expected_len = keeper.vault().len();
        assert_eq!(3, expected_len);

        let (_, _, new_key) = mock_encryption_key()?;

        let expected_passphrase = AccessKey::Password(new_key.clone());

        // Using an incorrect current passphrase should fail
        let bad_passphrase =
            AccessKey::Password(secrecy::Secret::new(String::from("oops")));
        assert!(ChangePassword::new(
            keeper.vault(),
            bad_passphrase,
            AccessKey::Password(new_key.clone()),
            None,
        )
        .build()
        .await
        .is_err());

        // Using a valid current passphrase should succeed
        let (new_key, new_vault, event_log_events) = ChangePassword::new(
            keeper.vault(),
            AccessKey::Password(current_key),
            AccessKey::Password(new_key),
            None,
        )
        .build()
        .await?;

        assert_eq!(expected_passphrase, new_key);
        assert_eq!(expected_len, new_vault.len());
        assert_eq!(expected_len + 1, event_log_events.len());

        Ok(())
    }
}