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