bitwarden_core/auth/password/
validate.rs

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