bitwarden_crypto/keys/
device_key.rs

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