1use aes::cipher::block_padding::NoPadding;
9use aes::cipher::{BlockDecryptMut, KeyInit, KeyIvInit};
10use aes::Aes256;
11use byteorder::{BigEndian, ByteOrder};
12
13use crate::innodb::constants::*;
14use crate::innodb::encryption::EncryptionInfo;
15use crate::innodb::keyring::Keyring;
16use crate::innodb::page_types::PageType;
17use crate::IdbError;
18
19type Aes256CbcDec = cbc::Decryptor<Aes256>;
20type Aes256EcbDec = ecb::Decryptor<Aes256>;
21
22#[derive(Debug)]
24pub struct DecryptionContext {
25 tablespace_key: [u8; 32],
27 tablespace_iv: [u8; 32],
29}
30
31impl DecryptionContext {
32 pub fn from_encryption_info(
38 info: &EncryptionInfo,
39 keyring: &Keyring,
40 ) -> Result<Self, IdbError> {
41 let master_key = keyring
42 .find_innodb_master_key(&info.server_uuid, info.master_key_id)
43 .ok_or_else(|| {
44 IdbError::Parse(format!(
45 "Master key not found in keyring: INNODBKey-{}-{}",
46 info.server_uuid, info.master_key_id
47 ))
48 })?;
49
50 if master_key.len() != 32 {
51 return Err(IdbError::Parse(format!(
52 "Master key has wrong length: expected 32, got {}",
53 master_key.len()
54 )));
55 }
56
57 let mut decrypted = info.encrypted_key_iv;
59 let decryptor = Aes256EcbDec::new_from_slice(master_key)
60 .map_err(|e| IdbError::Parse(format!("AES-256-ECB init failed: {}", e)))?;
61 decryptor
62 .decrypt_padded_mut::<NoPadding>(&mut decrypted)
63 .map_err(|e| IdbError::Parse(format!("AES-256-ECB decrypt failed: {}", e)))?;
64
65 let computed_crc = crc32c::crc32c(&decrypted);
67 if computed_crc != info.checksum {
68 return Err(IdbError::Parse(format!(
69 "Failed to decrypt tablespace key: CRC32 checksum mismatch \
70 (computed=0x{:08X}, expected=0x{:08X}). Wrong keyring?",
71 computed_crc, info.checksum
72 )));
73 }
74
75 let mut tablespace_key = [0u8; 32];
76 let mut tablespace_iv = [0u8; 32];
77 tablespace_key.copy_from_slice(&decrypted[..32]);
78 tablespace_iv.copy_from_slice(&decrypted[32..64]);
79
80 Ok(DecryptionContext {
81 tablespace_key,
82 tablespace_iv,
83 })
84 }
85
86 pub fn decrypt_page(&self, page_data: &mut [u8], page_size: usize) -> Result<bool, IdbError> {
95 if page_data.len() < page_size {
96 return Err(IdbError::Parse(
97 "Page data too short for decryption".to_string(),
98 ));
99 }
100
101 let page_type_raw = BigEndian::read_u16(&page_data[FIL_PAGE_TYPE..]);
103 let page_type = PageType::from_u16(page_type_raw);
104
105 if !matches!(
106 page_type,
107 PageType::Encrypted | PageType::CompressedEncrypted | PageType::EncryptedRtree
108 ) {
109 return Ok(false);
110 }
111
112 let original_type = BigEndian::read_u16(&page_data[FIL_PAGE_ORIGINAL_TYPE_V1..]);
115
116 let encrypt_start = SIZE_FIL_HEAD;
118 let encrypt_end = page_size - SIZE_FIL_TRAILER;
119 let encrypt_len = encrypt_end - encrypt_start;
120
121 let aes_block_size = 16;
123
124 if encrypt_len < aes_block_size {
125 return Err(IdbError::Parse(
126 "Encrypted page body too small for AES decryption".to_string(),
127 ));
128 }
129
130 let iv: [u8; 16] = self.tablespace_iv[..16].try_into().unwrap();
132
133 let main_len = (encrypt_len / aes_block_size) * aes_block_size;
138
139 if main_len > 0 {
140 let main_end = encrypt_start + main_len;
141 let decryptor = Aes256CbcDec::new_from_slices(&self.tablespace_key, &iv)
142 .map_err(|e| IdbError::Parse(format!("AES-256-CBC init failed: {}", e)))?;
143 decryptor
144 .decrypt_padded_mut::<NoPadding>(&mut page_data[encrypt_start..main_end])
145 .map_err(|e| IdbError::Parse(format!("AES-256-CBC decrypt failed: {}", e)))?;
146 }
147
148 BigEndian::write_u16(&mut page_data[FIL_PAGE_TYPE..], original_type);
150
151 Ok(true)
152 }
153
154 pub fn is_encrypted_page(page_data: &[u8]) -> bool {
156 if page_data.len() < SIZE_FIL_HEAD {
157 return false;
158 }
159 let page_type = PageType::from_u16(BigEndian::read_u16(&page_data[FIL_PAGE_TYPE..]));
160 matches!(
161 page_type,
162 PageType::Encrypted | PageType::CompressedEncrypted | PageType::EncryptedRtree
163 )
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use aes::cipher::BlockEncryptMut;
171
172 type Aes256CbcEnc = cbc::Encryptor<Aes256>;
173 type Aes256EcbEnc = ecb::Encryptor<Aes256>;
174
175 fn build_encrypted_page(
177 page_num: u32,
178 space_id: u32,
179 original_type: u16,
180 key: &[u8; 32],
181 iv: &[u8; 32],
182 page_size: usize,
183 ) -> Vec<u8> {
184 let mut page = vec![0u8; page_size];
185
186 BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], page_num);
188 BigEndian::write_u32(&mut page[FIL_PAGE_PREV..], FIL_NULL);
189 BigEndian::write_u32(&mut page[FIL_PAGE_NEXT..], FIL_NULL);
190 BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 5000);
191 BigEndian::write_u16(&mut page[FIL_PAGE_ORIGINAL_TYPE_V1..], original_type);
193 BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], space_id);
194
195 for i in SIZE_FIL_HEAD..page_size - SIZE_FIL_TRAILER {
197 page[i] = ((i * 7 + 13) & 0xFF) as u8;
198 }
199
200 let encrypt_start = SIZE_FIL_HEAD;
202 let encrypt_end = page_size - SIZE_FIL_TRAILER;
203 let encrypt_len = encrypt_end - encrypt_start;
204 let aes_block_size = 16;
205 let main_len = (encrypt_len / aes_block_size) * aes_block_size;
206
207 let cbc_iv: [u8; 16] = iv[..16].try_into().unwrap();
208 let encryptor = Aes256CbcEnc::new_from_slices(key, &cbc_iv).unwrap();
209 encryptor
210 .encrypt_padded_mut::<NoPadding>(
211 &mut page[encrypt_start..encrypt_start + main_len],
212 main_len,
213 )
214 .unwrap();
215
216 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 15); let trailer = page_size - SIZE_FIL_TRAILER;
221 BigEndian::write_u32(&mut page[trailer + 4..], (5000u64 & 0xFFFFFFFF) as u32);
222
223 page
224 }
225
226 #[test]
227 fn test_decrypt_page_roundtrip() {
228 let key: [u8; 32] = [0x42; 32];
229 let iv: [u8; 32] = [0x13; 32];
230 let page_size = 16384;
231
232 let mut reference = vec![0u8; page_size];
234 for i in SIZE_FIL_HEAD..page_size - SIZE_FIL_TRAILER {
235 reference[i] = ((i * 7 + 13) & 0xFF) as u8;
236 }
237
238 let mut encrypted = build_encrypted_page(1, 1, 17855, &key, &iv, page_size);
240
241 let pt = BigEndian::read_u16(&encrypted[FIL_PAGE_TYPE..]);
243 assert_eq!(pt, 15);
244
245 let ctx = DecryptionContext {
247 tablespace_key: key,
248 tablespace_iv: iv,
249 };
250 let decrypted = ctx.decrypt_page(&mut encrypted, page_size).unwrap();
251 assert!(decrypted);
252
253 let restored_type = BigEndian::read_u16(&encrypted[FIL_PAGE_TYPE..]);
255 assert_eq!(restored_type, 17855);
256
257 assert_eq!(
259 &encrypted[SIZE_FIL_HEAD..page_size - SIZE_FIL_TRAILER],
260 &reference[SIZE_FIL_HEAD..page_size - SIZE_FIL_TRAILER]
261 );
262 }
263
264 #[test]
265 fn test_decrypt_non_encrypted_page_is_noop() {
266 let key: [u8; 32] = [0x42; 32];
267 let iv: [u8; 32] = [0x13; 32];
268
269 let mut page = vec![0u8; 16384];
270 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855); let ctx = DecryptionContext {
273 tablespace_key: key,
274 tablespace_iv: iv,
275 };
276 let result = ctx.decrypt_page(&mut page, 16384).unwrap();
277 assert!(!result);
278 }
279
280 #[test]
281 fn test_is_encrypted_page() {
282 let mut page = vec![0u8; 38];
283 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 15);
284 assert!(DecryptionContext::is_encrypted_page(&page));
285
286 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 16);
287 assert!(DecryptionContext::is_encrypted_page(&page));
288
289 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17);
290 assert!(DecryptionContext::is_encrypted_page(&page));
291
292 BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855);
293 assert!(!DecryptionContext::is_encrypted_page(&page));
294 }
295
296 #[test]
297 fn test_from_encryption_info() {
298 use crate::innodb::keyring::Keyring;
299 use sha2::{Digest, Sha256};
300
301 let master_key: [u8; 32] = [0xAA; 32];
303 let ts_key: [u8; 32] = [0xBB; 32];
304 let ts_iv: [u8; 32] = [0xCC; 32];
305
306 let mut key_iv_data = [0u8; 64];
308 key_iv_data[..32].copy_from_slice(&ts_key);
309 key_iv_data[32..].copy_from_slice(&ts_iv);
310
311 let crc = crc32c::crc32c(&key_iv_data);
312
313 let encryptor = Aes256EcbEnc::new_from_slice(&master_key).unwrap();
314 let mut encrypted_key_iv = key_iv_data;
315 encryptor
316 .encrypt_padded_mut::<NoPadding>(&mut encrypted_key_iv, 64)
317 .unwrap();
318
319 let uuid = "12345678-1234-1234-1234-123456789abc";
320 let info = EncryptionInfo {
321 magic_version: 3,
322 master_key_id: 1,
323 server_uuid: uuid.to_string(),
324 encrypted_key_iv,
325 checksum: crc,
326 };
327
328 let obfuscate_key = b"*305=Ljt0*!@$Hnm(*-9-w;:";
330 let key_id = format!("INNODBKey-{}-1", uuid);
331 let mut obfuscated_master = master_key.to_vec();
332 for (i, byte) in obfuscated_master.iter_mut().enumerate() {
333 *byte ^= obfuscate_key[i % obfuscate_key.len()];
334 }
335
336 let mut entry = Vec::new();
337 let pod_size = 40 + key_id.len() + 3 + 0 + 32;
338 entry.extend_from_slice(&(pod_size as u64).to_le_bytes());
339 entry.extend_from_slice(&(key_id.len() as u64).to_le_bytes());
340 entry.extend_from_slice(&(3u64).to_le_bytes()); entry.extend_from_slice(&(0u64).to_le_bytes()); entry.extend_from_slice(&(32u64).to_le_bytes());
343 entry.extend_from_slice(key_id.as_bytes());
344 entry.extend_from_slice(b"AES");
345 entry.extend_from_slice(&obfuscated_master);
346
347 let mut file_data = entry;
348 let mut hasher = Sha256::new();
349 hasher.update(&file_data);
350 let hash = hasher.finalize();
351 file_data.extend_from_slice(&hash);
352
353 let tmp = tempfile::NamedTempFile::new().unwrap();
354 std::fs::write(tmp.path(), &file_data).unwrap();
355
356 let keyring = Keyring::load(tmp.path()).unwrap();
357 let ctx = DecryptionContext::from_encryption_info(&info, &keyring).unwrap();
358
359 assert_eq!(ctx.tablespace_key, ts_key);
360 assert_eq!(ctx.tablespace_iv, ts_iv);
361 }
362
363 #[test]
364 fn test_from_encryption_info_wrong_key() {
365 use crate::innodb::keyring::Keyring;
366 use sha2::{Digest, Sha256};
367
368 let master_key: [u8; 32] = [0xAA; 32];
369 let wrong_master: [u8; 32] = [0xDD; 32];
370 let ts_key: [u8; 32] = [0xBB; 32];
371 let ts_iv: [u8; 32] = [0xCC; 32];
372
373 let mut key_iv_data = [0u8; 64];
374 key_iv_data[..32].copy_from_slice(&ts_key);
375 key_iv_data[32..].copy_from_slice(&ts_iv);
376 let crc = crc32c::crc32c(&key_iv_data);
377
378 let encryptor = Aes256EcbEnc::new_from_slice(&master_key).unwrap();
380 let mut encrypted_key_iv = key_iv_data;
381 encryptor
382 .encrypt_padded_mut::<NoPadding>(&mut encrypted_key_iv, 64)
383 .unwrap();
384
385 let uuid = "12345678-1234-1234-1234-123456789abc";
386 let info = EncryptionInfo {
387 magic_version: 3,
388 master_key_id: 1,
389 server_uuid: uuid.to_string(),
390 encrypted_key_iv,
391 checksum: crc,
392 };
393
394 let obfuscate_key = b"*305=Ljt0*!@$Hnm(*-9-w;:";
396 let key_id = format!("INNODBKey-{}-1", uuid);
397 let mut obfuscated = wrong_master.to_vec();
398 for (i, byte) in obfuscated.iter_mut().enumerate() {
399 *byte ^= obfuscate_key[i % obfuscate_key.len()];
400 }
401
402 let mut entry = Vec::new();
403 let pod_size = 40 + key_id.len() + 3 + 0 + 32;
404 entry.extend_from_slice(&(pod_size as u64).to_le_bytes());
405 entry.extend_from_slice(&(key_id.len() as u64).to_le_bytes());
406 entry.extend_from_slice(&(3u64).to_le_bytes());
407 entry.extend_from_slice(&(0u64).to_le_bytes());
408 entry.extend_from_slice(&(32u64).to_le_bytes());
409 entry.extend_from_slice(key_id.as_bytes());
410 entry.extend_from_slice(b"AES");
411 entry.extend_from_slice(&obfuscated);
412
413 let mut file_data = entry;
414 let mut hasher = Sha256::new();
415 hasher.update(&file_data);
416 let hash = hasher.finalize();
417 file_data.extend_from_slice(&hash);
418
419 let tmp = tempfile::NamedTempFile::new().unwrap();
420 std::fs::write(tmp.path(), &file_data).unwrap();
421
422 let keyring = Keyring::load(tmp.path()).unwrap();
423 let result = DecryptionContext::from_encryption_info(&info, &keyring);
424 assert!(result.is_err());
425 assert!(result
426 .unwrap_err()
427 .to_string()
428 .contains("CRC32 checksum mismatch"));
429 }
430}