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