Skip to main content

bitwarden_core/auth/password/
validate.rs

1use bitwarden_crypto::{HashPurpose, MasterKey};
2use bitwarden_encoding::B64;
3
4use crate::{
5    Client, NotAuthenticatedError, WrongPasswordError,
6    auth::{AuthValidateError, password::determine_password_hash},
7    client::UserLoginMethod,
8};
9
10/// Validate if the provided password matches the password hash stored in the client.
11pub(crate) async fn validate_password(
12    client: &Client,
13    password: String,
14    password_hash: B64,
15) -> Result<bool, AuthValidateError> {
16    let login_method = client
17        .internal
18        .get_login_method()
19        .await
20        .ok_or(NotAuthenticatedError)?;
21
22    match login_method {
23        UserLoginMethod::Username { email, kdf, .. }
24        | UserLoginMethod::ApiKey { email, kdf, .. } => {
25            let hash =
26                determine_password_hash(&email, &kdf, &password, HashPurpose::LocalAuthorization)?;
27
28            Ok(hash == password_hash)
29        }
30    }
31}
32
33pub(crate) async fn validate_password_user_key(
34    client: &Client,
35    password: String,
36    encrypted_user_key: String,
37) -> Result<B64, AuthValidateError> {
38    use crate::key_management::SymmetricKeySlotId;
39
40    let login_method = client
41        .internal
42        .get_login_method()
43        .await
44        .ok_or(NotAuthenticatedError)?;
45
46    match login_method {
47        UserLoginMethod::Username { email, kdf, .. }
48        | UserLoginMethod::ApiKey { email, kdf, .. } => {
49            let master_key = MasterKey::derive(&password, &email, &kdf)?;
50            let user_key = master_key
51                .decrypt_user_key(encrypted_user_key.parse()?)
52                .map_err(|_| WrongPasswordError)?;
53
54            let key_store = client.internal.get_key_store();
55            let ctx = key_store.context();
56            // FIXME: [PM-18099] Once MasterKey deals with KeySlotIds, this should be updated
57            #[allow(deprecated)]
58            let existing_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
59
60            if user_key != *existing_key {
61                return Err(AuthValidateError::WrongUserKey);
62            }
63
64            Ok(master_key
65                .derive_master_key_hash(password.as_bytes(), HashPurpose::LocalAuthorization))
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use bitwarden_crypto::{EncString, Kdf};
73
74    use crate::{
75        auth::password::{validate::validate_password_user_key, validate_password},
76        key_management::{
77            MasterPasswordUnlockData, account_cryptographic_state::WrappedAccountCryptographicState,
78        },
79    };
80
81    #[tokio::test]
82    async fn test_validate_password() {
83        use std::num::NonZeroU32;
84
85        use crate::client::{Client, LoginMethod, UserLoginMethod};
86
87        let client = Client::new(None);
88        client
89            .internal
90            .set_login_method(LoginMethod::User(UserLoginMethod::Username {
91                email: "[email protected]".to_string(),
92                kdf: Kdf::PBKDF2 {
93                    iterations: NonZeroU32::new(100_000).unwrap(),
94                },
95                client_id: "1".to_string(),
96            }))
97            .await;
98
99        let password = "password123".to_string();
100        let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU="
101            .parse()
102            .unwrap();
103
104        let result = validate_password(&client, password, password_hash).await;
105
106        assert!(result.unwrap());
107    }
108
109    #[tokio::test]
110    async fn test_validate_password_user_key() {
111        use std::num::NonZeroU32;
112
113        use bitwarden_crypto::Kdf;
114
115        use crate::client::{Client, LoginMethod, UserLoginMethod};
116
117        let client = Client::new(None);
118
119        let password = "asdfasdfasdf";
120        let email = "[email protected]";
121        let kdf = Kdf::PBKDF2 {
122            iterations: NonZeroU32::new(600_000).unwrap(),
123        };
124
125        client
126            .internal
127            .set_login_method(LoginMethod::User(UserLoginMethod::Username {
128                email: email.to_string(),
129                kdf: kdf.clone(),
130                client_id: "1".to_string(),
131            }))
132            .await;
133
134        let user_key: EncString = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
135        let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap();
136
137        client
138            .internal
139            .initialize_user_crypto_master_password_unlock(
140                password.to_string(),
141                MasterPasswordUnlockData {
142                    kdf,
143                    master_key_wrapped_user_key: user_key.clone(),
144                    salt: email.to_string(),
145                    contained_key_id: None,
146                },
147                WrappedAccountCryptographicState::V1 { private_key },
148                &None,
149            )
150            .unwrap();
151
152        let result =
153            validate_password_user_key(&client, "asdfasdfasdf".to_owned(), user_key.to_string())
154                .await
155                .unwrap();
156
157        assert_eq!(
158            result.to_string(),
159            "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="
160        );
161        assert!(
162            validate_password(&client, password.to_owned(), result)
163                .await
164                .unwrap()
165        )
166    }
167
168    #[cfg(feature = "internal")]
169    #[tokio::test]
170    async fn test_validate_password_user_key_wrong_password() {
171        use std::num::NonZeroU32;
172
173        use bitwarden_crypto::Kdf;
174
175        use crate::client::{Client, LoginMethod, UserLoginMethod};
176
177        let client = Client::new(None);
178
179        let password = "asdfasdfasdf";
180        let email = "[email protected]";
181        let kdf = Kdf::PBKDF2 {
182            iterations: NonZeroU32::new(600_000).unwrap(),
183        };
184
185        client
186            .internal
187            .set_login_method(LoginMethod::User(UserLoginMethod::Username {
188                email: email.to_string(),
189                kdf: kdf.clone(),
190                client_id: "1".to_string(),
191            }))
192            .await;
193
194        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
195        let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap();
196
197        client
198            .internal
199            .initialize_user_crypto_master_password_unlock(
200                password.to_string(),
201                MasterPasswordUnlockData {
202                    kdf,
203                    master_key_wrapped_user_key: user_key.parse().unwrap(),
204                    salt: email.to_string(),
205                    contained_key_id: None,
206                },
207                WrappedAccountCryptographicState::V1 { private_key },
208                &None,
209            )
210            .unwrap();
211
212        let result =
213            validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.to_string())
214                .await
215                .unwrap();
216
217        assert_eq!(
218            result.to_string(),
219            "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="
220        );
221        assert!(
222            validate_password(&client, "asdfasdfasdf".to_string(), result)
223                .await
224                .unwrap()
225        )
226    }
227}