bitwarden_core/auth/
pin.rs

1use bitwarden_crypto::{EncString, PinKey};
2
3use crate::{
4    auth::AuthValidateError,
5    client::{LoginMethod, UserLoginMethod},
6    key_management::SymmetricKeyId,
7    Client, NotAuthenticatedError,
8};
9
10pub(crate) fn validate_pin(
11    client: &Client,
12    pin: String,
13    pin_protected_user_key: EncString,
14) -> Result<bool, AuthValidateError> {
15    let login_method = client
16        .internal
17        .get_login_method()
18        .ok_or(NotAuthenticatedError)?;
19
20    #[allow(irrefutable_let_patterns)]
21    let LoginMethod::User(login_method) = login_method.as_ref() else {
22        return Err(NotAuthenticatedError)?;
23    };
24
25    match login_method {
26        UserLoginMethod::Username { email, kdf, .. }
27        | UserLoginMethod::ApiKey { email, kdf, .. } => {
28            let key_store = client.internal.get_key_store();
29            let ctx = key_store.context();
30            // FIXME: [PM-18099] Once PinKey deals with KeyIds, this should be updated
31            #[allow(deprecated)]
32            let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
33
34            let pin_key = PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?;
35
36            let Ok(decrypted_key) = pin_key.decrypt_user_key(pin_protected_user_key) else {
37                return Ok(false);
38            };
39
40            Ok(*user_key == decrypted_key)
41        }
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use std::num::NonZeroU32;
48
49    use bitwarden_crypto::{Kdf, MasterKey};
50
51    use super::*;
52    use crate::client::{Client, LoginMethod, UserLoginMethod};
53
54    fn init_client() -> Client {
55        let client = Client::new(None);
56
57        let password = "asdfasdfasdf";
58        let email = "[email protected]";
59        let kdf = Kdf::PBKDF2 {
60            iterations: NonZeroU32::new(600_000).unwrap(),
61        };
62
63        client
64            .internal
65            .set_login_method(LoginMethod::User(UserLoginMethod::Username {
66                email: email.to_string(),
67                kdf: kdf.clone(),
68                client_id: "1".to_string(),
69            }));
70
71        let master_key = MasterKey::derive(password, email, &kdf).unwrap();
72
73        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
74        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();
75
76        client
77            .internal
78            .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key)
79            .unwrap();
80
81        client
82    }
83
84    #[test]
85    fn test_validate_valid_pin() {
86        let pin = "1234".to_string();
87        let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAzPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ="
88        .parse()
89        .unwrap();
90
91        let client = init_client();
92        assert!(validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap());
93    }
94
95    #[test]
96    fn test_validate_invalid_pin() {
97        let pin = "1234".to_string();
98        let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAyPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ="
99        .parse()
100        .unwrap();
101
102        let client = init_client();
103        assert!(!validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap());
104    }
105}