Skip to content

Commit 74d5ab3

Browse files
committed
Add certificate monitoring and UI enhancements
Introduced certificate monitoring functionality with a new `CertificateMonitor` class and `CertificateStatusForm` for managing and displaying certificate statuses. Added color-coded alerts for certificate expiry in both Windows Certificate Store and PFX scenarios. Enhanced the `MainForm` UI with new menu options for certificate monitoring and improved certificate information display. Updated the `System.Text.Json` library to version 9.0.0. Reformatted the `.csproj` file for better readability and maintainability. Added new resources to `MainForm.resx` and `CertificateStatusForm.resx` to enhance the user interface.
1 parent 73ba379 commit 74d5ab3

9 files changed

Lines changed: 7961 additions & 4265 deletions

src/SignToolGUI.sln

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,59 @@
1-
2-
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.8.34408.163
5-
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignToolGUI", "SignToolGUI\SignToolGUI.csproj", "{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}"
7-
EndProject
8-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3F9E1CC4-B444-46FE-9896-6D368CED044B}"
9-
ProjectSection(SolutionItems) = preProject
10-
..\CHANGELOG.MD = ..\CHANGELOG.MD
11-
..\LICENSE.md = ..\LICENSE.md
12-
..\Notes.md = ..\Notes.md
13-
..\README.md = ..\README.md
14-
..\SECURITY.md = ..\SECURITY.md
15-
EndProjectSection
16-
EndProject
17-
Project("{840C416C-B8F3-42BC-B0DD-F6BB14C9F8CB}") = "SignToolGUI Installer", "AI Setup Project\SignToolGUI Installer.aiproj", "{EED69928-407C-4C65-89A8-56244E46764D}"
18-
EndProject
19-
Global
20-
GlobalSection(SolutionConfigurationPlatforms) = preSolution
21-
All|Any CPU = All|Any CPU
22-
Debug|Any CPU = Debug|Any CPU
23-
DefaultBuild|Any CPU = DefaultBuild|Any CPU
24-
Release|Any CPU = Release|Any CPU
25-
EndGlobalSection
26-
GlobalSection(ProjectConfigurationPlatforms) = postSolution
27-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.All|Any CPU.ActiveCfg = Release|Any CPU
28-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.All|Any CPU.Build.0 = Release|Any CPU
29-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
31-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU
32-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU
33-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
34-
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Release|Any CPU.Build.0 = Release|Any CPU
35-
{EED69928-407C-4C65-89A8-56244E46764D}.All|Any CPU.ActiveCfg = DefaultBuild
36-
{EED69928-407C-4C65-89A8-56244E46764D}.All|Any CPU.Build.0 = DefaultBuild
37-
{EED69928-407C-4C65-89A8-56244E46764D}.Debug|Any CPU.ActiveCfg = DefaultBuild
38-
{EED69928-407C-4C65-89A8-56244E46764D}.Debug|Any CPU.Build.0 = DefaultBuild
39-
{EED69928-407C-4C65-89A8-56244E46764D}.DefaultBuild|Any CPU.ActiveCfg = DefaultBuild
40-
{EED69928-407C-4C65-89A8-56244E46764D}.DefaultBuild|Any CPU.Build.0 = DefaultBuild
41-
{EED69928-407C-4C65-89A8-56244E46764D}.Release|Any CPU.ActiveCfg = DefaultBuild
42-
{EED69928-407C-4C65-89A8-56244E46764D}.Release|Any CPU.Build.0 = DefaultBuild
43-
EndGlobalSection
44-
GlobalSection(SolutionProperties) = preSolution
45-
HideSolutionNode = FALSE
46-
EndGlobalSection
47-
GlobalSection(ExtensibilityGlobals) = postSolution
48-
SolutionGuid = {BBBDE099-CAA6-4E91-898C-E45557BE5FA7}
49-
EndGlobalSection
50-
EndGlobal
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 18
4+
VisualStudioVersion = 18.0.10828.68 main
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignToolGUI", "SignToolGUI\SignToolGUI.csproj", "{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3F9E1CC4-B444-46FE-9896-6D368CED044B}"
9+
ProjectSection(SolutionItems) = preProject
10+
..\CHANGELOG.MD = ..\CHANGELOG.MD
11+
..\LICENSE.md = ..\LICENSE.md
12+
..\Notes.md = ..\Notes.md
13+
..\README.md = ..\README.md
14+
..\SECURITY.md = ..\SECURITY.md
15+
EndProjectSection
16+
EndProject
17+
Project("{840C416C-B8F3-42BC-B0DD-F6BB14C9F8CB}") = "SignToolGUI Installer", "AI Setup Project\SignToolGUI Installer.aiproj", "{EED69928-407C-4C65-89A8-56244E46764D}"
18+
EndProject
19+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items (2)", "Solution Items (2)", "{17D2B0EB-13B6-4E9E-BEF2-4D99470588B4}"
20+
ProjectSection(SolutionItems) = preProject
21+
..\CHANGELOG.MD = ..\CHANGELOG.MD
22+
..\LICENSE.md = ..\LICENSE.md
23+
..\Notes.md = ..\Notes.md
24+
..\README.md = ..\README.md
25+
..\SECURITY.md = ..\SECURITY.md
26+
EndProjectSection
27+
EndProject
28+
Global
29+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
30+
All|Any CPU = All|Any CPU
31+
Debug|Any CPU = Debug|Any CPU
32+
DefaultBuild|Any CPU = DefaultBuild|Any CPU
33+
Release|Any CPU = Release|Any CPU
34+
EndGlobalSection
35+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
36+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.All|Any CPU.ActiveCfg = Release|Any CPU
37+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.All|Any CPU.Build.0 = Release|Any CPU
38+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU
41+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU
42+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
43+
{5E3E0028-03EE-4FE0-9A52-4A5AE04B5E0B}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{EED69928-407C-4C65-89A8-56244E46764D}.All|Any CPU.ActiveCfg = DefaultBuild
45+
{EED69928-407C-4C65-89A8-56244E46764D}.All|Any CPU.Build.0 = DefaultBuild
46+
{EED69928-407C-4C65-89A8-56244E46764D}.Debug|Any CPU.ActiveCfg = DefaultBuild
47+
{EED69928-407C-4C65-89A8-56244E46764D}.Debug|Any CPU.Build.0 = DefaultBuild
48+
{EED69928-407C-4C65-89A8-56244E46764D}.DefaultBuild|Any CPU.ActiveCfg = DefaultBuild
49+
{EED69928-407C-4C65-89A8-56244E46764D}.DefaultBuild|Any CPU.Build.0 = DefaultBuild
50+
{EED69928-407C-4C65-89A8-56244E46764D}.Release|Any CPU.ActiveCfg = DefaultBuild
51+
{EED69928-407C-4C65-89A8-56244E46764D}.Release|Any CPU.Build.0 = DefaultBuild
52+
EndGlobalSection
53+
GlobalSection(SolutionProperties) = preSolution
54+
HideSolutionNode = FALSE
55+
EndGlobalSection
56+
GlobalSection(ExtensibilityGlobals) = postSolution
57+
SolutionGuid = {BBBDE099-CAA6-4E91-898C-E45557BE5FA7}
58+
EndGlobalSection
59+
EndGlobal
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Security.Cryptography.X509Certificates;
5+
using System.Windows.Forms;
6+
using static SignToolGUI.Class.FileLogger;
7+
8+
namespace SignToolGUI.Class
9+
{
10+
public class CertificateMonitor
11+
{
12+
public enum AlertLevel
13+
{
14+
None,
15+
Warning, // 30-90 days
16+
Critical, // 0-30 days
17+
Expired // Already expired
18+
}
19+
20+
public class CertificateAlert
21+
{
22+
public X509Certificate2 Certificate { get; set; }
23+
public AlertLevel Level { get; set; }
24+
public int DaysUntilExpiry { get; set; }
25+
public string Message { get; set; }
26+
public string CertificateName { get; set; }
27+
}
28+
29+
private readonly int _warningThresholdDays;
30+
private readonly int _criticalThresholdDays;
31+
32+
public CertificateMonitor(int warningThresholdDays = 90, int criticalThresholdDays = 30)
33+
{
34+
_warningThresholdDays = warningThresholdDays;
35+
_criticalThresholdDays = criticalThresholdDays;
36+
}
37+
38+
public List<CertificateAlert> CheckCertificateExpiry(X509Certificate2Collection certificates)
39+
{
40+
var alerts = new List<CertificateAlert>();
41+
var currentDate = DateTime.Now;
42+
43+
foreach (X509Certificate2 cert in certificates)
44+
{
45+
if (cert == null) continue;
46+
47+
var daysUntilExpiry = (cert.NotAfter - currentDate).Days;
48+
var alertLevel = GetAlertLevel(daysUntilExpiry);
49+
50+
if (alertLevel != AlertLevel.None)
51+
{
52+
var alert = new CertificateAlert
53+
{
54+
Certificate = cert,
55+
Level = alertLevel,
56+
DaysUntilExpiry = daysUntilExpiry,
57+
CertificateName = cert.GetNameInfo(X509NameType.SimpleName, false) ?? "Unknown Certificate",
58+
Message = GenerateAlertMessage(cert, daysUntilExpiry, alertLevel)
59+
};
60+
61+
alerts.Add(alert);
62+
63+
// Log the certificate expiry alert
64+
var logLevel = alertLevel == AlertLevel.Expired || alertLevel == AlertLevel.Critical
65+
? EventType.Warning
66+
: EventType.Information;
67+
68+
Message($"Certificate expiry alert: {alert.Message}", logLevel, 2000 + (int)alertLevel);
69+
}
70+
}
71+
72+
return alerts.OrderBy(a => a.DaysUntilExpiry).ToList();
73+
}
74+
75+
public CertificateAlert CheckSingleCertificate(X509Certificate2 certificate)
76+
{
77+
if (certificate == null) return null;
78+
79+
var currentDate = DateTime.Now;
80+
var daysUntilExpiry = (certificate.NotAfter - currentDate).Days;
81+
var alertLevel = GetAlertLevel(daysUntilExpiry);
82+
83+
if (alertLevel == AlertLevel.None) return null;
84+
85+
return new CertificateAlert
86+
{
87+
Certificate = certificate,
88+
Level = alertLevel,
89+
DaysUntilExpiry = daysUntilExpiry,
90+
CertificateName = certificate.GetNameInfo(X509NameType.SimpleName, false) ?? "Unknown Certificate",
91+
Message = GenerateAlertMessage(certificate, daysUntilExpiry, alertLevel)
92+
};
93+
}
94+
95+
private AlertLevel GetAlertLevel(int daysUntilExpiry)
96+
{
97+
if (daysUntilExpiry < 0)
98+
return AlertLevel.Expired;
99+
else if (daysUntilExpiry <= _criticalThresholdDays)
100+
return AlertLevel.Critical;
101+
else if (daysUntilExpiry <= _warningThresholdDays)
102+
return AlertLevel.Warning;
103+
else
104+
return AlertLevel.None;
105+
}
106+
107+
private string GenerateAlertMessage(X509Certificate2 cert, int daysUntilExpiry, AlertLevel level)
108+
{
109+
var certName = cert.GetNameInfo(X509NameType.SimpleName, false) ?? "Unknown Certificate";
110+
var expiryDate = cert.NotAfter.ToShortDateString();
111+
112+
switch (level)
113+
{
114+
case AlertLevel.Expired:
115+
return $"Certificate '{certName}' has EXPIRED {Math.Abs(daysUntilExpiry)} days ago (expired on {expiryDate})";
116+
117+
case AlertLevel.Critical:
118+
return $"Certificate '{certName}' expires in {daysUntilExpiry} days (on {expiryDate}) - CRITICAL";
119+
120+
case AlertLevel.Warning:
121+
return $"Certificate '{certName}' expires in {daysUntilExpiry} days (on {expiryDate}) - Warning";
122+
123+
default:
124+
return string.Empty;
125+
}
126+
}
127+
128+
public void ShowExpiryAlerts(List<CertificateAlert> alerts, IWin32Window owner = null)
129+
{
130+
if (!alerts.Any()) return;
131+
132+
var expiredCerts = alerts.Where(a => a.Level == AlertLevel.Expired).ToList();
133+
var criticalCerts = alerts.Where(a => a.Level == AlertLevel.Critical).ToList();
134+
var warningCerts = alerts.Where(a => a.Level == AlertLevel.Warning).ToList();
135+
136+
var message = "Certificate Expiry Alerts:\n\n";
137+
138+
if (expiredCerts.Any())
139+
{
140+
message += "🔴 EXPIRED CERTIFICATES:\n";
141+
foreach (var cert in expiredCerts)
142+
{
143+
message += $"• {cert.Message}\n";
144+
}
145+
message += "\n";
146+
}
147+
148+
if (criticalCerts.Any())
149+
{
150+
message += "🟠 CRITICAL (expires within 30 days):\n";
151+
foreach (var cert in criticalCerts)
152+
{
153+
message += $"• {cert.Message}\n";
154+
}
155+
message += "\n";
156+
}
157+
158+
if (warningCerts.Any())
159+
{
160+
message += "🟡 WARNING (expires within 90 days):\n";
161+
foreach (var cert in warningCerts)
162+
{
163+
message += $"• {cert.Message}\n";
164+
}
165+
}
166+
167+
message += "\n⚠️ Please renew or replace expiring certificates before they become unusable for code signing.";
168+
169+
var icon = expiredCerts.Any() || criticalCerts.Any()
170+
? MessageBoxIcon.Warning
171+
: MessageBoxIcon.Information;
172+
173+
MessageBox.Show(owner, message, "Certificate Expiry Alerts", MessageBoxButtons.OK, icon);
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)