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