bitwarden_crypto/keys/
device_key.rs

1use super::{AsymmetricCryptoKey, PublicKeyEncryptionAlgorithm};
2use crate::{
3    error::Result, CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
4    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 = AsymmetricCryptoKey::from_der(&device_private_key)?;
69
70        let user_key: SymmetricCryptoKey =
71            protected_user_key.decapsulate_key_unsigned(&device_private_key)?;
72        Ok(user_key)
73    }
74
75    fn to_base64(&self) -> String {
76        self.0.to_base64()
77    }
78}
79
80impl TryFrom<String> for DeviceKey {
81    type Error = CryptoError;
82
83    fn try_from(value: String) -> Result<Self, Self::Error> {
84        SymmetricCryptoKey::try_from(value).map(DeviceKey)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::derive_symmetric_key;
92
93    #[test]
94    fn test_trust_device() {
95        let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
96
97        let result = DeviceKey::trust_device(&key).unwrap();
98
99        let device_key = DeviceKey::try_from(result.device_key).unwrap();
100        let decrypted = device_key
101            .decrypt_user_key(
102                result.protected_device_private_key,
103                result.protected_user_key,
104            )
105            .unwrap();
106
107        assert_eq!(key, decrypted);
108        assert_eq!(key, decrypted);
109    }
110
111    #[test]
112    fn test_decrypt_user_key() {
113        // Example keys from desktop app
114        let user_key: &mut [u8] = &mut [
115            109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, 212,
116            173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, 215, 21,
117            128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41, 102, 45, 183,
118            218, 106, 89, 254, 208, 251, 101, 130, 10,
119        ];
120        let user_key = SymmetricCryptoKey::try_from(user_key).unwrap();
121
122        let key_data: &mut [u8] = &mut [
123            114, 235, 60, 115, 172, 156, 203, 145, 195, 130, 215, 250, 88, 146, 215, 230, 12, 109,
124            245, 222, 54, 217, 255, 211, 221, 105, 230, 236, 65, 52, 209, 133, 76, 208, 113, 254,
125            194, 216, 156, 19, 230, 62, 32, 93, 87, 7, 144, 156, 117, 142, 250, 32, 182, 118, 187,
126            8, 247, 7, 203, 201, 65, 147, 206, 247,
127        ];
128        let device_key = DeviceKey(key_data.try_into().unwrap());
129
130        let protected_user_key: UnsignedSharedKey = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap();
131
132        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();
133
134        let decrypted = device_key
135            .decrypt_user_key(protected_device_private_key, protected_user_key)
136            .unwrap();
137
138        assert_eq!(decrypted, user_key);
139    }
140}