use crate::innodb::constants::*;
use crate::innodb::vendor::VendorInfo;
use byteorder::{BigEndian, ByteOrder};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionAlgorithm {
None,
Aes,
}
pub fn detect_encryption(fsp_flags: u32, vendor_info: Option<&VendorInfo>) -> EncryptionAlgorithm {
if vendor_info.is_some_and(|v| v.vendor == crate::innodb::vendor::InnoDbVendor::MariaDB) {
return EncryptionAlgorithm::None;
}
if (fsp_flags >> 13) & 0x01 != 0 {
EncryptionAlgorithm::Aes
} else {
EncryptionAlgorithm::None
}
}
pub fn is_encrypted(fsp_flags: u32) -> bool {
detect_encryption(fsp_flags, None) != EncryptionAlgorithm::None
}
pub fn is_mariadb_encrypted_page(page_type: u16) -> bool {
page_type == crate::innodb::constants::FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
}
pub fn mariadb_encryption_key_version(page_data: &[u8]) -> Option<u32> {
if page_data.len() < 30 {
return None;
}
Some(BigEndian::read_u32(&page_data[26..]))
}
#[derive(Debug, Clone, Serialize)]
pub struct EncryptionInfo {
pub magic_version: u8,
pub master_key_id: u32,
pub server_uuid: String,
#[serde(skip)]
pub encrypted_key_iv: [u8; 64],
pub checksum: u32,
}
fn pages_per_extent(page_size: u32) -> u32 {
if page_size <= 16384 {
1048576 / page_size } else {
64 }
}
fn xdes_arr_size(page_size: u32) -> u32 {
page_size / pages_per_extent(page_size)
}
pub fn encryption_info_offset(page_size: u32) -> usize {
let xdes_arr_offset = FIL_PAGE_DATA + FSP_HEADER_SIZE;
let xdes_entries = xdes_arr_size(page_size) as usize;
xdes_arr_offset + xdes_entries * XDES_SIZE
}
pub fn parse_encryption_info(page0: &[u8], page_size: u32) -> Option<EncryptionInfo> {
let offset = encryption_info_offset(page_size);
if page0.len() < offset + ENCRYPTION_INFO_SIZE {
return None;
}
let magic = &page0[offset..offset + ENCRYPTION_MAGIC_SIZE];
let magic_version = if magic == ENCRYPTION_MAGIC_V1 {
1
} else if magic == ENCRYPTION_MAGIC_V2 {
2
} else if magic == ENCRYPTION_MAGIC_V3 {
3
} else {
return None;
};
let master_key_id = BigEndian::read_u32(&page0[offset + 3..]);
let uuid_bytes = &page0[offset + 7..offset + 7 + ENCRYPTION_SERVER_UUID_LEN];
let server_uuid = String::from_utf8_lossy(uuid_bytes).to_string();
let mut encrypted_key_iv = [0u8; 64];
encrypted_key_iv.copy_from_slice(&page0[offset + 43..offset + 43 + 64]);
let checksum = BigEndian::read_u32(&page0[offset + 107..]);
Some(EncryptionInfo {
magic_version,
master_key_id,
server_uuid,
encrypted_key_iv,
checksum,
})
}
impl std::fmt::Display for EncryptionAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EncryptionAlgorithm::None => write!(f, "None"),
EncryptionAlgorithm::Aes => write!(f, "AES"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::innodb::vendor::MariaDbFormat;
#[test]
fn test_detect_encryption_mysql() {
assert_eq!(detect_encryption(0, None), EncryptionAlgorithm::None);
assert_eq!(detect_encryption(1 << 13, None), EncryptionAlgorithm::Aes);
assert_eq!(detect_encryption(0xFF, None), EncryptionAlgorithm::None);
assert_eq!(
detect_encryption(0xFF | (1 << 13), None),
EncryptionAlgorithm::Aes
);
}
#[test]
fn test_detect_encryption_mariadb_returns_none() {
let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
assert_eq!(
detect_encryption(1 << 13, Some(&vendor)),
EncryptionAlgorithm::None
);
}
#[test]
fn test_is_encrypted() {
assert!(!is_encrypted(0));
assert!(is_encrypted(1 << 13));
}
#[test]
fn test_is_mariadb_encrypted_page() {
assert!(is_mariadb_encrypted_page(37401));
assert!(!is_mariadb_encrypted_page(17855));
}
#[test]
fn test_mariadb_encryption_key_version() {
let mut page = vec![0u8; 38];
BigEndian::write_u32(&mut page[26..], 42);
assert_eq!(mariadb_encryption_key_version(&page), Some(42));
}
#[test]
fn test_encryption_info_offset_16k() {
assert_eq!(encryption_info_offset(16384), 10390);
}
#[test]
fn test_encryption_info_offset_various() {
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); }
#[test]
fn test_parse_encryption_info_v3() {
let mut page = vec![0u8; 16384];
let offset = encryption_info_offset(16384);
page[offset..offset + 3].copy_from_slice(b"lCC");
BigEndian::write_u32(&mut page[offset + 3..], 42);
let uuid = "12345678-1234-1234-1234-123456789abc";
page[offset + 7..offset + 7 + 36].copy_from_slice(uuid.as_bytes());
for i in 0..64 {
page[offset + 43 + i] = i as u8;
}
BigEndian::write_u32(&mut page[offset + 107..], 0xDEADBEEF);
let info = parse_encryption_info(&page, 16384).unwrap();
assert_eq!(info.magic_version, 3);
assert_eq!(info.master_key_id, 42);
assert_eq!(info.server_uuid, uuid);
assert_eq!(info.checksum, 0xDEADBEEF);
assert_eq!(info.encrypted_key_iv[0], 0);
assert_eq!(info.encrypted_key_iv[63], 63);
}
#[test]
fn test_parse_encryption_info_v1() {
let mut page = vec![0u8; 16384];
let offset = encryption_info_offset(16384);
page[offset..offset + 3].copy_from_slice(b"lCA");
BigEndian::write_u32(&mut page[offset + 3..], 1);
let uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
page[offset + 7..offset + 7 + 36].copy_from_slice(uuid.as_bytes());
BigEndian::write_u32(&mut page[offset + 107..], 0x12345678);
let info = parse_encryption_info(&page, 16384).unwrap();
assert_eq!(info.magic_version, 1);
assert_eq!(info.master_key_id, 1);
}
#[test]
fn test_parse_encryption_info_no_magic() {
let page = vec![0u8; 16384];
assert!(parse_encryption_info(&page, 16384).is_none());
}
#[test]
fn test_parse_encryption_info_bad_magic() {
let mut page = vec![0u8; 16384];
let offset = encryption_info_offset(16384);
page[offset..offset + 3].copy_from_slice(b"lCD");
assert!(parse_encryption_info(&page, 16384).is_none());
}
}