## Critical Bug Fixes ### Currency Display (£ vs $) - Fix MenuBuilder.cs: Replace $ with £ for product prices (line 60) and order totals (line 329) - Fix ProductCarouselService.cs: Replace $ with £ in product captions and multi-buy offers (lines 317, 325) - Fix CallbackHandler.cs: Replace $ with £ in order confirmation message (line 800) ### Payment Amount Display Bug - Fix MessageFormatter.cs: Remove flawed crypto detection logic (< 1.0m check) - Bug: Order for £700 in ETH displayed as "£1.66" instead of "1.66 ETH" - Root cause: RequiredAmount is always stored as crypto amount, not fiat - Solution: Always display RequiredAmount with crypto symbol - Impact: Fixes display for XMR, DOGE, LTC, and large ETH amounts ## Security: Remove PGP Encryption Feature ### Critical Security Issue Resolved - PGP "encryption" was only Base64 encoding - NOT real encryption - Shipping addresses stored as easily decoded text - False sense of security for users ### Changes Made - Mark EncryptWithPGP method as [Obsolete] in PrivacyService.cs - Remove PGP encryption logic from order creation (LittleShopService.cs) - Mark PGP properties as [Obsolete] in UserSession.cs models - Disable EnablePGPEncryption feature flag in appsettings.json - Add comments explaining feature removal ### Recommendation Implement proper PGP encryption using BouncyCastle in future, or keep removed. ## Testing Required - Verify all prices display with £ symbol - Verify crypto payments show correct amount format (e.g., "1.66000000 ETH") - Verify no PGP options appear in UI - Test order creation without PGP encryption 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
181 lines
6.6 KiB
C#
181 lines
6.6 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using PgpCore;
|
|
|
|
namespace TeleBot.Services
|
|
{
|
|
public interface IPrivacyService
|
|
{
|
|
string HashIdentifier(long telegramId);
|
|
string GenerateAnonymousReference();
|
|
Task<string?> EncryptWithPGP(string data, string publicKey);
|
|
Task<HttpClient> CreateTorHttpClient();
|
|
byte[] EncryptData(byte[] data, string key);
|
|
byte[] DecryptData(byte[] encryptedData, string key);
|
|
void SanitizeLogMessage(ref string message);
|
|
}
|
|
|
|
public class PrivacyService : IPrivacyService
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILogger<PrivacyService> _logger;
|
|
private readonly string _salt;
|
|
|
|
public PrivacyService(IConfiguration configuration, ILogger<PrivacyService> logger)
|
|
{
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
_salt = configuration["Privacy:HashSalt"] ?? "TeleBot-Privacy-Salt-2024";
|
|
}
|
|
|
|
public string HashIdentifier(long telegramId)
|
|
{
|
|
using var sha256 = SHA256.Create();
|
|
var bytes = Encoding.UTF8.GetBytes($"{telegramId}:{_salt}");
|
|
var hash = sha256.ComputeHash(bytes);
|
|
return Convert.ToBase64String(hash);
|
|
}
|
|
|
|
public string GenerateAnonymousReference()
|
|
{
|
|
// Generate a random reference that can't be linked back to user
|
|
var bytes = new byte[16];
|
|
using var rng = RandomNumberGenerator.Create();
|
|
rng.GetBytes(bytes);
|
|
|
|
var reference = Convert.ToBase64String(bytes)
|
|
.Replace("+", "")
|
|
.Replace("/", "")
|
|
.Replace("=", "")
|
|
.Substring(0, 12)
|
|
.ToUpper();
|
|
|
|
return $"ANON-{reference}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// ⚠️ REMOVED: PGP ENCRYPTION FEATURE HAS BEEN REMOVED ⚠️
|
|
/// This method was not properly implemented and provided a false sense of security.
|
|
/// It only performed Base64 encoding, not real encryption.
|
|
/// Feature removed to eliminate security vulnerability.
|
|
/// </summary>
|
|
[Obsolete("PGP encryption feature removed - was not properly implemented")]
|
|
public async Task<string?> EncryptWithPGP(string data, string publicKey)
|
|
{
|
|
_logger.LogWarning("⚠️ PGP encryption feature has been removed - returning null");
|
|
await Task.CompletedTask;
|
|
return null;
|
|
}
|
|
|
|
public Task<HttpClient> CreateTorHttpClient()
|
|
{
|
|
if (!_configuration.GetValue<bool>("Privacy:EnableTor"))
|
|
{
|
|
// Return regular HttpClient if Tor is disabled
|
|
return Task.FromResult(new HttpClient());
|
|
}
|
|
|
|
try
|
|
{
|
|
// Use existing Tor SOCKS proxy if available
|
|
var torPort = _configuration.GetValue<int>("Privacy:TorSocksPort", 9050);
|
|
var proxy = new WebProxy($"socks5://localhost:{torPort}");
|
|
var handler = new HttpClientHandler
|
|
{
|
|
Proxy = proxy,
|
|
UseProxy = true
|
|
};
|
|
|
|
return Task.FromResult(new HttpClient(handler));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to create Tor HTTP client, falling back to regular client");
|
|
return Task.FromResult(new HttpClient());
|
|
}
|
|
}
|
|
|
|
public byte[] EncryptData(byte[] data, string key)
|
|
{
|
|
using var aes = Aes.Create();
|
|
aes.Mode = CipherMode.CBC;
|
|
|
|
// Derive key from string
|
|
using var sha256 = SHA256.Create();
|
|
aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
|
|
|
|
var nonce = new byte[12];
|
|
using var rng = RandomNumberGenerator.Create();
|
|
rng.GetBytes(nonce);
|
|
aes.IV = nonce;
|
|
|
|
using var encryptor = aes.CreateEncryptor();
|
|
var encrypted = encryptor.TransformFinalBlock(data, 0, data.Length);
|
|
|
|
// Combine nonce and encrypted data
|
|
var result = new byte[nonce.Length + encrypted.Length];
|
|
Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length);
|
|
Buffer.BlockCopy(encrypted, 0, result, nonce.Length, encrypted.Length);
|
|
|
|
return result;
|
|
}
|
|
|
|
public byte[] DecryptData(byte[] encryptedData, string key)
|
|
{
|
|
using var aes = Aes.Create();
|
|
aes.Mode = CipherMode.CBC;
|
|
|
|
// Derive key from string
|
|
using var sha256 = SHA256.Create();
|
|
aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
|
|
|
|
// Extract nonce
|
|
var nonce = new byte[12];
|
|
Buffer.BlockCopy(encryptedData, 0, nonce, 0, nonce.Length);
|
|
aes.IV = nonce;
|
|
|
|
// Extract encrypted portion
|
|
var encrypted = new byte[encryptedData.Length - nonce.Length];
|
|
Buffer.BlockCopy(encryptedData, nonce.Length, encrypted, 0, encrypted.Length);
|
|
|
|
using var decryptor = aes.CreateDecryptor();
|
|
return decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length);
|
|
}
|
|
|
|
public void SanitizeLogMessage(ref string message)
|
|
{
|
|
// Remove potential PII from log messages
|
|
message = System.Text.RegularExpressions.Regex.Replace(
|
|
message,
|
|
@"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
|
|
"[EMAIL_REDACTED]"
|
|
);
|
|
|
|
message = System.Text.RegularExpressions.Regex.Replace(
|
|
message,
|
|
@"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b",
|
|
"[PHONE_REDACTED]"
|
|
);
|
|
|
|
message = System.Text.RegularExpressions.Regex.Replace(
|
|
message,
|
|
@"\b\d{16}\b",
|
|
"[CARD_REDACTED]"
|
|
);
|
|
|
|
// Remove Telegram user IDs
|
|
message = System.Text.RegularExpressions.Regex.Replace(
|
|
message,
|
|
@"telegram_id[:=]\d+",
|
|
"telegram_id=[REDACTED]"
|
|
);
|
|
}
|
|
}
|
|
} |