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; } = "";
}
}