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