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