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