1use crate::innodb::constants::*;
12use crate::innodb::vendor::VendorInfo;
13use byteorder::{BigEndian, ByteOrder};
14use serde::Serialize;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum EncryptionAlgorithm {
19 None,
20 Aes,
21}
22
23pub fn detect_encryption(fsp_flags: u32, vendor_info: Option<&VendorInfo>) -> EncryptionAlgorithm {
29 if vendor_info.is_some_and(|v| v.vendor == crate::innodb::vendor::InnoDbVendor::MariaDB) {
31 return EncryptionAlgorithm::None;
32 }
33
34 if (fsp_flags >> 13) & 0x01 != 0 {
35 EncryptionAlgorithm::Aes
36 } else {
37 EncryptionAlgorithm::None
38 }
39}
40
41pub fn is_encrypted(fsp_flags: u32) -> bool {
43 detect_encryption(fsp_flags, None) != EncryptionAlgorithm::None
44}
45
46pub fn is_mariadb_encrypted_page(page_type: u16) -> bool {
48 page_type == crate::innodb::constants::FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
49}
50
51pub fn mariadb_encryption_key_version(page_data: &[u8]) -> Option<u32> {
56 if page_data.len() < 30 {
57 return None;
58 }
59 Some(BigEndian::read_u32(&page_data[26..]))
60}
61
62#[derive(Debug, Clone, Serialize)]
68pub struct EncryptionInfo {
69 pub magic_version: u8,
71 pub master_key_id: u32,
73 pub server_uuid: String,
75 #[serde(skip)]
77 pub encrypted_key_iv: [u8; 64],
78 pub checksum: u32,
80}
81
82fn pages_per_extent(page_size: u32) -> u32 {
84 if page_size <= 16384 {
85 1048576 / page_size } else {
87 64 }
89}
90
91fn xdes_arr_size(page_size: u32) -> u32 {
93 page_size / pages_per_extent(page_size)
94}
95
96pub fn encryption_info_offset(page_size: u32) -> usize {
100 let xdes_arr_offset = FIL_PAGE_DATA + FSP_HEADER_SIZE;
101 let xdes_entries = xdes_arr_size(page_size) as usize;
102 xdes_arr_offset + xdes_entries * XDES_SIZE
103}
104
105pub fn parse_encryption_info(page0: &[u8], page_size: u32) -> Option<EncryptionInfo> {
110 let offset = encryption_info_offset(page_size);
111
112 if page0.len() < offset + ENCRYPTION_INFO_SIZE {
113 return None;
114 }
115
116 let magic = &page0[offset..offset + ENCRYPTION_MAGIC_SIZE];
117 let magic_version = if magic == ENCRYPTION_MAGIC_V1 {
118 1
119 } else if magic == ENCRYPTION_MAGIC_V2 {
120 2
121 } else if magic == ENCRYPTION_MAGIC_V3 {
122 3
123 } else {
124 return None;
125 };
126
127 let master_key_id = BigEndian::read_u32(&page0[offset + 3..]);
128 let uuid_bytes = &page0[offset + 7..offset + 7 + ENCRYPTION_SERVER_UUID_LEN];
129 let server_uuid = String::from_utf8_lossy(uuid_bytes).to_string();
130
131 let mut encrypted_key_iv = [0u8; 64];
132 encrypted_key_iv.copy_from_slice(&page0[offset + 43..offset + 43 + 64]);
133
134 let checksum = BigEndian::read_u32(&page0[offset + 107..]);
135
136 Some(EncryptionInfo {
137 magic_version,
138 master_key_id,
139 server_uuid,
140 encrypted_key_iv,
141 checksum,
142 })
143}
144
145impl std::fmt::Display for EncryptionAlgorithm {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 match self {
148 EncryptionAlgorithm::None => write!(f, "None"),
149 EncryptionAlgorithm::Aes => write!(f, "AES"),
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::innodb::vendor::MariaDbFormat;
158
159 #[test]
160 fn test_detect_encryption_mysql() {
161 assert_eq!(detect_encryption(0, None), EncryptionAlgorithm::None);
162 assert_eq!(detect_encryption(1 << 13, None), EncryptionAlgorithm::Aes);
163 assert_eq!(detect_encryption(0xFF, None), EncryptionAlgorithm::None);
164 assert_eq!(
165 detect_encryption(0xFF | (1 << 13), None),
166 EncryptionAlgorithm::Aes
167 );
168 }
169
170 #[test]
171 fn test_detect_encryption_mariadb_returns_none() {
172 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
173 assert_eq!(
175 detect_encryption(1 << 13, Some(&vendor)),
176 EncryptionAlgorithm::None
177 );
178 }
179
180 #[test]
181 fn test_is_encrypted() {
182 assert!(!is_encrypted(0));
183 assert!(is_encrypted(1 << 13));
184 }
185
186 #[test]
187 fn test_is_mariadb_encrypted_page() {
188 assert!(is_mariadb_encrypted_page(37401));
189 assert!(!is_mariadb_encrypted_page(17855));
190 }
191
192 #[test]
193 fn test_mariadb_encryption_key_version() {
194 let mut page = vec![0u8; 38];
195 BigEndian::write_u32(&mut page[26..], 42);
196 assert_eq!(mariadb_encryption_key_version(&page), Some(42));
197 }
198
199 #[test]
200 fn test_encryption_info_offset_16k() {
201 assert_eq!(encryption_info_offset(16384), 10390);
203 }
204
205 #[test]
206 fn test_encryption_info_offset_various() {
207 assert_eq!(encryption_info_offset(4096), 38 + 112 + 16 * 40); assert_eq!(encryption_info_offset(8192), 38 + 112 + 64 * 40); assert_eq!(encryption_info_offset(32768), 38 + 112 + 512 * 40); }
211
212 #[test]
213 fn test_parse_encryption_info_v3() {
214 let mut page = vec![0u8; 16384];
215 let offset = encryption_info_offset(16384);
216
217 page[offset..offset + 3].copy_from_slice(b"lCC");
219 BigEndian::write_u32(&mut page[offset + 3..], 42);
221 let uuid = "12345678-1234-1234-1234-123456789abc";
223 page[offset + 7..offset + 7 + 36].copy_from_slice(uuid.as_bytes());
224 for i in 0..64 {
226 page[offset + 43 + i] = i as u8;
227 }
228 BigEndian::write_u32(&mut page[offset + 107..], 0xDEADBEEF);
230
231 let info = parse_encryption_info(&page, 16384).unwrap();
232 assert_eq!(info.magic_version, 3);
233 assert_eq!(info.master_key_id, 42);
234 assert_eq!(info.server_uuid, uuid);
235 assert_eq!(info.checksum, 0xDEADBEEF);
236 assert_eq!(info.encrypted_key_iv[0], 0);
237 assert_eq!(info.encrypted_key_iv[63], 63);
238 }
239
240 #[test]
241 fn test_parse_encryption_info_v1() {
242 let mut page = vec![0u8; 16384];
243 let offset = encryption_info_offset(16384);
244 page[offset..offset + 3].copy_from_slice(b"lCA");
245 BigEndian::write_u32(&mut page[offset + 3..], 1);
246 let uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
247 page[offset + 7..offset + 7 + 36].copy_from_slice(uuid.as_bytes());
248 BigEndian::write_u32(&mut page[offset + 107..], 0x12345678);
249
250 let info = parse_encryption_info(&page, 16384).unwrap();
251 assert_eq!(info.magic_version, 1);
252 assert_eq!(info.master_key_id, 1);
253 }
254
255 #[test]
256 fn test_parse_encryption_info_no_magic() {
257 let page = vec![0u8; 16384];
258 assert!(parse_encryption_info(&page, 16384).is_none());
259 }
260
261 #[test]
262 fn test_parse_encryption_info_bad_magic() {
263 let mut page = vec![0u8; 16384];
264 let offset = encryption_info_offset(16384);
265 page[offset..offset + 3].copy_from_slice(b"lCD");
266 assert!(parse_encryption_info(&page, 16384).is_none());
267 }
268}