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