Skip to main content

bitwarden_core/auth/
pin.rs

1use 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::SymmetricKeyId,
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            // FIXME: [PM-18099] Once PinKey deals with KeyIds, this should be updated
29            #[allow(deprecated)]
30            let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::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
43/// Validates a PIN-protected user key envelope by attempting to unseal it with the provided PIN.
44pub(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                },
108                WrappedAccountCryptographicState::V1 { private_key },
109                &None,
110            )
111            .unwrap();
112
113        client
114    }
115
116    #[tokio::test]
117    async fn test_validate_valid_pin() {
118        let pin = "1234".to_string();
119        let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAzPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ="
120        .parse()
121        .unwrap();
122
123        let client = init_client().await;
124        assert!(
125            validate_pin(&client, pin.clone(), pin_protected_user_key)
126                .await
127                .unwrap()
128        );
129    }
130
131    #[tokio::test]
132    async fn test_validate_invalid_pin() {
133        let pin = "1234".to_string();
134        let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAyPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ="
135        .parse()
136        .unwrap();
137
138        let client = init_client().await;
139        assert!(
140            !validate_pin(&client, pin.clone(), pin_protected_user_key)
141                .await
142                .unwrap()
143        );
144    }
145
146    #[tokio::test]
147    async fn test_validate_pin_protected_user_key_envelope_valid_pin() {
148        let pin = "1234";
149        let client = init_client().await;
150
151        // Create a PIN-protected envelope from the user key
152        let key_store = client.internal.get_key_store();
153        let ctx = key_store.context();
154        let envelope = PasswordProtectedKeyEnvelope::seal(
155            SymmetricKeyId::User,
156            pin,
157            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
158            &ctx,
159        )
160        .unwrap();
161
162        // Validate with the correct PIN
163        let result = validate_pin_protected_user_key_envelope(&client, pin.to_string(), envelope);
164        assert!(result);
165    }
166
167    #[tokio::test]
168    async fn test_validate_pin_protected_user_key_envelope_invalid_pin() {
169        let correct_pin = "1234";
170        let wrong_pin = "5678";
171        let client = init_client().await;
172
173        // Create a PIN-protected envelope with the correct PIN
174        let key_store = client.internal.get_key_store();
175        let ctx = key_store.context();
176        let envelope = PasswordProtectedKeyEnvelope::seal(
177            SymmetricKeyId::User,
178            correct_pin,
179            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
180            &ctx,
181        )
182        .unwrap();
183
184        // Validate with the wrong PIN
185        let result =
186            validate_pin_protected_user_key_envelope(&client, wrong_pin.to_string(), envelope);
187        assert!(!result);
188    }
189
190    #[tokio::test]
191    async fn test_validate_pin_protected_user_key_malformed_envelope() {
192        let pin = "1234";
193
194        let client = init_client().await;
195
196        // Create a PIN-protected envelope with the correct PIN
197        let key_store = client.internal.get_key_store();
198        let ctx = key_store.context();
199        let envelope = PasswordProtectedKeyEnvelope::seal(
200            SymmetricKeyId::User,
201            pin,
202            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
203            &ctx,
204        )
205        .unwrap();
206
207        let mut envelope_bytes: Vec<u8> = (&envelope).into();
208        // Corrupt some bytes
209        envelope_bytes[60] ^= 0xFF;
210
211        let envelope: PasswordProtectedKeyEnvelope =
212            PasswordProtectedKeyEnvelope::try_from(&envelope_bytes).unwrap();
213
214        let client = Client::new(None);
215
216        // Validate should fail because no user key is present in this client
217        let result = validate_pin_protected_user_key_envelope(&client, pin.to_string(), envelope);
218        assert!(!result);
219    }
220}