bitwarden_crypto/keys/
device_key.rs

1use super::{AsymmetricCryptoKey, PublicKeyEncryptionAlgorithm};
2use crate::{
3    error::Result, CryptoError, EncString, KeyDecryptable, KeyEncryptable, Pkcs8PrivateKeyBytes,
4    SymmetricCryptoKey, UnsignedSharedKey,
5};
6
7/// Device Key
8///
9/// Encrypts the DevicePrivateKey
10/// Allows the device to decrypt the UserKey, via the DevicePrivateKey.
11#[derive(Debug)]
12pub struct DeviceKey(SymmetricCryptoKey);
13
14#[allow(missing_docs)]
15#[derive(Debug)]
16#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
17pub struct TrustDeviceResponse {
18    /// Base64 encoded device key
19    pub device_key: String,
20    /// UserKey encrypted with DevicePublicKey
21    pub protected_user_key: UnsignedSharedKey,
22    /// DevicePrivateKey encrypted with [DeviceKey]
23    pub protected_device_private_key: EncString,
24    /// DevicePublicKey encrypted with [UserKey](super::UserKey)
25    pub protected_device_public_key: EncString,
26}
27
28impl DeviceKey {
29    /// Generate a new device key
30    ///
31    /// Note: Input has to be a SymmetricCryptoKey instead of UserKey because that's what we get
32    /// from EncSettings.
33    pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result<TrustDeviceResponse> {
34        let device_key = DeviceKey(SymmetricCryptoKey::make_aes256_cbc_hmac_key());
35
36        let device_private_key =
37            AsymmetricCryptoKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
38
39        let protected_user_key = UnsignedSharedKey::encapsulate_key_unsigned(
40            user_key,
41            &device_private_key.to_public_key(),
42        )?;
43
44        let protected_device_public_key = device_private_key
45            .to_public_key()
46            .to_der()?
47            .encrypt_with_key(user_key)?;
48
49        let protected_device_private_key = device_private_key
50            .to_der()?
51            .encrypt_with_key(&device_key.0)?;
52
53        Ok(TrustDeviceResponse {
54            device_key: device_key.to_base64(),
55            protected_user_key,
56            protected_device_private_key,
57            protected_device_public_key,
58        })
59    }
60
61    /// Decrypt the user key using the device key
62    pub fn decrypt_user_key(
63        &self,
64        protected_device_private_key: EncString,
65        protected_user_key: UnsignedSharedKey,
66    ) -> Result<SymmetricCryptoKey> {
67        let device_private_key: Vec<u8> = protected_device_private_key.decrypt_with_key(&self.0)?;
68        let device_private_key = Pkcs8PrivateKeyBytes::from(device_private_key);
69        let device_private_key = AsymmetricCryptoKey::from_der(&device_private_key)?;
70
71        let user_key: SymmetricCryptoKey =
72            protected_user_key.decapsulate_key_unsigned(&device_private_key)?;
73        Ok(user_key)
74    }
75
76    fn to_base64(&self) -> String {
77        self.0.to_base64()
78    }
79}
80
81impl TryFrom<String> for DeviceKey {
82    type Error = CryptoError;
83
84    fn try_from(value: String) -> Result<Self, Self::Error> {
85        SymmetricCryptoKey::try_from(value).map(DeviceKey)
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::{derive_symmetric_key, BitwardenLegacyKeyBytes};
93
94    #[test]
95    fn test_trust_device() {
96        let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
97
98        let result = DeviceKey::trust_device(&key).unwrap();
99
100        let device_key = DeviceKey::try_from(result.device_key).unwrap();
101        let decrypted = device_key
102            .decrypt_user_key(
103                result.protected_device_private_key,
104                result.protected_user_key,
105            )
106            .unwrap();
107
108        assert_eq!(key, decrypted);
109        assert_eq!(key, decrypted);
110    }
111
112    #[test]
113    fn test_decrypt_user_key() {
114        // Example keys from desktop app
115        let user_key: &[u8] = &[
116            109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, 212,
117            173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, 215, 21,
118            128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41, 102, 45, 183,
119            218, 106, 89, 254, 208, 251, 101, 130, 10,
120        ];
121        let user_key =
122            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(user_key)).unwrap();
123
124        let key_data: &[u8] = &[
125            114, 235, 60, 115, 172, 156, 203, 145, 195, 130, 215, 250, 88, 146, 215, 230, 12, 109,
126            245, 222, 54, 217, 255, 211, 221, 105, 230, 236, 65, 52, 209, 133, 76, 208, 113, 254,
127            194, 216, 156, 19, 230, 62, 32, 93, 87, 7, 144, 156, 117, 142, 250, 32, 182, 118, 187,
128            8, 247, 7, 203, 201, 65, 147, 206, 247,
129        ];
130        let device_key = DeviceKey(
131            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key_data)).unwrap(),
132        );
133
134        let protected_user_key: UnsignedSharedKey = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap();
135
136        let protected_device_private_key: EncString = "2.GyQfUYWW6Byy4UV5icFLxg==|EMiU7OTF79N6tfv3+YUs5zJhBAgqv6sa5YCoPl6yAETh7Tfk+JmbeizxXFPj5Q1X/tcVpDZl/3fGcxtnIxg1YtvDFn7j8uPnoApOWhCKmwcvJSIkt+qvX3lELNBwZXozSiy7PbQ0JbCMe2d4MkimR5k8+lE9FB3208yYK7nOJhlrsUCnOekCYEU9/4NCMA8tz8SpITx/MN4JJ1TQ/KjPJYLt+3JNUxK47QlgREWQvyVzCRt7ZGtcgIJ/U1qycAWMpEg9NkuV8j5QRA1S7VBsA6qliJwys5+dmTuIOmOMwdKFZDc4ZvWoRkPp2TSJBu7L8sSAgU6mmDWac8iQ+9Ka/drdfwYLrH8GAZvURk79tSpRrT7+PAFe2QdUtliUIyiqkh8iJVjZube4hRnEsRuX9V9b+UdtAr6zAj7mugO/VAu5T9J38V79V2ohG3NtXysDeKLXpAlkhjllWXeq/wret2fD4WiwqEDj0G2A/PY3F3OziIgp0UKc00AfqrPq8OVK3A+aowwVqdYadgxyoVCKWJ8unJeAXG7MrMQ9tHpzF6COoaEy7Wwoc17qko33zazwLZbfAjB4oc8Ea26jRKnJZP56sVZAjOSQQMziAsA08MRaa/DQhgRea1+Ygba0gMft8Dww8anN2gQBveTZRBWyqXYgN3U0Ity5gNauT8RnFk9faqVFt2Qxnp0JgJ+PsqEt5Hn4avBRZQQ7o8VvPnxYLDKFe3I2m6HFYFWRhOGeDYxexIuaiF2iIAYFVUmnDuWpgnUiL4XJ3KHDsjkPzcV3z4D2Knr/El2VVXve8jhDjETfovmmN28+i2e29PXvKIymTskMFpFCQPc7wBY/Id7pmgb3SujKYNpkAS2sByDoRir0my49DDGfta0dENssJhFd3x+87fZbEj3cMiikg2pBwpTLgmfIUa5cVZU2s8JZ9wu7gaioYzvX+elHa3EHLcnEUoJTtSf9kjb+Nbq4ktMgYAO2wIC96t1LvmqK4Qn2cOdw5QNlRqALhqe5V31kyIcwRMK0AyIoOPhnSqtpYdFiR3LDTvZA8dU0vSsuchCwHNMeRUtKvdzN/tk+oeznyY/mpakUESN501lEKd/QFLtJZsDZTtNlcA8fU3kDtws4ZIMR0O5+PFmgQFSU8OMobf9ClUzy/wHTvYGyDuSwbOoPeS955QKkUKXCNMj33yrPr+ioHQ1BNwLX3VmMF4bNRBY/vr+CG0/EZi0Gwl0kyHGl0yWEtpQuu+/PaROJeOraWy5D1UoZZhY4n0zJZBt1eg3FZ2rhKv4gdUc50nZpeNWE8pIqZ6RQ7qPJuqfF1Z+G73iOSnLYCHDiiFmhD5ivf9IGkTAcWcBsQ/2wcSj9bFJr4DrKfsbQ4CkSWICWVn/W+InKkO6BTsBbYmvte5SvbaN+UOtiUSkHLBCCr8273VNgcB/hgtbUires3noxYZJxoczr+i7vdlEgQnWEKrpo0CifsFxGwYS3Yy2K79iwvDMaLPDf73zLSbuoUl6602F2Mzcjnals67f+gSpaDvWt7Kg9c/ZfGjq8oNxVaXJnX3gSDsO+fhwVAtnDApL+tL8cFfxGerW4KGi9/74woH+C3MMIViBtNnrpEuvxUW97Dg5nd40oGDeyi/q+8HdcxkneyFY=|JYdol19Yi+n1r7M+06EwK5JCi2s/CWqKui2Cy6hEb3k=".parse().unwrap();
137
138        let decrypted = device_key
139            .decrypt_user_key(protected_device_private_key, protected_user_key)
140            .unwrap();
141
142        assert_eq!(decrypted, user_key);
143    }
144}