bitwarden_core/auth/password/
validate.rs1use 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
10pub(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 #[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 client::internal::UserKeyState,
89 };
90
91 #[test]
92 fn test_validate_password() {
93 use std::num::NonZeroU32;
94
95 use crate::client::{Client, LoginMethod, UserLoginMethod};
96
97 let client = Client::new(None);
98 client
99 .internal
100 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
101 email: "[email protected]".to_string(),
102 kdf: Kdf::PBKDF2 {
103 iterations: NonZeroU32::new(100_000).unwrap(),
104 },
105 client_id: "1".to_string(),
106 }));
107
108 let password = "password123".to_string();
109 let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU="
110 .parse()
111 .unwrap();
112
113 let result = validate_password(&client, password, password_hash);
114
115 assert!(result.unwrap());
116 }
117
118 #[test]
119 fn test_validate_password_user_key() {
120 use std::num::NonZeroU32;
121
122 use bitwarden_crypto::{Kdf, MasterKey};
123
124 use crate::client::{Client, LoginMethod, UserLoginMethod};
125
126 let client = Client::new(None);
127
128 let password = "asdfasdfasdf";
129 let email = "[email protected]";
130 let kdf = Kdf::PBKDF2 {
131 iterations: NonZeroU32::new(600_000).unwrap(),
132 };
133
134 client
135 .internal
136 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
137 email: email.to_string(),
138 kdf: kdf.clone(),
139 client_id: "1".to_string(),
140 }));
141
142 let master_key = MasterKey::derive(password, email, &kdf).unwrap();
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_key(
150 master_key,
151 user_key.clone(),
152 UserKeyState {
153 private_key,
154 signing_key: None,
155 security_state: None,
156 },
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, MasterKey};
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 master_key = MasterKey::derive(password, email, &kdf).unwrap();
197
198 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
199 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();
200
201 client
202 .internal
203 .initialize_user_crypto_master_key(
204 master_key,
205 user_key.parse().unwrap(),
206 UserKeyState {
207 private_key,
208 signing_key: None,
209 security_state: None,
210 },
211 )
212 .unwrap();
213
214 let result =
215 validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.to_string())
216 .unwrap();
217
218 assert_eq!(
219 result.to_string(),
220 "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="
221 );
222 assert!(validate_password(&client, "asdfasdfasdf".to_string(), result).unwrap())
223 }
224}