bitwarden_importers/importers/onepassword/access/
opdata.rs1use aes_gcm::{
7 Aes256Gcm, KeyInit,
8 aead::{Aead, Nonce, Payload},
9};
10use data_encoding::BASE64URL_NOPAD;
11use zeroize::Zeroize;
12
13use super::{error::OnePasswordError, wire::EncryptedEnvelope};
14
15const ENCRYPTION_SCHEME: &str = "A256GCM";
16const CONTAINER_TYPE: &str = "b5+jwk+json";
17
18pub(super) fn encrypt(
20 key: &[u8],
21 plaintext: &[u8],
22 iv: &[u8],
23 aad: &[u8],
24) -> Result<Vec<u8>, OnePasswordError> {
25 let cipher = Aes256Gcm::new_from_slice(key)
26 .map_err(|_| OnePasswordError::Internal("the key must be 32 bytes long".into()))?;
27 let nonce = Nonce::<Aes256Gcm>::try_from(iv)
28 .map_err(|_| OnePasswordError::Internal("the iv must be 12 bytes long".into()))?;
29 cipher
30 .encrypt(
31 &nonce,
32 Payload {
33 msg: plaintext,
34 aad,
35 },
36 )
37 .map_err(|_| OnePasswordError::Internal("AES-GCM encryption failed".into()))
38}
39
40pub(super) fn decrypt(
42 key: &[u8],
43 ciphertext: &[u8],
44 iv: &[u8],
45 aad: &[u8],
46) -> Result<Vec<u8>, OnePasswordError> {
47 if ciphertext.len() < 16 {
48 return Err(OnePasswordError::Internal(
49 "the ciphertext must be at least 16 bytes long".into(),
50 ));
51 }
52 let cipher = Aes256Gcm::new_from_slice(key)
53 .map_err(|_| OnePasswordError::Internal("the key must be 32 bytes long".into()))?;
54 let nonce = Nonce::<Aes256Gcm>::try_from(iv)
55 .map_err(|_| OnePasswordError::Internal("the iv must be 12 bytes long".into()))?;
56 cipher
57 .decrypt(
58 &nonce,
59 Payload {
60 msg: ciphertext,
61 aad,
62 },
63 )
64 .map_err(|_| OnePasswordError::Internal("the auth tag doesn't match".into()))
65}
66
67#[derive(Debug)]
71pub(super) struct Encrypted {
72 pub key_id: String,
73 pub scheme: String,
74 pub iv: Vec<u8>,
75 pub ciphertext: Vec<u8>,
76}
77
78impl Encrypted {
79 pub(super) fn parse(envelope: &EncryptedEnvelope) -> Result<Encrypted, OnePasswordError> {
81 Ok(Encrypted {
82 key_id: envelope.kid.clone(),
83 scheme: envelope.enc.clone(),
84 iv: match &envelope.iv {
85 Some(iv) => decode64_loose(iv)?,
86 None => Vec::new(),
87 },
88 ciphertext: decode64_loose(&envelope.data)?,
89 })
90 }
91}
92
93pub(super) struct AesKey {
95 pub id: String,
96 pub key: Vec<u8>,
97}
98
99impl Drop for AesKey {
100 fn drop(&mut self) {
101 self.key.zeroize();
102 }
103}
104
105impl AesKey {
106 pub(super) fn new(id: impl Into<String>, key: Vec<u8>) -> AesKey {
107 AesKey { id: id.into(), key }
108 }
109
110 pub(super) fn encrypt(
113 &self,
114 plaintext: &[u8],
115 iv: &[u8],
116 ) -> Result<EncryptedEnvelope, OnePasswordError> {
117 let ciphertext = encrypt(&self.key, plaintext, iv, &[])?;
118 Ok(EncryptedEnvelope {
119 kid: self.id.clone(),
120 enc: ENCRYPTION_SCHEME.to_string(),
121 cty: CONTAINER_TYPE.to_string(),
122 iv: Some(BASE64URL_NOPAD.encode(iv)),
123 data: BASE64URL_NOPAD.encode(&ciphertext),
124 })
125 }
126
127 pub(super) fn decrypt(&self, encrypted: &Encrypted) -> Result<Vec<u8>, OnePasswordError> {
129 if encrypted.key_id != self.id {
130 return Err(OnePasswordError::Internal("mismatching key id".into()));
131 }
132 if encrypted.scheme != ENCRYPTION_SCHEME {
133 return Err(OnePasswordError::Internal(format!(
134 "invalid encryption scheme '{}', expected '{ENCRYPTION_SCHEME}'",
135 encrypted.scheme
136 )));
137 }
138 decrypt(&self.key, &encrypted.ciphertext, &encrypted.iv, &[])
139 }
140}
141
142pub(super) fn decode64_loose(s: &str) -> Result<Vec<u8>, OnePasswordError> {
144 let normalized: String = s
145 .trim_end_matches('=')
146 .chars()
147 .map(|c| match c {
148 '-' => '+',
149 '_' => '/',
150 other => other,
151 })
152 .collect();
153 data_encoding::BASE64_NOPAD
154 .decode(normalized.as_bytes())
155 .map_err(|_| OnePasswordError::Parse)
156}
157
158#[cfg(test)]
159mod tests {
160 use data_encoding::HEXLOWER;
161
162 use super::*;
163
164 fn hex(s: &str) -> Vec<u8> {
165 HEXLOWER.decode(s.as_bytes()).expect("valid hex")
166 }
167
168 struct Vector {
171 key: &'static str,
172 plaintext: &'static str,
173 iv: &'static str,
174 adata: &'static str,
175 ciphertext: &'static str,
176 tag: &'static str,
177 }
178
179 const VECTORS: &[Vector] = &[
180 Vector {
181 key: "e3c08a8f06c6e3ad95a70557b23f75483ce33021a9c72b7025666204c69c0b72",
182 plaintext: "",
183 iv: "12153524c0895e81b2c28465",
184 adata: "d609b1f056637a0d46df998d88e5222ab2c2846512153524c0895e8108000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340001",
185 ciphertext: "",
186 tag: "2f0bc5af409e06d609ea8b7d0fa5ea50",
187 },
188 Vector {
189 key: "e3c08a8f06c6e3ad95a70557b23f75483ce33021a9c72b7025666204c69c0b72",
190 plaintext: "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a0002",
191 iv: "12153524c0895e81b2c28465",
192 adata: "d609b1f056637a0d46df998d88e52e00b2c2846512153524c0895e81",
193 ciphertext: "e2006eb42f5277022d9b19925bc419d7a592666c925fe2ef718eb4e308efeaa7c5273b394118860a5be2a97f56ab7836",
194 tag: "5ca597cdbb3edb8d1a1151ea0af7b436",
195 },
196 Vector {
197 key: "691d3ee909d7f54167fd1ca0b5d769081f2bde1aee655fdbab80bd5295ae6be7",
198 plaintext: "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340004",
199 iv: "f0761e8dcd3d000176d457ed",
200 adata: "e20106d7cd0df0761e8dcd3d88e54c2a76d457ed",
201 ciphertext: "c1623f55730c93533097addad25664966125352b43adacbd61c5ef3ac90b5bee929ce4630ea79f6ce519",
202 tag: "12af39c2d1fdc2051f8b7b3c9d397ef2",
203 },
204 ];
205
206 #[test]
207 fn encrypt_returns_ciphertext() {
208 for v in VECTORS {
209 let out = encrypt(&hex(v.key), &hex(v.plaintext), &hex(v.iv), &hex(v.adata))
210 .expect("encrypt succeeds");
211 assert_eq!(out, hex(&format!("{}{}", v.ciphertext, v.tag)));
212 }
213 }
214
215 #[test]
216 fn decrypt_returns_plaintext() {
217 for v in VECTORS {
218 let ciphertext = hex(&format!("{}{}", v.ciphertext, v.tag));
219 let out = decrypt(&hex(v.key), &ciphertext, &hex(v.iv), &hex(v.adata))
220 .expect("decrypt succeeds");
221 assert_eq!(out, hex(v.plaintext));
222 }
223 }
224
225 #[test]
226 fn decrypt_throws_on_modified_ciphertext() {
227 let v = &VECTORS[1];
228 let mut ciphertext = hex(&format!("{}{}", v.ciphertext, v.tag));
229 ciphertext[0] ^= 1;
230 let err =
231 decrypt(&hex(v.key), &ciphertext, &hex(v.iv), &hex(v.adata)).expect_err("tampered");
232 assert!(err.to_string().contains("auth tag"));
233 }
234
235 #[test]
236 fn rejects_invalid_lengths() {
237 let msg = |r: Result<Vec<u8>, OnePasswordError>| r.expect_err("invalid").to_string();
238 assert!(msg(encrypt(&[0; 13], &[0; 16], &[0; 12], &[])).contains("key must"));
239 assert!(msg(encrypt(&[0; 32], &[0; 16], &[0; 13], &[])).contains("iv must"));
240 assert!(msg(decrypt(&[0; 32], &[0; 13], &[0; 12], &[])).contains("ciphertext must"));
241 assert!(msg(decrypt(&[0; 13], &[0; 16], &[0; 12], &[])).contains("key must"));
242 assert!(msg(decrypt(&[0; 32], &[0; 16], &[0; 13], &[])).contains("iv must"));
243 }
244
245 #[test]
246 fn decrypts_opdata_envelope() {
247 let master_key = hex("44c38e8fedb84a1ab5ba74ed98dde931f6500ae39c1d9c85e20a7268ab2074f0");
248 let key = AesKey::new("mp", master_key);
249
250 let envelope: EncryptedEnvelope =
251 serde_json::from_str(include_str!("fixtures/encrypted-aes-key.json"))
252 .expect("valid fixture");
253 let encrypted = Encrypted::parse(&envelope).expect("decodes envelope");
254
255 let plaintext = String::from_utf8(key.decrypt(&encrypted).expect("decrypts"))
256 .expect("plaintext is utf8");
257 assert!(plaintext.contains("szerdhg2ww2ahjo4ilz57x7cce"));
258 }
259}