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}