bitwarden_crypto/keys/
device_key.rs

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