bitwarden_crypto/keys/
device_key.rs

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