use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncryptionType {
Deterministic,
Randomized,
}
impl EncryptionType {
#[must_use]
pub fn algorithm_name(&self) -> &'static str {
match self {
EncryptionType::Deterministic => "AEAD_AES_256_CBC_HMAC_SHA_256_DETERMINISTIC",
EncryptionType::Randomized => "AEAD_AES_256_CBC_HMAC_SHA_256_RANDOMIZED",
}
}
#[must_use]
pub fn from_sys_columns_value(value: i32) -> Option<Self> {
match value {
1 => Some(EncryptionType::Deterministic),
2 => Some(EncryptionType::Randomized),
_ => None,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct CekMetadata {
pub database_id: u32,
pub cek_id: u32,
pub cek_version: u32,
pub cek_md_version: u64,
pub encrypted_value: Vec<u8>,
pub key_store_provider_name: String,
pub cmk_path: String,
pub encryption_algorithm: String,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ColumnEncryptionInfo {
pub column_name: String,
pub column_ordinal: u16,
pub is_encrypted: bool,
pub encryption_type: Option<EncryptionType>,
pub encryption_algorithm: Option<String>,
pub cek_metadata: Option<CekMetadata>,
}
impl ColumnEncryptionInfo {
#[must_use]
pub fn unencrypted(column_name: impl Into<String>, column_ordinal: u16) -> Self {
Self {
column_name: column_name.into(),
column_ordinal,
is_encrypted: false,
encryption_type: None,
encryption_algorithm: None,
cek_metadata: None,
}
}
#[must_use]
pub fn encrypted(
column_name: impl Into<String>,
column_ordinal: u16,
encryption_type: EncryptionType,
cek_metadata: CekMetadata,
) -> Self {
Self {
column_name: column_name.into(),
column_ordinal,
is_encrypted: true,
encryption_type: Some(encryption_type),
encryption_algorithm: Some(encryption_type.algorithm_name().to_string()),
cek_metadata: Some(cek_metadata),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum EncryptionError {
KeyStoreNotFound(String),
CmkError(String),
CekDecryptionFailed(String),
EncryptionFailed(String),
DecryptionFailed(String),
MetadataNotAvailable(String),
UnsupportedOperation(String),
ConfigurationError(String),
}
impl std::error::Error for EncryptionError {}
impl fmt::Display for EncryptionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncryptionError::KeyStoreNotFound(name) => {
write!(f, "Key store provider not found: {name}")
}
EncryptionError::CmkError(msg) => {
write!(f, "Column Master Key error: {msg}")
}
EncryptionError::CekDecryptionFailed(msg) => {
write!(f, "Failed to decrypt Column Encryption Key: {msg}")
}
EncryptionError::EncryptionFailed(msg) => {
write!(f, "Encryption failed: {msg}")
}
EncryptionError::DecryptionFailed(msg) => {
write!(f, "Decryption failed: {msg}")
}
EncryptionError::MetadataNotAvailable(msg) => {
write!(f, "Encryption metadata not available: {msg}")
}
EncryptionError::UnsupportedOperation(msg) => {
write!(f, "Unsupported operation with encryption: {msg}")
}
EncryptionError::ConfigurationError(msg) => {
write!(f, "Encryption configuration error: {msg}")
}
}
}
}
#[async_trait::async_trait]
pub trait KeyStoreProvider: Send + Sync {
fn provider_name(&self) -> &str;
async fn decrypt_cek(
&self,
cmk_path: &str,
algorithm: &str,
encrypted_cek: &[u8],
) -> Result<Vec<u8>, EncryptionError>;
async fn sign_data(&self, _cmk_path: &str, _data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
Err(EncryptionError::UnsupportedOperation(
"Signing not supported by this key store provider".into(),
))
}
async fn verify_signature(
&self,
_cmk_path: &str,
_data: &[u8],
_signature: &[u8],
) -> Result<bool, EncryptionError> {
Err(EncryptionError::UnsupportedOperation(
"Signature verification not supported by this key store provider".into(),
))
}
}
#[derive(Default)]
pub struct ColumnEncryptionConfig {
pub enabled: bool,
providers: Vec<Box<dyn KeyStoreProvider>>,
pub cache_ceks: bool,
pub allow_unsafe_operations: bool,
}
impl ColumnEncryptionConfig {
#[must_use]
pub fn new() -> Self {
Self {
enabled: true,
providers: Vec::new(),
cache_ceks: true,
allow_unsafe_operations: false,
}
}
pub fn register_provider(&mut self, provider: impl KeyStoreProvider + 'static) {
self.providers.push(Box::new(provider));
}
#[must_use]
pub fn with_provider(mut self, provider: impl KeyStoreProvider + 'static) -> Self {
self.register_provider(provider);
self
}
#[must_use]
pub fn with_cek_caching(mut self, enabled: bool) -> Self {
self.cache_ceks = enabled;
self
}
pub fn get_provider(&self, name: &str) -> Option<&dyn KeyStoreProvider> {
self.providers
.iter()
.find(|p| p.provider_name() == name)
.map(|p| p.as_ref())
}
#[must_use]
pub fn is_ready(&self) -> bool {
self.enabled && !self.providers.is_empty()
}
}
impl fmt::Debug for ColumnEncryptionConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ColumnEncryptionConfig")
.field("enabled", &self.enabled)
.field(
"providers",
&self
.providers
.iter()
.map(|p| p.provider_name())
.collect::<Vec<_>>(),
)
.field("cache_ceks", &self.cache_ceks)
.field("allow_unsafe_operations", &self.allow_unsafe_operations)
.finish()
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct EncryptedValue {
pub ciphertext: Vec<u8>,
pub cek_id: u32,
pub encryption_type: EncryptionType,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_encryption_type_algorithm_names() {
assert_eq!(
EncryptionType::Deterministic.algorithm_name(),
"AEAD_AES_256_CBC_HMAC_SHA_256_DETERMINISTIC"
);
assert_eq!(
EncryptionType::Randomized.algorithm_name(),
"AEAD_AES_256_CBC_HMAC_SHA_256_RANDOMIZED"
);
}
#[test]
fn test_encryption_type_from_sys_columns() {
assert_eq!(
EncryptionType::from_sys_columns_value(1),
Some(EncryptionType::Deterministic)
);
assert_eq!(
EncryptionType::from_sys_columns_value(2),
Some(EncryptionType::Randomized)
);
assert_eq!(EncryptionType::from_sys_columns_value(0), None);
assert_eq!(EncryptionType::from_sys_columns_value(99), None);
}
#[test]
fn test_column_encryption_info_unencrypted() {
let info = ColumnEncryptionInfo::unencrypted("name", 1);
assert!(!info.is_encrypted);
assert!(info.encryption_type.is_none());
assert!(info.cek_metadata.is_none());
}
#[test]
fn test_column_encryption_config_debug() {
let config = ColumnEncryptionConfig::new();
let debug = format!("{config:?}");
assert!(debug.contains("ColumnEncryptionConfig"));
assert!(debug.contains("enabled: true"));
}
#[test]
fn test_encryption_error_display() {
let error = EncryptionError::KeyStoreNotFound("AZURE_KEY_VAULT".into());
assert!(error.to_string().contains("AZURE_KEY_VAULT"));
let error = EncryptionError::EncryptionFailed("test error".into());
assert!(error.to_string().contains("test error"));
}
}