Files
Wino-Mail/Wino.Mail.ViewModels/SignatureAndEncryptionPageViewModel.cs
T

239 lines
8.4 KiB
C#
Raw Normal View History

2025-11-23 20:56:57 +01:00
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Common;
namespace Wino.Mail.ViewModels;
public partial class SignatureAndEncryptionPageViewModel : MailBaseViewModel
{
private readonly ISmimeCertificateService _smimeCertificateService;
private readonly IDialogServiceBase _dialogService;
private readonly IFileService _fileService;
public ObservableCollection<X509Certificate2> PersonalCertificates { get; } = [];
public ObservableCollection<X509Certificate2> RecipientCertificates { get; } = [];
public List<X509Certificate2> SelectedPersonalCertificates { get; } = [];
public List<X509Certificate2> SelectedRecipientCertificates { get; } = [];
public bool PersonalCertificatesEmpty => PersonalCertificates.Count == 0;
public SignatureAndEncryptionPageViewModel(
IDialogServiceBase dialogService,
ISmimeCertificateService smimeCertificateService,
IFileService fileService
)
{
_dialogService = dialogService;
_fileService = fileService;
_smimeCertificateService = smimeCertificateService;
PersonalCertificates.CollectionChanged += (s, e) => { OnPropertyChanged(nameof(PersonalCertificatesEmpty)); };
LoadAllCertificates();
}
private void LoadAllCertificates()
{
PersonalCertificates.Clear();
var personalCerts = _smimeCertificateService.GetCertificates();
foreach (var cert in personalCerts)
{
PersonalCertificates.Add(cert);
}
// Recipient certificates
RecipientCertificates.Clear();
var recipientCerts = _smimeCertificateService.GetCertificates(storeName: StoreName.AddressBook);
foreach (var cert in recipientCerts)
{
RecipientCertificates.Add(cert);
}
}
[RelayCommand]
public async Task ImportPersonalCertificatesAsync()
{
await ImportCertificates(StoreName.My);
}
[RelayCommand]
public async Task ImportRecipientCertificatesAsync()
{
await ImportCertificates(StoreName.AddressBook);
}
private async Task ImportCertificates(StoreName storeName)
{
var files = await PickCertificateFilesAsync();
var failedImports = new List<string>();
var successCount = 0;
foreach (var file in files)
{
string password = null;
if (file.FileExtension.Equals(".pfx") || file.FileExtension.Equals(".p12"))
{
password = await PromptForPasswordAsync(file.FileName);
}
try
{
_smimeCertificateService.ImportCertificate(file.FileExtension, file.Data, password,
storeName: storeName);
successCount++;
}
catch (Exception ex)
{
failedImports.Add($"{file.FileName}: {ex.Message}");
}
}
LoadAllCertificates();
if (successCount > 0)
{
_dialogService.InfoBarMessage(
string.Format(Translator.Smime_ImportCertificates_Success),
Translator.GeneralTitle_Info,
InfoBarMessageType.Success);
}
if (failedImports.Count > 0)
{
await _dialogService.ShowMessageAsync(
$"{Translator.Smime_ImportCertificates_Error}\n\n{string.Join("\n", failedImports)}",
Translator.GeneralTitle_Warning,
Core.Domain.Enums.WinoCustomMessageDialogIcon.Warning);
}
}
[RelayCommand]
public async Task RemovePersonalCertificatesAsync()
{
await RemoveCertificatesAsync(SelectedPersonalCertificates, StoreName.My);
}
[RelayCommand]
public async Task RemoveRecipientCertificatesAsync()
{
await RemoveCertificatesAsync(SelectedRecipientCertificates, StoreName.AddressBook);
}
private async Task RemoveCertificatesAsync(List<X509Certificate2> certificates, StoreName storeName)
{
if (certificates.Any())
{
var confirm = await ConfirmAsync(string.Format(Translator.Smime_RemoveCertificates_Confirm,
string.Join(", ", certificates.Select(cert => cert.Subject))));
if (confirm)
{
foreach (var cert in certificates)
{
_smimeCertificateService.RemoveCertificate(cert.Thumbprint, storeName: storeName);
}
LoadAllCertificates();
_dialogService.InfoBarMessage(
Translator.Smime_RemoveCertificates_Success,
Translator.GeneralTitle_Info,
InfoBarMessageType.Success
);
}
}
}
[RelayCommand]
public async Task ExportPersonalCertificatesAsync()
{
await ExportCertificatesAsync(SelectedPersonalCertificates);
}
[RelayCommand]
public async Task ExportRecipientCertificatesAsync()
{
await ExportCertificatesAsync(SelectedRecipientCertificates);
}
// Export logic for .cer or .pem
private async Task ExportCertificatesAsync(IEnumerable<X509Certificate2> cert)
{
var failedExports = new List<string>();
var successCount = 0;
foreach (var certificate in cert)
{
var fileName = $"{certificate.Subject.Replace("CN=", "")}.cer";
var path = await _dialogService.PickFilePathAsync(fileName);
if (path != null)
{
var folderPath = System.IO.Path.GetDirectoryName(path);
await using var stream = await _fileService.GetFileStreamAsync(folderPath, fileName);
if (stream != null)
{
try
{
var certificateData = certificate.Export(X509ContentType.Cert);
await stream.WriteAsync(certificateData, 0, certificateData.Length);
await stream.FlushAsync();
successCount++;
}
catch (Exception ex)
{
failedExports.Add($"{certificate.Subject}: {ex.Message}");
}
}
else
{
failedExports.Add($"{certificate.Subject}: File stream error");
}
}
}
if (successCount > 0)
{
_dialogService.InfoBarMessage(
Translator.Smime_ExportCertificates_Success,
Translator.GeneralTitle_Info,
InfoBarMessageType.Success
);
}
if (failedExports.Count > 0)
{
await _dialogService.ShowMessageAsync(
$"{Translator.Smime_ExportCertificates_Error}\n\n{string.Join("\n", failedExports)}",
Translator.GeneralTitle_Warning,
Core.Domain.Enums.WinoCustomMessageDialogIcon.Warning);
}
}
private async Task ShowCertificateDetailsAsync(X509Certificate2 cert)
{
var details = string.Format(Translator.Smime_CertificateDetails, cert.Subject, cert.Issuer, cert.NotBefore,
cert.NotAfter, cert.Thumbprint);
await _dialogService.ShowMessageAsync(details, Translator.GeneralTitle_Info,
Core.Domain.Enums.WinoCustomMessageDialogIcon.Information);
}
// Confirmation dialog
private async Task<bool> ConfirmAsync(string message)
{
return await _dialogService.ShowConfirmationDialogAsync(message, Translator.Smime_Confirm_Title,
Translator.Buttons_Yes);
}
// File picker for importing certificates
private async Task<List<SharedFile>> PickCertificateFilesAsync()
{
return await _dialogService.PickFilesAsync(".pfx", ".p12", ".cer", ".crt");
}
// Ask for password for .pfx/.p12
private async Task<string> PromptForPasswordAsync(string fileName)
{
return await _dialogService.ShowTextInputDialogAsync("",
Translator.Smime_CertificatePassword_Title,
string.Format(Translator.Smime_CertificatePassword_Placeholder, fileName), Translator.Buttons_OK);
}
}