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#[bitwarden_ffi::wasm_record]
21#[derive(Serialize, Deserialize)]
22pub struct TrustDeviceResponse {
23 pub device_key: B64,
25 pub protected_user_key: UnsignedSharedKey,
27 pub protected_device_private_key: EncString,
29 pub protected_device_public_key: EncString,
31}
32
33impl DeviceKey {
34 pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result<TrustDeviceResponse> {
39 let device_key = DeviceKey(SymmetricCryptoKey::make_aes256_cbc_hmac_key());
40
41 let device_private_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
42
43 #[expect(deprecated)]
44 let protected_user_key = UnsignedSharedKey::encapsulate_key_unsigned(
45 user_key,
46 &device_private_key.to_public_key(),
47 )?;
48
49 let protected_device_public_key = device_private_key
50 .to_public_key()
51 .to_der()?
52 .encrypt_with_key(user_key)?;
53
54 let protected_device_private_key = device_private_key
55 .to_der()?
56 .encrypt_with_key(&device_key.0)?;
57
58 Ok(TrustDeviceResponse {
59 device_key: device_key.to_base64(),
60 protected_user_key,
61 protected_device_private_key,
62 protected_device_public_key,
63 })
64 }
65
66 pub fn decrypt_user_key(
68 &self,
69 protected_device_private_key: EncString,
70 protected_user_key: UnsignedSharedKey,
71 ) -> Result<SymmetricCryptoKey> {
72 let device_private_key: Vec<u8> = protected_device_private_key.decrypt_with_key(&self.0)?;
73 let device_private_key = Pkcs8PrivateKeyBytes::from(device_private_key);
74 let device_private_key = PrivateKey::from_der(&device_private_key)?;
75
76 #[expect(deprecated)]
77 let user_key: SymmetricCryptoKey =
78 protected_user_key.decapsulate_key_unsigned(&device_private_key)?;
79 Ok(user_key)
80 }
81
82 fn to_base64(&self) -> B64 {
83 self.0.to_base64()
84 }
85}
86
87impl TryFrom<String> for DeviceKey {
88 type Error = CryptoError;
89
90 fn try_from(value: String) -> Result<Self, Self::Error> {
91 SymmetricCryptoKey::try_from(value).map(DeviceKey)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::{BitwardenLegacyKeyBytes, derive_symmetric_key};
99
100 #[test]
101 fn test_trust_device() {
102 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
103
104 let result = DeviceKey::trust_device(&key).unwrap();
105
106 let device_key = DeviceKey::try_from(result.device_key.to_string()).unwrap();
107 let decrypted = device_key
108 .decrypt_user_key(
109 result.protected_device_private_key,
110 result.protected_user_key,
111 )
112 .unwrap();
113
114 assert_eq!(key, decrypted);
115 assert_eq!(key, decrypted);
116 }
117
118 #[test]
119 fn test_decrypt_user_key() {
120 let user_key: &[u8] = &[
122 109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, 212,
123 173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, 215, 21,
124 128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41, 102, 45, 183,
125 218, 106, 89, 254, 208, 251, 101, 130, 10,
126 ];
127 let user_key =
128 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(user_key)).unwrap();
129
130 let key_data: &[u8] = &[
131 114, 235, 60, 115, 172, 156, 203, 145, 195, 130, 215, 250, 88, 146, 215, 230, 12, 109,
132 245, 222, 54, 217, 255, 211, 221, 105, 230, 236, 65, 52, 209, 133, 76, 208, 113, 254,
133 194, 216, 156, 19, 230, 62, 32, 93, 87, 7, 144, 156, 117, 142, 250, 32, 182, 118, 187,
134 8, 247, 7, 203, 201, 65, 147, 206, 247,
135 ];
136 let device_key = DeviceKey(
137 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key_data)).unwrap(),
138 );
139
140 let protected_user_key: UnsignedSharedKey = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap();
141
142 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();
143
144 let decrypted = device_key
145 .decrypt_user_key(protected_device_private_key, protected_user_key)
146 .unwrap();
147
148 assert_eq!(decrypted, user_key);
149 }
150}