Skip to main content

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}