Remove XApi, WinRM, LocalCmd, HTTP API, TLS probe, SQL deploy model, and deploy UI. Co-authored-by: Cursor <cursoragent@cursor.com>
650 lines
22 KiB
C#
650 lines
22 KiB
C#
using System.Security.Cryptography;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
using ZA.CoreService.ESBCertificateManager.Configuration;
|
||
using ZA.CoreService.ESBCertificateManager.Data;
|
||
using ZA.CoreService.ESBCertificateManager.Models;
|
||
using ZA.CoreService.ESBCertificateManager.Services;
|
||
|
||
namespace ZA.CoreService.ESBCertificateManager;
|
||
|
||
public partial class Form1 : Form
|
||
{
|
||
private readonly Color BackgroundColor = Color.FromArgb(10, 18, 34);
|
||
private readonly Color SidebarColor = Color.FromArgb(7, 23, 45);
|
||
private readonly Color CardColor = Color.FromArgb(16, 38, 65);
|
||
private readonly Color BorderColor = Color.FromArgb(36, 74, 117);
|
||
private readonly Color BlueColor = Color.FromArgb(0, 110, 182);
|
||
private readonly Color GoldColor = Color.FromArgb(208, 171, 57);
|
||
private readonly Color TextColor = Color.FromArgb(241, 245, 249);
|
||
private readonly Color MutedTextColor = Color.FromArgb(169, 184, 200);
|
||
private readonly Color GreenColor = Color.FromArgb(60, 203, 127);
|
||
private readonly Color RedColor = Color.FromArgb(239, 106, 106);
|
||
|
||
private readonly AppSettings _settings;
|
||
private readonly DeploymentOrchestrator _orchestrator;
|
||
private readonly SonicContainerDiscovery _sonicDiscovery;
|
||
|
||
private TextBox txtCertificatePath = null!;
|
||
private Label lblCertificateSubject = null!;
|
||
private Label lblCertificateIssuer = null!;
|
||
private Label lblCertificateExpiry = null!;
|
||
private Label lblCertificateFingerprint = null!;
|
||
private Label lblCertificateValidity = null!;
|
||
private Label lblStatus = null!;
|
||
private Label lblSonicStatus = null!;
|
||
private ComboBox cmbSonicConnection = null!;
|
||
private DataGridView dgvTargets = null!;
|
||
private Button btnRestartOnly = null!;
|
||
private Button btnCancelRun = null!;
|
||
|
||
private CertificateInfo? _loadedCertificateInfo;
|
||
private IReadOnlyList<DeploymentTarget> _loadedTargets = [];
|
||
private bool _isOperationRunning;
|
||
private CancellationTokenSource? _runCts;
|
||
|
||
public Form1()
|
||
{
|
||
InitializeComponent();
|
||
_settings = AppSettingsLoader.Load();
|
||
_orchestrator = new DeploymentOrchestrator(_settings);
|
||
_sonicDiscovery = new SonicContainerDiscovery(_settings.SonicConnections);
|
||
BuildUi();
|
||
Shown += async (_, _) => await LoadTargetsAsync();
|
||
}
|
||
|
||
private void BuildUi()
|
||
{
|
||
SuspendLayout();
|
||
Text = "ESB Certificate Manager";
|
||
FormBorderStyle = FormBorderStyle.Sizable;
|
||
StartPosition = FormStartPosition.CenterScreen;
|
||
MinimumSize = new Size(960, 640);
|
||
Size = new Size(1100, 720);
|
||
BackColor = BackgroundColor;
|
||
ForeColor = TextColor;
|
||
Font = new Font("Segoe UI", 9.5f);
|
||
|
||
Panel sidebar = new()
|
||
{
|
||
Dock = DockStyle.Left,
|
||
Width = 220,
|
||
BackColor = SidebarColor,
|
||
Padding = new Padding(16)
|
||
};
|
||
sidebar.Controls.Add(new Label
|
||
{
|
||
Text = "ESB Certificate\nManager",
|
||
Dock = DockStyle.Top,
|
||
Height = 70,
|
||
Font = new Font("Segoe UI Semibold", 14f),
|
||
ForeColor = GoldColor
|
||
});
|
||
sidebar.Controls.Add(new Label
|
||
{
|
||
Text = "1. Zertifikat erkennen\n2. Container neu starten",
|
||
Dock = DockStyle.Top,
|
||
Height = 80,
|
||
ForeColor = MutedTextColor
|
||
});
|
||
|
||
Panel main = new()
|
||
{
|
||
Dock = DockStyle.Fill,
|
||
Padding = new Padding(20),
|
||
BackColor = BackgroundColor
|
||
};
|
||
|
||
TableLayoutPanel layout = new()
|
||
{
|
||
Dock = DockStyle.Fill,
|
||
ColumnCount = 1,
|
||
RowCount = 4,
|
||
BackColor = BackgroundColor
|
||
};
|
||
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 160));
|
||
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 56));
|
||
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 70));
|
||
|
||
layout.Controls.Add(BuildCertificateCard(), 0, 0);
|
||
layout.Controls.Add(BuildSonicBar(), 0, 1);
|
||
layout.Controls.Add(BuildTargetsCard(), 0, 2);
|
||
layout.Controls.Add(BuildActionBar(), 0, 3);
|
||
|
||
main.Controls.Add(layout);
|
||
Controls.Add(main);
|
||
Controls.Add(sidebar);
|
||
ResumeLayout();
|
||
}
|
||
|
||
private Panel BuildCertificateCard()
|
||
{
|
||
Panel card = MakeCard();
|
||
card.Controls.Add(SectionTitle("Zertifikat erkennen", 12));
|
||
|
||
txtCertificatePath = new TextBox
|
||
{
|
||
Left = 16,
|
||
Top = 48,
|
||
Width = 620,
|
||
Height = 28,
|
||
ReadOnly = true,
|
||
BackColor = Color.FromArgb(12, 28, 48),
|
||
ForeColor = TextColor,
|
||
BorderStyle = BorderStyle.FixedSingle
|
||
};
|
||
|
||
Button btnSelect = MakeButton("Datei wählen…", BlueColor);
|
||
btnSelect.Left = 650;
|
||
btnSelect.Top = 46;
|
||
btnSelect.Width = 140;
|
||
btnSelect.Click += (_, _) => TrySelectCertificateFile();
|
||
|
||
lblCertificateSubject = MetaLabel(16, 90, "Subject: –");
|
||
lblCertificateIssuer = MetaLabel(16, 112, "Issuer: –");
|
||
lblCertificateExpiry = MetaLabel(400, 90, "Gültig bis: –");
|
||
lblCertificateFingerprint = MetaLabel(400, 112, "SHA-256: –");
|
||
lblCertificateValidity = MetaLabel(16, 134, "Status: –");
|
||
|
||
card.Controls.AddRange([
|
||
txtCertificatePath, btnSelect,
|
||
lblCertificateSubject, lblCertificateIssuer,
|
||
lblCertificateExpiry, lblCertificateFingerprint, lblCertificateValidity
|
||
]);
|
||
return card;
|
||
}
|
||
|
||
private Panel BuildSonicBar()
|
||
{
|
||
Panel bar = MakeCard();
|
||
Label lbl = new()
|
||
{
|
||
Text = "Sonic:",
|
||
Left = 16,
|
||
Top = 16,
|
||
AutoSize = true,
|
||
ForeColor = MutedTextColor
|
||
};
|
||
|
||
cmbSonicConnection = new ComboBox
|
||
{
|
||
Left = 70,
|
||
Top = 12,
|
||
Width = 200,
|
||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||
BackColor = Color.FromArgb(12, 28, 48),
|
||
ForeColor = TextColor,
|
||
FlatStyle = FlatStyle.Flat
|
||
};
|
||
foreach (SonicConnection c in _settings.SonicConnections)
|
||
{
|
||
cmbSonicConnection.Items.Add(c.Name);
|
||
}
|
||
if (cmbSonicConnection.Items.Count > 0)
|
||
{
|
||
cmbSonicConnection.SelectedIndex = 0;
|
||
}
|
||
|
||
Button btnLoad = MakeButton("Container laden", BlueColor);
|
||
btnLoad.Left = 290;
|
||
btnLoad.Top = 10;
|
||
btnLoad.Width = 140;
|
||
btnLoad.Click += async (_, _) => await LoadFromSonicAsync();
|
||
|
||
lblSonicStatus = new Label
|
||
{
|
||
Left = 450,
|
||
Top = 16,
|
||
AutoSize = true,
|
||
ForeColor = MutedTextColor,
|
||
Text = "MfApi"
|
||
};
|
||
|
||
bar.Controls.AddRange([lbl, cmbSonicConnection, btnLoad, lblSonicStatus]);
|
||
return bar;
|
||
}
|
||
|
||
private Panel BuildTargetsCard()
|
||
{
|
||
Panel card = MakeCard();
|
||
card.Controls.Add(SectionTitle("Ziele / Container", 12));
|
||
|
||
dgvTargets = new DataGridView
|
||
{
|
||
Left = 16,
|
||
Top = 44,
|
||
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
|
||
Width = 780,
|
||
Height = 220,
|
||
BackgroundColor = Color.FromArgb(12, 28, 48),
|
||
ForeColor = TextColor,
|
||
GridColor = BorderColor,
|
||
BorderStyle = BorderStyle.None,
|
||
RowHeadersVisible = false,
|
||
AllowUserToAddRows = false,
|
||
AllowUserToDeleteRows = false,
|
||
ReadOnly = false,
|
||
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||
EnableHeadersVisualStyles = false
|
||
};
|
||
dgvTargets.ColumnHeadersDefaultCellStyle.BackColor = SidebarColor;
|
||
dgvTargets.ColumnHeadersDefaultCellStyle.ForeColor = GoldColor;
|
||
dgvTargets.DefaultCellStyle.BackColor = Color.FromArgb(12, 28, 48);
|
||
dgvTargets.DefaultCellStyle.ForeColor = TextColor;
|
||
dgvTargets.DefaultCellStyle.SelectionBackColor = BlueColor;
|
||
|
||
dgvTargets.Columns.Add(new DataGridViewCheckBoxColumn
|
||
{
|
||
Name = "Selected",
|
||
HeaderText = "",
|
||
Width = 40,
|
||
FillWeight = 15
|
||
});
|
||
dgvTargets.Columns.Add(new DataGridViewTextBoxColumn { Name = "Name", HeaderText = "Name", ReadOnly = true });
|
||
dgvTargets.Columns.Add(new DataGridViewTextBoxColumn { Name = "Container", HeaderText = "Container", ReadOnly = true });
|
||
dgvTargets.Columns.Add(new DataGridViewTextBoxColumn { Name = "Connection", HeaderText = "Verbindung", ReadOnly = true });
|
||
dgvTargets.Columns.Add(new DataGridViewTextBoxColumn { Name = "Status", HeaderText = "Status", ReadOnly = true });
|
||
dgvTargets.Columns.Add(new DataGridViewTextBoxColumn
|
||
{
|
||
Name = "TargetId",
|
||
Visible = false
|
||
});
|
||
|
||
card.Resize += (_, _) =>
|
||
{
|
||
dgvTargets.Width = Math.Max(200, card.ClientSize.Width - 32);
|
||
dgvTargets.Height = Math.Max(80, card.ClientSize.Height - 60);
|
||
};
|
||
|
||
card.Controls.Add(dgvTargets);
|
||
return card;
|
||
}
|
||
|
||
private Panel BuildActionBar()
|
||
{
|
||
Panel bar = MakeCard();
|
||
|
||
btnRestartOnly = MakeButton("ESB neu starten", GoldColor);
|
||
btnRestartOnly.ForeColor = Color.Black;
|
||
btnRestartOnly.Left = 16;
|
||
btnRestartOnly.Top = 14;
|
||
btnRestartOnly.Width = 180;
|
||
btnRestartOnly.Click += async (_, _) => await RunRestartOnlyAsync();
|
||
|
||
btnCancelRun = MakeButton("Abbrechen", RedColor);
|
||
btnCancelRun.Left = 210;
|
||
btnCancelRun.Top = 14;
|
||
btnCancelRun.Width = 120;
|
||
btnCancelRun.Enabled = false;
|
||
btnCancelRun.Click += (_, _) => _runCts?.Cancel();
|
||
|
||
lblStatus = new Label
|
||
{
|
||
Left = 350,
|
||
Top = 20,
|
||
AutoSize = true,
|
||
ForeColor = MutedTextColor,
|
||
Text = "Bereit."
|
||
};
|
||
|
||
bar.Controls.AddRange([btnRestartOnly, btnCancelRun, lblStatus]);
|
||
return bar;
|
||
}
|
||
|
||
private async Task LoadTargetsAsync()
|
||
{
|
||
try
|
||
{
|
||
(ITargetRepository repo, string msg) = await TargetRepositoryFactory.CreateAsync(_settings);
|
||
_loadedTargets = await repo.GetActiveTargetsAsync();
|
||
BindTargets(_loadedTargets);
|
||
SetStatus(msg, false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus($"Ziele laden fehlgeschlagen: {ex.Message}", true);
|
||
}
|
||
}
|
||
|
||
private async Task LoadFromSonicAsync()
|
||
{
|
||
if (cmbSonicConnection.SelectedItem is not string name)
|
||
{
|
||
SetStatus("Keine Sonic-Verbindung gewählt.", true);
|
||
return;
|
||
}
|
||
|
||
SetStatus($"Lade Container von '{name}'…", false);
|
||
SonicDiscoveryResult result = await _sonicDiscovery.DiscoverAsync(name, _loadedTargets);
|
||
if (!result.Success)
|
||
{
|
||
SetStatus(result.ErrorMessage ?? "Discovery fehlgeschlagen", true);
|
||
return;
|
||
}
|
||
|
||
_loadedTargets = result.DiscoveredTargets;
|
||
BindTargets(_loadedTargets);
|
||
lblSonicStatus.Text = $"Domain={result.DomainName}; {result.DiscoveredTargets.Count} Container";
|
||
SetStatus(result.ErrorMessage ?? $"Container geladen ({result.DiscoveredTargets.Count}).", false);
|
||
}
|
||
|
||
private void BindTargets(IReadOnlyList<DeploymentTarget> targets)
|
||
{
|
||
dgvTargets.Rows.Clear();
|
||
foreach (DeploymentTarget t in targets)
|
||
{
|
||
int row = dgvTargets.Rows.Add(true, t.Name, t.ContainerName, t.SonicConnectionName, "bereit", t.Id);
|
||
dgvTargets.Rows[row].Tag = t;
|
||
}
|
||
}
|
||
|
||
private List<DeploymentTarget> GetSelectedTargets()
|
||
=> dgvTargets.Rows.Cast<DataGridViewRow>()
|
||
.Where(r => !r.IsNewRow && r.Cells["Selected"].Value is true)
|
||
.Select(r => r.Tag)
|
||
.OfType<DeploymentTarget>()
|
||
.ToList();
|
||
|
||
private async Task RunRestartOnlyAsync()
|
||
{
|
||
if (_isOperationRunning)
|
||
{
|
||
return;
|
||
}
|
||
|
||
List<DeploymentTarget> selected = GetSelectedTargets();
|
||
PreflightValidationResult preflight = _orchestrator.ValidateRestartOnly(selected);
|
||
if (!preflight.IsValid)
|
||
{
|
||
MessageBox.Show(
|
||
this,
|
||
string.Join(Environment.NewLine, preflight.Issues.Select(i => i.Message)),
|
||
"Neustart blockiert",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
|
||
string names = string.Join(", ", selected.Select(t => t.ContainerName));
|
||
if (MessageBox.Show(
|
||
this,
|
||
$"Container wirklich neu starten?\n\n{names}",
|
||
"ESB neu starten",
|
||
MessageBoxButtons.YesNo,
|
||
MessageBoxIcon.Warning) != DialogResult.Yes)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_runCts?.Dispose();
|
||
_runCts = new CancellationTokenSource();
|
||
SetOperationRunning(true);
|
||
SetStatus($"Neustart läuft ({selected.Count})…", false);
|
||
|
||
Progress<TargetProgressUpdate> progress = new(u =>
|
||
{
|
||
SetTargetRowStatus(u.TargetId, u.StatusText);
|
||
SetStatus(u.StatusText, u.SuccessHint == false);
|
||
});
|
||
|
||
try
|
||
{
|
||
DeploymentRunResult run = await _orchestrator.RestartOnlyAsync(selected, progress, _runCts.Token);
|
||
foreach (TargetStepResult r in run.TargetResults)
|
||
{
|
||
SetTargetRowStatus(r.TargetId, r.StatusText);
|
||
}
|
||
|
||
string details = string.Join(
|
||
Environment.NewLine + Environment.NewLine,
|
||
run.TargetResults.Select(r =>
|
||
$"{r.TargetName}: {r.StatusText}" +
|
||
(string.IsNullOrWhiteSpace(r.Detail) ? string.Empty : Environment.NewLine + r.Detail)));
|
||
|
||
SetStatus(
|
||
run.OverallSuccess ? "Neustart erfolgreich." : "Neustart fehlgeschlagen.",
|
||
!run.OverallSuccess);
|
||
|
||
MessageBox.Show(
|
||
this,
|
||
details,
|
||
run.OverallSuccess ? "Neustart erfolgreich" : "Neustart fehlgeschlagen",
|
||
MessageBoxButtons.OK,
|
||
run.OverallSuccess ? MessageBoxIcon.Information : MessageBoxIcon.Warning);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
SetStatus("Neustart abgebrochen.", true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus(ex.Message, true);
|
||
MessageBox.Show(this, ex.Message, "Neustart", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
finally
|
||
{
|
||
SetOperationRunning(false);
|
||
_runCts?.Dispose();
|
||
_runCts = null;
|
||
}
|
||
}
|
||
|
||
private void SetTargetRowStatus(int targetId, string status)
|
||
{
|
||
foreach (DataGridViewRow row in dgvTargets.Rows)
|
||
{
|
||
if (row.Tag is DeploymentTarget t && t.Id == targetId)
|
||
{
|
||
row.Cells["Status"].Value = status;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void SetOperationRunning(bool running)
|
||
{
|
||
_isOperationRunning = running;
|
||
btnRestartOnly.Enabled = !running;
|
||
btnCancelRun.Enabled = running;
|
||
dgvTargets.Enabled = !running;
|
||
}
|
||
|
||
private void SetStatus(string text, bool isError)
|
||
{
|
||
lblStatus.Text = text;
|
||
lblStatus.ForeColor = isError ? RedColor : MutedTextColor;
|
||
}
|
||
|
||
private void TrySelectCertificateFile()
|
||
{
|
||
using OpenFileDialog dialog = new()
|
||
{
|
||
Title = "Zertifikat wählen",
|
||
Filter = "Zertifikate (*.cer;*.crt;*.pem;*.pfx)|*.cer;*.crt;*.pem;*.pfx|Alle Dateien (*.*)|*.*"
|
||
};
|
||
|
||
if (dialog.ShowDialog(this) != DialogResult.OK)
|
||
{
|
||
return;
|
||
}
|
||
|
||
string path = dialog.FileName;
|
||
if (!IsSupportedCertificateFile(path))
|
||
{
|
||
MessageBox.Show(this, "Dateityp wird nicht unterstützt (.cer/.crt/.pem/.pfx).", "Zertifikat",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
CertificateInfo info = ReadCertificateWithOptionalPassword(path);
|
||
txtCertificatePath.Text = path;
|
||
_loadedCertificateInfo = info;
|
||
DisplayCertificateInfo(info);
|
||
SetStatus("Zertifikat erkannt.", false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_loadedCertificateInfo = null;
|
||
SetStatus($"Zertifikat ungültig: {ex.Message}", true);
|
||
MessageBox.Show(this, ex.Message, "Zertifikat", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
private CertificateInfo ReadCertificateWithOptionalPassword(string path)
|
||
{
|
||
string ext = Path.GetExtension(path);
|
||
if (!ext.Equals(".pfx", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return ReadCertificate(path, null);
|
||
}
|
||
|
||
try
|
||
{
|
||
return ReadCertificate(path, null);
|
||
}
|
||
catch (CryptographicException)
|
||
{
|
||
string? pwd = PromptPassword();
|
||
if (pwd is null)
|
||
{
|
||
throw new InvalidOperationException("PFX-Passwort abgebrochen.");
|
||
}
|
||
|
||
return ReadCertificate(path, pwd);
|
||
}
|
||
}
|
||
|
||
private string? PromptPassword()
|
||
{
|
||
using Form dialog = new()
|
||
{
|
||
Text = "PFX-Passwort",
|
||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||
StartPosition = FormStartPosition.CenterParent,
|
||
ClientSize = new Size(360, 140),
|
||
MaximizeBox = false,
|
||
MinimizeBox = false,
|
||
BackColor = CardColor,
|
||
ForeColor = TextColor
|
||
};
|
||
Label label = new() { Text = "Passwort:", Left = 16, Top = 20, AutoSize = true };
|
||
TextBox txt = new()
|
||
{
|
||
Left = 16,
|
||
Top = 48,
|
||
Width = 320,
|
||
UseSystemPasswordChar = true
|
||
};
|
||
Button ok = new() { Text = "OK", DialogResult = DialogResult.OK, Left = 160, Top = 90, Width = 80 };
|
||
Button cancel = new() { Text = "Abbrechen", DialogResult = DialogResult.Cancel, Left = 250, Top = 90, Width = 90 };
|
||
dialog.Controls.AddRange([label, txt, ok, cancel]);
|
||
dialog.AcceptButton = ok;
|
||
dialog.CancelButton = cancel;
|
||
return dialog.ShowDialog(this) == DialogResult.OK ? txt.Text : null;
|
||
}
|
||
|
||
private void DisplayCertificateInfo(CertificateInfo info)
|
||
{
|
||
lblCertificateSubject.Text = "Subject: " + info.Subject;
|
||
lblCertificateIssuer.Text = "Issuer: " + info.Issuer;
|
||
lblCertificateExpiry.Text = "Gültig bis: " + info.ValidUntil.ToLocalTime().ToString("dd.MM.yyyy HH:mm");
|
||
lblCertificateFingerprint.Text = "SHA-256: " + info.FingerprintSha256;
|
||
lblCertificateValidity.Text = info.IsCurrentlyValid ? "Status: GÜLTIG" : "Status: UNGÜLTIG / ABGELAUFEN";
|
||
lblCertificateValidity.ForeColor = info.IsCurrentlyValid ? GreenColor : RedColor;
|
||
}
|
||
|
||
private static CertificateInfo ReadCertificate(string certificatePath, string? pfxPassword)
|
||
{
|
||
string extension = Path.GetExtension(certificatePath);
|
||
using X509Certificate2 certificate =
|
||
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|
||
? new X509Certificate2(certificatePath, pfxPassword, X509KeyStorageFlags.EphemeralKeySet)
|
||
: new X509Certificate2(certificatePath);
|
||
|
||
string subject = certificate.GetNameInfo(X509NameType.SimpleName, forIssuer: false);
|
||
string issuer = certificate.GetNameInfo(X509NameType.SimpleName, forIssuer: true);
|
||
if (string.IsNullOrWhiteSpace(subject)) subject = certificate.Subject;
|
||
if (string.IsNullOrWhiteSpace(issuer)) issuer = certificate.Issuer;
|
||
|
||
string fingerprint = FormatFingerprint(certificate.GetCertHashString(HashAlgorithmName.SHA256));
|
||
DateTimeOffset validFrom = new(certificate.NotBefore);
|
||
DateTimeOffset validUntil = new(certificate.NotAfter);
|
||
DateTimeOffset now = DateTimeOffset.Now;
|
||
|
||
return new CertificateInfo
|
||
{
|
||
Subject = subject,
|
||
Issuer = issuer,
|
||
ValidFrom = validFrom,
|
||
ValidUntil = validUntil,
|
||
FingerprintSha256 = fingerprint,
|
||
IsCurrentlyValid = now >= validFrom && now <= validUntil
|
||
};
|
||
}
|
||
|
||
private static string FormatFingerprint(string fingerprint)
|
||
=> string.IsNullOrWhiteSpace(fingerprint)
|
||
? string.Empty
|
||
: string.Join(":", Enumerable.Range(0, fingerprint.Length / 2)
|
||
.Select(i => fingerprint.Substring(i * 2, 2)));
|
||
|
||
private static bool IsSupportedCertificateFile(string filePath)
|
||
{
|
||
if (!File.Exists(filePath))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
string ext = Path.GetExtension(filePath);
|
||
return ext.Equals(".cer", StringComparison.OrdinalIgnoreCase)
|
||
|| ext.Equals(".crt", StringComparison.OrdinalIgnoreCase)
|
||
|| ext.Equals(".pem", StringComparison.OrdinalIgnoreCase)
|
||
|| ext.Equals(".pfx", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
private Panel MakeCard()
|
||
=> new()
|
||
{
|
||
Dock = DockStyle.Fill,
|
||
BackColor = CardColor,
|
||
Padding = new Padding(8),
|
||
Margin = new Padding(0, 0, 0, 10)
|
||
};
|
||
|
||
private Label SectionTitle(string text, int top)
|
||
=> new()
|
||
{
|
||
Text = text,
|
||
Left = 16,
|
||
Top = top,
|
||
AutoSize = true,
|
||
Font = new Font("Segoe UI Semibold", 11f),
|
||
ForeColor = GoldColor
|
||
};
|
||
|
||
private Label MetaLabel(int left, int top, string text)
|
||
=> new()
|
||
{
|
||
Text = text,
|
||
Left = left,
|
||
Top = top,
|
||
AutoSize = true,
|
||
ForeColor = MutedTextColor
|
||
};
|
||
|
||
private Button MakeButton(string text, Color back)
|
||
=> new()
|
||
{
|
||
Text = text,
|
||
Height = 32,
|
||
FlatStyle = FlatStyle.Flat,
|
||
BackColor = back,
|
||
ForeColor = TextColor,
|
||
FlatAppearance = { BorderSize = 0 },
|
||
Cursor = Cursors.Hand
|
||
};
|
||
}
|