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