mssql_auth/encryption.rs
1//! Always Encrypted infrastructure for SQL Server.
2//!
3//! This module provides the foundational types and interfaces for implementing
4//! SQL Server's Always Encrypted feature, which provides client-side encryption
5//! for sensitive database columns.
6//!
7//! ## Architecture Overview
8//!
9//! Always Encrypted uses a two-tier key hierarchy:
10//!
11//! ```text
12//! ┌─────────────────────────────────────────────────────────────────┐
13//! │ Key Hierarchy │
14//! ├─────────────────────────────────────────────────────────────────┤
15//! │ │
16//! │ Column Master Key (CMK) │
17//! │ ├── Stored externally (KeyVault, CertStore, HSM) │
18//! │ ├── Never sent to SQL Server │
19//! │ └── Used to encrypt/decrypt CEKs │
20//! │ │ │
21//! │ ▼ │
22//! │ Column Encryption Key (CEK) │
23//! │ ├── Stored in database (encrypted by CMK) │
24//! │ ├── Decrypted on client side │
25//! │ └── Used for actual data encryption (AES-256) │
26//! │ │ │
27//! │ ▼ │
28//! │ Encrypted Column Data │
29//! │ ├── Deterministic: Same input → same ciphertext │
30//! │ └── Randomized: Same input → different ciphertext │
31//! │ │
32//! └─────────────────────────────────────────────────────────────────┘
33//! ```
34//!
35//! ## Security Model
36//!
37//! - **Client-only decryption**: The SQL Server never sees plaintext data
38//! - **DBA protection**: Even database administrators cannot read encrypted data
39//! - **Key separation**: CMK stays in secure key store, never transmitted
40//!
41//! ## Usage
42//!
43//! ```rust,ignore
44//! use mssql_auth::encryption::{ColumnEncryptionConfig, KeyStoreProvider};
45//!
46//! // Create encryption configuration
47//! let config = ColumnEncryptionConfig::new()
48//! .with_key_store(azure_key_vault_provider)
49//! .build();
50//!
51//! // Use with connection
52//! let client = Client::connect(config.with_encryption(encryption_config)).await?;
53//! ```
54//!
55//! ## Implementation Status
56//!
57//! This module provides the **infrastructure and interfaces** for Always Encrypted.
58//! Full implementation requires:
59//!
60//! - [ ] Key store provider implementations (Azure KeyVault, Windows CertStore)
61//! - [ ] AES-256 encryption/decryption routines
62//! - [ ] RSA-OAEP key unwrapping
63//! - [ ] Metadata fetching from sys.columns
64//! - [ ] Parameter encryption hooks
65//! - [ ] Result decryption hooks
66//!
67//! Tracked as CRYPTO-001 in the project roadmap.
68
69use std::fmt;
70
71/// Encryption type for Always Encrypted columns.
72///
73/// Determines how data is encrypted and what operations are supported.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[non_exhaustive]
76pub enum EncryptionType {
77 /// Deterministic encryption: same plaintext → same ciphertext.
78 ///
79 /// Supports:
80 /// - Equality comparisons (`WHERE col = @param`)
81 /// - JOIN operations
82 /// - GROUP BY
83 /// - DISTINCT
84 /// - Indexing
85 ///
86 /// **Security note**: Reveals data patterns; less secure than randomized.
87 Deterministic,
88
89 /// Randomized encryption: same plaintext → different ciphertext each time.
90 ///
91 /// Maximum security but does NOT support:
92 /// - Any comparisons (equality, range, etc.)
93 /// - JOIN operations on encrypted column
94 /// - GROUP BY or DISTINCT
95 /// - Indexing
96 Randomized,
97}
98
99impl EncryptionType {
100 /// Returns the algorithm identifier used in metadata.
101 #[must_use]
102 pub fn algorithm_name(&self) -> &'static str {
103 match self {
104 EncryptionType::Deterministic => "AEAD_AES_256_CBC_HMAC_SHA_256_DETERMINISTIC",
105 EncryptionType::Randomized => "AEAD_AES_256_CBC_HMAC_SHA_256_RANDOMIZED",
106 }
107 }
108
109 /// Parse from the numeric value stored in sys.columns.
110 #[must_use]
111 pub fn from_sys_columns_value(value: i32) -> Option<Self> {
112 match value {
113 1 => Some(EncryptionType::Deterministic),
114 2 => Some(EncryptionType::Randomized),
115 _ => None,
116 }
117 }
118}
119
120/// Metadata about a Column Encryption Key (CEK).
121///
122/// This metadata is retrieved from SQL Server's `sys.column_encryption_keys`
123/// and related system views.
124#[derive(Debug, Clone)]
125#[non_exhaustive]
126pub struct CekMetadata {
127 /// Database-level identifier for this CEK.
128 pub database_id: u32,
129 /// CEK identifier within the database.
130 pub cek_id: u32,
131 /// Version of the CEK (for key rotation).
132 pub cek_version: u32,
133 /// Metadata version (changes with any metadata update).
134 pub cek_md_version: u64,
135 /// The encrypted CEK value (encrypted by CMK).
136 pub encrypted_value: Vec<u8>,
137 /// Name of the key store provider (e.g., "AZURE_KEY_VAULT").
138 pub key_store_provider_name: String,
139 /// Path to the Column Master Key in the key store.
140 pub cmk_path: String,
141 /// Asymmetric algorithm used to encrypt the CEK (e.g., "RSA_OAEP").
142 pub encryption_algorithm: String,
143}
144
145/// Encryption information for a specific database column.
146#[derive(Debug, Clone)]
147#[non_exhaustive]
148pub struct ColumnEncryptionInfo {
149 /// The column name.
150 pub column_name: String,
151 /// The ordinal position (1-based).
152 pub column_ordinal: u16,
153 /// Whether this column is encrypted.
154 pub is_encrypted: bool,
155 /// The encryption type (if encrypted).
156 pub encryption_type: Option<EncryptionType>,
157 /// The encryption algorithm name.
158 pub encryption_algorithm: Option<String>,
159 /// CEK metadata (if encrypted).
160 pub cek_metadata: Option<CekMetadata>,
161}
162
163impl ColumnEncryptionInfo {
164 /// Create info for a non-encrypted column.
165 #[must_use]
166 pub fn unencrypted(column_name: impl Into<String>, column_ordinal: u16) -> Self {
167 Self {
168 column_name: column_name.into(),
169 column_ordinal,
170 is_encrypted: false,
171 encryption_type: None,
172 encryption_algorithm: None,
173 cek_metadata: None,
174 }
175 }
176
177 /// Create info for an encrypted column.
178 #[must_use]
179 pub fn encrypted(
180 column_name: impl Into<String>,
181 column_ordinal: u16,
182 encryption_type: EncryptionType,
183 cek_metadata: CekMetadata,
184 ) -> Self {
185 Self {
186 column_name: column_name.into(),
187 column_ordinal,
188 is_encrypted: true,
189 encryption_type: Some(encryption_type),
190 encryption_algorithm: Some(encryption_type.algorithm_name().to_string()),
191 cek_metadata: Some(cek_metadata),
192 }
193 }
194}
195
196/// Error types for Always Encrypted operations.
197#[derive(Debug)]
198#[non_exhaustive]
199pub enum EncryptionError {
200 /// The requested key store provider is not registered.
201 KeyStoreNotFound(String),
202 /// Failed to retrieve or unwrap the Column Master Key.
203 CmkError(String),
204 /// Failed to decrypt the Column Encryption Key.
205 CekDecryptionFailed(String),
206 /// Failed to encrypt data.
207 EncryptionFailed(String),
208 /// Failed to decrypt data.
209 DecryptionFailed(String),
210 /// The column's encryption metadata is not available.
211 MetadataNotAvailable(String),
212 /// The requested operation is not supported with this encryption type.
213 UnsupportedOperation(String),
214 /// Configuration error.
215 ConfigurationError(String),
216}
217
218impl std::error::Error for EncryptionError {}
219
220impl fmt::Display for EncryptionError {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 match self {
223 EncryptionError::KeyStoreNotFound(name) => {
224 write!(f, "Key store provider not found: {name}")
225 }
226 EncryptionError::CmkError(msg) => {
227 write!(f, "Column Master Key error: {msg}")
228 }
229 EncryptionError::CekDecryptionFailed(msg) => {
230 write!(f, "Failed to decrypt Column Encryption Key: {msg}")
231 }
232 EncryptionError::EncryptionFailed(msg) => {
233 write!(f, "Encryption failed: {msg}")
234 }
235 EncryptionError::DecryptionFailed(msg) => {
236 write!(f, "Decryption failed: {msg}")
237 }
238 EncryptionError::MetadataNotAvailable(msg) => {
239 write!(f, "Encryption metadata not available: {msg}")
240 }
241 EncryptionError::UnsupportedOperation(msg) => {
242 write!(f, "Unsupported operation with encryption: {msg}")
243 }
244 EncryptionError::ConfigurationError(msg) => {
245 write!(f, "Encryption configuration error: {msg}")
246 }
247 }
248 }
249}
250
251/// Trait for Column Master Key (CMK) providers.
252///
253/// Implementations of this trait provide access to CMKs stored in various
254/// key stores (Azure Key Vault, Windows Certificate Store, HSMs, etc.).
255///
256/// # Security
257///
258/// Implementations must ensure:
259/// - Keys are never logged or exposed in error messages
260/// - Keys are zeroized from memory when no longer needed
261/// - Access is authenticated and authorized appropriately
262///
263/// # Example
264///
265/// ```rust,ignore
266/// use mssql_auth::encryption::{KeyStoreProvider, EncryptionError};
267///
268/// struct AzureKeyVaultProvider {
269/// vault_url: String,
270/// credential: azure_identity::DefaultAzureCredential,
271/// }
272///
273/// #[async_trait::async_trait]
274/// impl KeyStoreProvider for AzureKeyVaultProvider {
275/// fn provider_name(&self) -> &str {
276/// "AZURE_KEY_VAULT"
277/// }
278///
279/// async fn decrypt_cek(
280/// &self,
281/// cmk_path: &str,
282/// algorithm: &str,
283/// encrypted_cek: &[u8],
284/// ) -> Result<Vec<u8>, EncryptionError> {
285/// // Use Azure Key Vault to unwrap the CEK
286/// // ...
287/// }
288/// }
289/// ```
290#[async_trait::async_trait]
291pub trait KeyStoreProvider: Send + Sync {
292 /// Returns the provider name as used in SQL Server metadata.
293 ///
294 /// Common values:
295 /// - `"AZURE_KEY_VAULT"` - Azure Key Vault
296 /// - `"MSSQL_CERTIFICATE_STORE"` - Windows Certificate Store
297 /// - `"MSSQL_CNG_STORE"` - Windows CNG Store
298 /// - `"MSSQL_CSP_PROVIDER"` - Windows CSP Provider
299 fn provider_name(&self) -> &str;
300
301 /// Decrypt a Column Encryption Key (CEK) using the Column Master Key (CMK).
302 ///
303 /// # Arguments
304 ///
305 /// * `cmk_path` - Path to the CMK in the key store
306 /// * `algorithm` - The asymmetric algorithm (e.g., "RSA_OAEP")
307 /// * `encrypted_cek` - The encrypted CEK bytes
308 ///
309 /// # Returns
310 ///
311 /// The decrypted CEK bytes, which can then be used for data encryption/decryption.
312 ///
313 /// # Errors
314 ///
315 /// Returns an error if the key cannot be found or decryption fails.
316 async fn decrypt_cek(
317 &self,
318 cmk_path: &str,
319 algorithm: &str,
320 encrypted_cek: &[u8],
321 ) -> Result<Vec<u8>, EncryptionError>;
322
323 /// Sign data using the Column Master Key (optional).
324 ///
325 /// This is used for key attestation in Secure Enclaves.
326 /// Default implementation returns an error indicating it's not supported.
327 async fn sign_data(&self, _cmk_path: &str, _data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
328 Err(EncryptionError::UnsupportedOperation(
329 "Signing not supported by this key store provider".into(),
330 ))
331 }
332
333 /// Verify a signature (optional).
334 ///
335 /// This is used for key attestation in Secure Enclaves.
336 /// Default implementation returns an error indicating it's not supported.
337 async fn verify_signature(
338 &self,
339 _cmk_path: &str,
340 _data: &[u8],
341 _signature: &[u8],
342 ) -> Result<bool, EncryptionError> {
343 Err(EncryptionError::UnsupportedOperation(
344 "Signature verification not supported by this key store provider".into(),
345 ))
346 }
347}
348
349/// Configuration for Always Encrypted.
350#[derive(Default)]
351pub struct ColumnEncryptionConfig {
352 /// Whether column encryption is enabled.
353 pub enabled: bool,
354 /// Registered key store providers.
355 providers: Vec<Box<dyn KeyStoreProvider>>,
356 /// Cache decrypted CEKs (performance optimization).
357 pub cache_ceks: bool,
358 /// Allow unsafe operations (e.g., queries on encrypted columns without parameterization).
359 pub allow_unsafe_operations: bool,
360}
361
362impl ColumnEncryptionConfig {
363 /// Create a new configuration with encryption enabled.
364 #[must_use]
365 pub fn new() -> Self {
366 Self {
367 enabled: true,
368 providers: Vec::new(),
369 cache_ceks: true,
370 allow_unsafe_operations: false,
371 }
372 }
373
374 /// Register a key store provider.
375 ///
376 /// Multiple providers can be registered to support different key stores.
377 pub fn register_provider(&mut self, provider: impl KeyStoreProvider + 'static) {
378 self.providers.push(Box::new(provider));
379 }
380
381 /// Builder method to add a key store provider.
382 #[must_use]
383 pub fn with_provider(mut self, provider: impl KeyStoreProvider + 'static) -> Self {
384 self.register_provider(provider);
385 self
386 }
387
388 /// Builder method to control CEK caching.
389 #[must_use]
390 pub fn with_cek_caching(mut self, enabled: bool) -> Self {
391 self.cache_ceks = enabled;
392 self
393 }
394
395 /// Get a provider by name.
396 pub fn get_provider(&self, name: &str) -> Option<&dyn KeyStoreProvider> {
397 self.providers
398 .iter()
399 .find(|p| p.provider_name() == name)
400 .map(|p| p.as_ref())
401 }
402
403 /// Check if encryption is enabled and providers are available.
404 #[must_use]
405 pub fn is_ready(&self) -> bool {
406 self.enabled && !self.providers.is_empty()
407 }
408}
409
410impl fmt::Debug for ColumnEncryptionConfig {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 f.debug_struct("ColumnEncryptionConfig")
413 .field("enabled", &self.enabled)
414 .field(
415 "providers",
416 &self
417 .providers
418 .iter()
419 .map(|p| p.provider_name())
420 .collect::<Vec<_>>(),
421 )
422 .field("cache_ceks", &self.cache_ceks)
423 .field("allow_unsafe_operations", &self.allow_unsafe_operations)
424 .finish()
425 }
426}
427
428/// Represents an encrypted value with its metadata.
429///
430/// This is used internally to track encrypted parameter values.
431#[derive(Debug, Clone)]
432#[non_exhaustive]
433pub struct EncryptedValue {
434 /// The ciphertext bytes.
435 pub ciphertext: Vec<u8>,
436 /// The CEK ID used for encryption.
437 pub cek_id: u32,
438 /// The encryption type.
439 pub encryption_type: EncryptionType,
440}
441
442#[cfg(test)]
443#[allow(clippy::unwrap_used, clippy::expect_used)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_encryption_type_algorithm_names() {
449 assert_eq!(
450 EncryptionType::Deterministic.algorithm_name(),
451 "AEAD_AES_256_CBC_HMAC_SHA_256_DETERMINISTIC"
452 );
453 assert_eq!(
454 EncryptionType::Randomized.algorithm_name(),
455 "AEAD_AES_256_CBC_HMAC_SHA_256_RANDOMIZED"
456 );
457 }
458
459 #[test]
460 fn test_encryption_type_from_sys_columns() {
461 assert_eq!(
462 EncryptionType::from_sys_columns_value(1),
463 Some(EncryptionType::Deterministic)
464 );
465 assert_eq!(
466 EncryptionType::from_sys_columns_value(2),
467 Some(EncryptionType::Randomized)
468 );
469 assert_eq!(EncryptionType::from_sys_columns_value(0), None);
470 assert_eq!(EncryptionType::from_sys_columns_value(99), None);
471 }
472
473 #[test]
474 fn test_column_encryption_info_unencrypted() {
475 let info = ColumnEncryptionInfo::unencrypted("name", 1);
476 assert!(!info.is_encrypted);
477 assert!(info.encryption_type.is_none());
478 assert!(info.cek_metadata.is_none());
479 }
480
481 #[test]
482 fn test_column_encryption_config_debug() {
483 let config = ColumnEncryptionConfig::new();
484 let debug = format!("{config:?}");
485 assert!(debug.contains("ColumnEncryptionConfig"));
486 assert!(debug.contains("enabled: true"));
487 }
488
489 #[test]
490 fn test_encryption_error_display() {
491 let error = EncryptionError::KeyStoreNotFound("AZURE_KEY_VAULT".into());
492 assert!(error.to_string().contains("AZURE_KEY_VAULT"));
493
494 let error = EncryptionError::EncryptionFailed("test error".into());
495 assert!(error.to_string().contains("test error"));
496 }
497}