bitwarden_core/auth/
pin.rs1use bitwarden_crypto::{
2 EncString, PinKey,
3 safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeNamespace},
4};
5use tracing::info;
6
7use crate::{
8 Client, NotAuthenticatedError,
9 auth::AuthValidateError,
10 client::{LoginMethod, UserLoginMethod},
11 key_management::SymmetricKeyId,
12};
13
14pub(crate) fn validate_pin(
15 client: &Client,
16 pin: String,
17 pin_protected_user_key: EncString,
18) -> Result<bool, AuthValidateError> {
19 let login_method = client
20 .internal
21 .get_login_method()
22 .ok_or(NotAuthenticatedError)?;
23
24 #[allow(irrefutable_let_patterns)]
25 let LoginMethod::User(login_method) = login_method.as_ref() else {
26 return Err(NotAuthenticatedError)?;
27 };
28
29 match login_method {
30 UserLoginMethod::Username { email, kdf, .. }
31 | UserLoginMethod::ApiKey { email, kdf, .. } => {
32 let key_store = client.internal.get_key_store();
33 let ctx = key_store.context();
34 #[allow(deprecated)]
36 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
37
38 let pin_key = PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?;
39
40 let Ok(decrypted_key) = pin_key.decrypt_user_key(pin_protected_user_key) else {
41 return Ok(false);
42 };
43
44 Ok(*user_key == decrypted_key)
45 }
46 }
47}
48
49pub(crate) fn validate_pin_protected_user_key_envelope(
51 client: &Client,
52 pin: String,
53 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
54) -> bool {
55 let key_store = client.internal.get_key_store();
56 let mut ctx = key_store.context();
57
58 if let Err(e) = pin_protected_user_key_envelope.unseal(
59 pin.as_str(),
60 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
61 &mut ctx,
62 ) {
63 info!("Validating PIN-protected user key envelope failed: {e:?}");
64 false
65 } else {
66 true
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use std::num::NonZeroU32;
73
74 use bitwarden_crypto::{Kdf, safe::PasswordProtectedKeyEnvelopeNamespace};
75
76 use super::*;
77 use crate::{
78 client::{Client, LoginMethod, UserLoginMethod},
79 key_management::{
80 MasterPasswordUnlockData, account_cryptographic_state::WrappedAccountCryptographicState,
81 },
82 };
83
84 fn init_client() -> Client {
85 let client = Client::new(None);
86
87 let password = "asdfasdfasdf";
88 let email = "[email protected]";
89 let kdf = Kdf::PBKDF2 {
90 iterations: NonZeroU32::new(600_000).unwrap(),
91 };
92
93 client
94 .internal
95 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
96 email: email.to_string(),
97 kdf: kdf.clone(),
98 client_id: "1".to_string(),
99 }));
100
101 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
102 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();
103
104 client
105 .internal
106 .initialize_user_crypto_master_password_unlock(
107 password.to_string(),
108 MasterPasswordUnlockData {
109 kdf,
110 master_key_wrapped_user_key: user_key.parse().unwrap(),
111 salt: email.to_string(),
112 },
113 WrappedAccountCryptographicState::V1 { private_key },
114 &None,
115 )
116 .unwrap();
117
118 client
119 }
120
121 #[test]
122 fn test_validate_valid_pin() {
123 let pin = "1234".to_string();
124 let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAzPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ="
125 .parse()
126 .unwrap();
127
128 let client = init_client();
129 assert!(validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap());
130 }
131
132 #[test]
133 fn test_validate_invalid_pin() {
134 let pin = "1234".to_string();
135 let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAyPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ="
136 .parse()
137 .unwrap();
138
139 let client = init_client();
140 assert!(!validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap());
141 }
142
143 #[test]
144 fn test_validate_pin_protected_user_key_envelope_valid_pin() {
145 let pin = "1234";
146 let client = init_client();
147
148 let key_store = client.internal.get_key_store();
150 let ctx = key_store.context();
151 let envelope = PasswordProtectedKeyEnvelope::seal(
152 SymmetricKeyId::User,
153 pin,
154 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
155 &ctx,
156 )
157 .unwrap();
158
159 let result = validate_pin_protected_user_key_envelope(&client, pin.to_string(), envelope);
161 assert!(result);
162 }
163
164 #[test]
165 fn test_validate_pin_protected_user_key_envelope_invalid_pin() {
166 let correct_pin = "1234";
167 let wrong_pin = "5678";
168 let client = init_client();
169
170 let key_store = client.internal.get_key_store();
172 let ctx = key_store.context();
173 let envelope = PasswordProtectedKeyEnvelope::seal(
174 SymmetricKeyId::User,
175 correct_pin,
176 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
177 &ctx,
178 )
179 .unwrap();
180
181 let result =
183 validate_pin_protected_user_key_envelope(&client, wrong_pin.to_string(), envelope);
184 assert!(!result);
185 }
186
187 #[test]
188 fn test_validate_pin_protected_user_key_malformed_envelope() {
189 let pin = "1234";
190
191 let client = init_client();
192
193 let key_store = client.internal.get_key_store();
195 let ctx = key_store.context();
196 let envelope = PasswordProtectedKeyEnvelope::seal(
197 SymmetricKeyId::User,
198 pin,
199 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
200 &ctx,
201 )
202 .unwrap();
203
204 let mut envelope_bytes: Vec<u8> = (&envelope).into();
205 envelope_bytes[60] ^= 0xFF;
207
208 let envelope: PasswordProtectedKeyEnvelope =
209 PasswordProtectedKeyEnvelope::try_from(&envelope_bytes).unwrap();
210
211 let client = Client::new(None);
212
213 let result = validate_pin_protected_user_key_envelope(&client, pin.to_string(), envelope);
215 assert!(!result);
216 }
217}