using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using MarketAlly.AIPlugin.Context.Configuration; namespace MarketAlly.AIPlugin.Context.Security { /// /// Provides encryption and security features for context storage /// public class EncryptedContextStorage { private readonly ContextConfiguration _configuration; private readonly ILogger _logger; private readonly SensitiveDataDetector _sensitiveDataDetector; private readonly byte[] _encryptionKey; private readonly AesCryptoServiceProvider _aes; public EncryptedContextStorage(ContextConfiguration configuration, ILogger logger) { _configuration = configuration; _logger = logger; _sensitiveDataDetector = new SensitiveDataDetector(configuration.Security.SensitiveDataPatterns); // Initialize encryption key _encryptionKey = DeriveEncryptionKey(configuration.Security.EncryptionKey); _aes = new AesCryptoServiceProvider { Key = _encryptionKey, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 }; _logger.LogInformation("Encrypted context storage initialized with {SecurityLevel} security level", configuration.Security.EnableEncryption ? "High" : "Standard"); } /// /// Processes context content for security, encrypting sensitive data if configured /// public async Task SecureContextAsync(StoredContextEntry entry) { var secureEntry = new SecureContextEntry { Id = entry.Id, Type = entry.Type, Summary = entry.Summary, Tags = entry.Tags, ProjectPath = entry.ProjectPath, Priority = entry.Priority, Timestamp = entry.Timestamp, Metadata = entry.Metadata, IsEncrypted = false, SensitiveDataDetected = false }; try { // Detect sensitive data var sensitiveItems = await _sensitiveDataDetector.DetectSensitiveDataAsync(entry.Content); secureEntry.SensitiveDataDetected = sensitiveItems.Any(); if (sensitiveItems.Any()) { _logger.LogWarning("Detected {Count} sensitive data patterns in context entry {EntryId}", sensitiveItems.Count, entry.Id); secureEntry.SensitiveDataTypes = sensitiveItems.Select(i => i.Type).Distinct().ToList(); } // Encrypt content if encryption is enabled and sensitive data is detected if (_configuration.Security.EnableEncryption && (secureEntry.SensitiveDataDetected || _configuration.Security.AutoEncryptSensitiveData)) { secureEntry.Content = await EncryptContentAsync(entry.Content); secureEntry.IsEncrypted = true; _logger.LogDebug("Encrypted content for context entry {EntryId}", entry.Id); } else { // If not encrypting, optionally redact sensitive data if (secureEntry.SensitiveDataDetected && _configuration.Security.EnableSensitiveDataDetection) { secureEntry.Content = RedactSensitiveData(entry.Content, sensitiveItems); secureEntry.ContentRedacted = true; } else { secureEntry.Content = entry.Content; } } return secureEntry; } catch (Exception ex) { _logger.LogError(ex, "Failed to secure context entry {EntryId}", entry.Id); // Fallback: return entry with content redacted if there was an error secureEntry.Content = "[CONTENT PROCESSING ERROR]"; secureEntry.ProcessingError = ex.Message; return secureEntry; } } /// /// Decrypts and returns the original context entry /// public async Task UnsecureContextAsync(SecureContextEntry secureEntry) { var entry = new StoredContextEntry { Id = secureEntry.Id, Type = secureEntry.Type, Summary = secureEntry.Summary, Tags = secureEntry.Tags, ProjectPath = secureEntry.ProjectPath, Priority = secureEntry.Priority, Timestamp = secureEntry.Timestamp, Metadata = secureEntry.Metadata }; try { if (secureEntry.IsEncrypted) { entry.Content = await DecryptContentAsync(secureEntry.Content); _logger.LogDebug("Decrypted content for context entry {EntryId}", entry.Id); } else { entry.Content = secureEntry.Content; } return entry; } catch (Exception ex) { _logger.LogError(ex, "Failed to decrypt context entry {EntryId}", entry.Id); // Return entry with error message if decryption fails entry.Content = $"[DECRYPTION ERROR: {ex.Message}]"; return entry; } } /// /// Encrypts content using AES encryption /// private async Task EncryptContentAsync(string content) { if (string.IsNullOrEmpty(content)) return content; try { var contentBytes = Encoding.UTF8.GetBytes(content); using var encryptor = _aes.CreateEncryptor(); using var msEncrypt = new MemoryStream(); // Write IV to the beginning of the stream await msEncrypt.WriteAsync(_aes.IV, 0, _aes.IV.Length); using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { await csEncrypt.WriteAsync(contentBytes, 0, contentBytes.Length); } var encryptedBytes = msEncrypt.ToArray(); return Convert.ToBase64String(encryptedBytes); } finally { // Generate new IV for next encryption _aes.GenerateIV(); } } /// /// Decrypts content using AES decryption /// private async Task DecryptContentAsync(string encryptedContent) { if (string.IsNullOrEmpty(encryptedContent)) return encryptedContent; var encryptedBytes = Convert.FromBase64String(encryptedContent); using var msDecrypt = new MemoryStream(encryptedBytes); // Read IV from the beginning of the stream var iv = new byte[_aes.IV.Length]; await msDecrypt.ReadAsync(iv, 0, iv.Length); using var decryptor = _aes.CreateDecryptor(_aes.Key, iv); using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); using var srDecrypt = new StreamReader(csDecrypt); return await srDecrypt.ReadToEndAsync(); } /// /// Redacts sensitive data from content /// private string RedactSensitiveData(string content, IEnumerable sensitiveItems) { var redactedContent = content; foreach (var item in sensitiveItems.OrderByDescending(i => i.StartIndex)) { var redactionText = $"[REDACTED:{item.Type}]"; redactedContent = redactedContent.Remove(item.StartIndex, item.Length) .Insert(item.StartIndex, redactionText); } return redactedContent; } /// /// Derives an encryption key from the configuration /// private byte[] DeriveEncryptionKey(string? configuredKey) { if (!string.IsNullOrEmpty(configuredKey)) { // Use PBKDF2 to derive a proper key from the configured key using var pbkdf2 = new Rfc2898DeriveBytes( configuredKey, Encoding.UTF8.GetBytes("MarketAlly.Context.Salt"), 10000, HashAlgorithmName.SHA256); return pbkdf2.GetBytes(32); // 256-bit key } else { // Generate a random key (this should be stored securely in production) _logger.LogWarning("No encryption key configured, using randomly generated key. " + "This key will not persist across application restarts."); var key = new byte[32]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(key); return key; } } /// /// Validates the integrity of encrypted data /// public async Task ValidateSecurityAsync(SecureContextEntry entry) { var result = new SecurityValidationResult { EntryId = entry.Id, IsValid = true, ValidationTime = DateTime.UtcNow }; try { // Test decryption if entry is encrypted if (entry.IsEncrypted) { var decryptedContent = await DecryptContentAsync(entry.Content); result.CanDecrypt = !string.IsNullOrEmpty(decryptedContent) && !decryptedContent.StartsWith("[DECRYPTION ERROR"); if (!result.CanDecrypt) { result.IsValid = false; result.ValidationErrors.Add("Content cannot be decrypted"); } } // Validate sensitive data detection consistency if (entry.SensitiveDataDetected && !entry.SensitiveDataTypes.Any()) { result.ValidationWarnings.Add("Sensitive data detected but no types specified"); } // Check for processing errors if (!string.IsNullOrEmpty(entry.ProcessingError)) { result.IsValid = false; result.ValidationErrors.Add($"Processing error: {entry.ProcessingError}"); } return result; } catch (Exception ex) { _logger.LogError(ex, "Security validation failed for entry {EntryId}", entry.Id); result.IsValid = false; result.ValidationErrors.Add($"Validation exception: {ex.Message}"); return result; } } /// /// Gets security statistics for monitoring /// public SecurityStatistics GetSecurityStatistics() { return new SecurityStatistics { EncryptionEnabled = _configuration.Security.EnableEncryption, SensitiveDataDetectionEnabled = _configuration.Security.EnableSensitiveDataDetection, AutoEncryptionEnabled = _configuration.Security.AutoEncryptSensitiveData, DetectionPatterns = _configuration.Security.SensitiveDataPatterns.Count, EncryptionAlgorithm = "AES-256-CBC" }; } public void Dispose() { _aes?.Dispose(); } } /// /// Detects sensitive data in content using configurable patterns /// public class SensitiveDataDetector { private readonly List _patterns; public SensitiveDataDetector(IEnumerable patternStrings) { _patterns = patternStrings.Select((pattern, index) => new SensitiveDataPattern { Type = GetPatternType(pattern), Regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled), Pattern = pattern }).ToList(); } /// /// Detects sensitive data in the given content /// public async Task> DetectSensitiveDataAsync(string content) { var items = new List(); if (string.IsNullOrEmpty(content)) return items; await Task.Run(() => { foreach (var pattern in _patterns) { var matches = pattern.Regex.Matches(content); foreach (Match match in matches) { items.Add(new SensitiveDataItem { Type = pattern.Type, StartIndex = match.Index, Length = match.Length, Value = match.Value, Pattern = pattern.Pattern }); } } }); return items.OrderBy(i => i.StartIndex).ToList(); } /// /// Determines the pattern type from the regex pattern /// private string GetPatternType(string pattern) { return pattern switch { var p when p.Contains("@") => "Email", var p when p.Contains("\\d{3}-\\d{2}-\\d{4}") => "SSN", var p when p.Contains("\\d{4}") && p.Contains("[-\\s]") => "CreditCard", var p when p.Contains("[A-Za-z0-9+/]{40,}") => "APIKey", var p when p.Contains("bearer") => "BearerToken", var p when p.Contains("password") => "Password", _ => "Unknown" }; } } /// /// Secure context entry with encryption and security metadata /// public class SecureContextEntry { public string Id { get; set; } = ""; public string Type { get; set; } = ""; public string Content { get; set; } = ""; public string Summary { get; set; } = ""; public List Tags { get; set; } = new(); public string ProjectPath { get; set; } = ""; public string Priority { get; set; } = ""; public DateTime Timestamp { get; set; } public Dictionary Metadata { get; set; } = new(); // Security properties public bool IsEncrypted { get; set; } public bool SensitiveDataDetected { get; set; } public bool ContentRedacted { get; set; } public List SensitiveDataTypes { get; set; } = new(); public string? ProcessingError { get; set; } } /// /// Pattern for detecting sensitive data /// public class SensitiveDataPattern { public string Type { get; set; } = ""; public Regex Regex { get; set; } = null!; public string Pattern { get; set; } = ""; } /// /// Detected sensitive data item /// public class SensitiveDataItem { public string Type { get; set; } = ""; public int StartIndex { get; set; } public int Length { get; set; } public string Value { get; set; } = ""; public string Pattern { get; set; } = ""; } /// /// Result of security validation /// public class SecurityValidationResult { public string EntryId { get; set; } = ""; public bool IsValid { get; set; } public bool CanDecrypt { get; set; } public DateTime ValidationTime { get; set; } public List ValidationErrors { get; set; } = new(); public List ValidationWarnings { get; set; } = new(); } /// /// Security configuration and statistics /// public class SecurityStatistics { public bool EncryptionEnabled { get; set; } public bool SensitiveDataDetectionEnabled { get; set; } public bool AutoEncryptionEnabled { get; set; } public int DetectionPatterns { get; set; } public string EncryptionAlgorithm { get; set; } = ""; } }