Skip to content

Commit 525d925

Browse files
committed
Add timestamp server management and UI enhancements
Introduced new forms (`TimestampServerEditForm` and `TimestampServerManagementForm`) for managing timestamp servers, including adding, editing, testing, and reordering servers. Added `TimestampServer` and `TimestampManager` classes to handle server configurations, prioritization, and availability testing. Refactored signing classes (`SignerPfx`, `SignerThumbprint`, `SignerTrustedSigning`) to inherit from a new `SignerBase` class, centralizing common logic and reducing redundancy. Enhanced certificate monitoring with alerts for expiry and improved certificate validation. Updated `MainForm` with new menu options for managing timestamp servers and certificate monitoring. Improved logging, error handling, and asynchronous operations for better performance and user experience. Updated assembly version to `1.5.0.0` and added new library references for advanced functionality. Cleaned up redundant code and improved UI layouts for better usability.
1 parent 74d5ab3 commit 525d925

20 files changed

Lines changed: 10317 additions & 2979 deletions

CHANGELOG.MD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## Version 1.5.0.0 (00-00-2025):
2+
3+
- Added certificate monitoring functionality with new CertificateMonitor class and CertificateStatus Form
4+
- Introduced color-coded alerts for certificate expiry in both Windows Certificate Store and PFX scenarios
5+
- Enhanced MainForm UI with new menu options for certificate monitoring
6+
- Improved certificate information display with better visual feedback
7+
8+
19
## Version 1.4.0.0 (17-03-2025):
210

311
- Updated Trusted Signing from v0.1.103.0 to the latest v0.1.108.0
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using static SignToolGUI.Class.FileLogger;
7+
8+
namespace SignToolGUI.Class
9+
{
10+
public abstract class SignerBase
11+
{
12+
public string SignToolExe { get; set; }
13+
public bool Verbose { get; set; }
14+
public bool Debug { get; set; }
15+
public bool Timestamp { get; set; }
16+
public TimestampManager TimestampManager { get; set; }
17+
18+
public delegate void StatusReport(string message);
19+
public event StatusReport OnSignToolOutput;
20+
21+
protected SignerBase(string executable, TimestampManager timestampManager = null)
22+
{
23+
SignToolExe = executable;
24+
TimestampManager = timestampManager ?? new TimestampManager();
25+
26+
// Subscribe to timestamp manager status updates
27+
TimestampManager.OnTimestampStatus += OnTimestampStatusReceived;
28+
}
29+
30+
// Protected method that derived classes can use to raise the event
31+
protected virtual void RaiseOnSignToolOutput(string message)
32+
{
33+
OnSignToolOutput?.Invoke(message);
34+
}
35+
36+
// Handle timestamp status messages
37+
private void OnTimestampStatusReceived(string message)
38+
{
39+
RaiseOnSignToolOutput(message);
40+
}
41+
42+
protected bool VerifyFileExists()
43+
{
44+
return File.Exists(SignToolExe);
45+
}
46+
47+
protected abstract string BuildSigningArguments(string targetAssembly, string timestampUrl = null);
48+
49+
public async Task SignAsync(string targetAssembly)
50+
{
51+
await SignWithFallbackAsync(targetAssembly);
52+
}
53+
54+
// Keep the synchronous version for backward compatibility
55+
public void Sign(string targetAssembly)
56+
{
57+
Task.Run(() => SignWithFallbackAsync(targetAssembly));
58+
}
59+
60+
private async Task SignWithFallbackAsync(string targetAssembly)
61+
{
62+
if (!VerifyFileExists())
63+
{
64+
RaiseOnSignToolOutput(@"SignTool.exe can't be found!");
65+
return;
66+
}
67+
68+
var startTime = DateTime.Now;
69+
var maxAttempts = Timestamp ? TimestampManager.GetMaxRetryAttempts() : 1;
70+
var success = false;
71+
var lastError = string.Empty;
72+
73+
for (int attempt = 1; attempt <= maxAttempts && !success; attempt++)
74+
{
75+
try
76+
{
77+
string timestampUrl = null;
78+
79+
if (Timestamp)
80+
{
81+
timestampUrl = TimestampManager.GetNextTimestampUrl(attempt);
82+
if (string.IsNullOrEmpty(timestampUrl))
83+
{
84+
RaiseOnSignToolOutput("No timestamp servers available!");
85+
break;
86+
}
87+
}
88+
89+
var arguments = BuildSigningArguments(targetAssembly, timestampUrl);
90+
var attemptStartTime = DateTime.Now;
91+
92+
RaiseOnSignToolOutput($"Signing attempt {attempt}/{maxAttempts}...");
93+
Message($"Starting signing attempt {attempt}/{maxAttempts} for file: {Path.GetFileName(targetAssembly)}", EventType.Information, 3010);
94+
95+
using (var process = new Process())
96+
{
97+
process.StartInfo = new ProcessStartInfo(SignToolExe)
98+
{
99+
Arguments = arguments,
100+
UseShellExecute = false,
101+
CreateNoWindow = true,
102+
WindowStyle = ProcessWindowStyle.Hidden,
103+
RedirectStandardOutput = true,
104+
RedirectStandardError = true
105+
};
106+
107+
var output = string.Empty;
108+
var error = string.Empty;
109+
110+
process.OutputDataReceived += (sender, e) =>
111+
{
112+
if (!string.IsNullOrEmpty(e.Data))
113+
{
114+
output += e.Data + Environment.NewLine;
115+
RaiseOnSignToolOutput(e.Data);
116+
}
117+
};
118+
119+
process.ErrorDataReceived += (sender, e) =>
120+
{
121+
if (!string.IsNullOrEmpty(e.Data))
122+
{
123+
error += e.Data + Environment.NewLine;
124+
RaiseOnSignToolOutput(e.Data);
125+
}
126+
};
127+
128+
process.Start();
129+
process.BeginOutputReadLine();
130+
process.BeginErrorReadLine();
131+
132+
// Wait for the process to complete asynchronously
133+
await Task.Run(() => process.WaitForExit());
134+
135+
var responseTime = DateTime.Now - attemptStartTime;
136+
var exitCode = process.ExitCode;
137+
138+
if (exitCode == 0)
139+
{
140+
success = true;
141+
RaiseOnSignToolOutput($"Signing completed successfully on attempt {attempt}");
142+
143+
if (Timestamp && !string.IsNullOrEmpty(timestampUrl))
144+
{
145+
TimestampManager.ProcessTimestampResult(true, timestampUrl, null, attempt, responseTime);
146+
}
147+
148+
Message($"File signed successfully on attempt {attempt}: {Path.GetFileName(targetAssembly)}", EventType.Information, 3011);
149+
}
150+
else
151+
{
152+
lastError = !string.IsNullOrEmpty(error) ? error.Trim() : $"SignTool exited with code {exitCode}";
153+
154+
if (Timestamp && !string.IsNullOrEmpty(timestampUrl))
155+
{
156+
TimestampManager.ProcessTimestampResult(false, timestampUrl, lastError, attempt, responseTime);
157+
}
158+
159+
if (attempt < maxAttempts)
160+
{
161+
RaiseOnSignToolOutput($"Attempt {attempt} failed, retrying with next timestamp server...");
162+
163+
// Wait before retrying
164+
var retryDelay = TimestampManager.GetRetryDelay();
165+
if (retryDelay > 0)
166+
{
167+
await Task.Delay(retryDelay);
168+
}
169+
}
170+
else
171+
{
172+
RaiseOnSignToolOutput($"All {maxAttempts} signing attempts failed");
173+
Message($"All signing attempts failed for file: {Path.GetFileName(targetAssembly)}. Last error: {lastError}", EventType.Error, 3012);
174+
}
175+
}
176+
}
177+
}
178+
catch (Exception ex)
179+
{
180+
lastError = ex.Message;
181+
RaiseOnSignToolOutput($"Exception during attempt {attempt}: {ex.Message}");
182+
183+
if (attempt >= maxAttempts)
184+
{
185+
Message($"Exception during signing: {ex.Message}", EventType.Error, 3013);
186+
break;
187+
}
188+
}
189+
}
190+
191+
var totalTime = DateTime.Now - startTime;
192+
193+
if (success)
194+
{
195+
RaiseOnSignToolOutput($"File signing completed successfully in {totalTime.TotalSeconds:F2} seconds");
196+
}
197+
else
198+
{
199+
RaiseOnSignToolOutput($"File signing failed after {maxAttempts} attempts. Last error: {lastError}");
200+
}
201+
}
202+
203+
protected string GlobalOptionSwitches()
204+
{
205+
switch (Verbose)
206+
{
207+
case true when Debug:
208+
return "/v /debug";
209+
case true:
210+
return "/v";
211+
default:
212+
return Debug ? "/debug" : string.Empty;
213+
}
214+
}
215+
}
216+
}

src/SignToolGUI/Class/SignerPfx.cs

Lines changed: 10 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,39 @@
11
using System;
2-
using System.Diagnostics;
3-
using System.IO;
42

53
namespace SignToolGUI.Class
64
{
7-
internal sealed class SignerPfx
5+
internal sealed class SignerPfx : SignerBase
86
{
9-
public string SignToolExe { get; set; }
10-
public string TimeStampServer { get; set; }
117
public string CertificatePath { get; set; }
128
public string CertificatePassword { get; set; }
13-
public bool Verbose { get; set; }
14-
public bool Debug { get; set; }
15-
public bool Timestamp { get; set; }
169

17-
public delegate void StatusReport(string message);
18-
public event StatusReport OnSignToolOutput;
19-
20-
public SignerPfx(string executable, string certPath, string certPasswrd = null, string timestampServer = null)
10+
public SignerPfx(string executable, string certPath, string certPasswrd = null, TimestampManager timestampManager = null)
11+
: base(executable, timestampManager)
2112
{
22-
SignToolExe = executable;
23-
TimeStampServer = timestampServer;
2413
CertificatePath = certPath;
2514
CertificatePassword = certPasswrd;
2615
}
2716

28-
private bool VerifyFileExists()
29-
{
30-
return File.Exists(SignToolExe);
31-
}
32-
33-
public void Sign(string targetAssembly)
34-
{
35-
SignAsync(targetAssembly);
36-
}
37-
38-
private void SignAsync(object targetAssembly)
17+
protected override string BuildSigningArguments(string targetAssembly, string timestampUrl = null)
3918
{
40-
if (!VerifyFileExists())
41-
{
42-
OnSignToolOutput?.Invoke(@"SignTool.exe can't be found!");
43-
return;
44-
}
45-
// Check if the timestamp server URL is set
46-
if (string.IsNullOrEmpty(TimeStampServer))
47-
{
48-
OnSignToolOutput?.Invoke("Timestamp server URL is not set!");
49-
return;
50-
}
51-
// Check if the certificate path is set
5219
if (string.IsNullOrEmpty(CertificatePath))
5320
{
54-
OnSignToolOutput?.Invoke("Certificate path is not set!");
55-
return;
21+
throw new InvalidOperationException("Certificate path is not set!");
5622
}
57-
// Check if the certificate password is set
23+
5824
if (string.IsNullOrEmpty(CertificatePassword))
5925
{
60-
OnSignToolOutput?.Invoke("Certificate password is not set!");
61-
return;
26+
throw new InvalidOperationException("Certificate password is not set!");
6227
}
6328

64-
// Construct the arguments for the signing process
6529
var arguments = $@"sign {GlobalOptionSwitches()} /fd sha256 /f ""{CertificatePath}"" /p ""{CertificatePassword}"" /a ""{targetAssembly}""";
6630

67-
if (Timestamp)
68-
{
69-
// Include timestamp server argument if Timestamp is true
70-
arguments = $@"sign {GlobalOptionSwitches()} /fd sha256 /tr ""{TimeStampServer}"" /td sha256 /f ""{CertificatePath}"" /p ""{CertificatePassword}"" /a ""{targetAssembly}""";
71-
}
72-
73-
// Parse data needed to sign the target assembly
74-
var processSha256 = new Process
75-
{
76-
StartInfo = new ProcessStartInfo(SignToolExe)
77-
{
78-
Arguments = arguments,
79-
UseShellExecute = false,
80-
CreateNoWindow = true,
81-
WindowStyle = ProcessWindowStyle.Hidden,
82-
RedirectStandardOutput = true,
83-
RedirectStandardError = true
84-
}
85-
};
86-
processSha256.OutputDataReceived += (sender, e) =>
87-
{
88-
OnSignToolOutput?.Invoke(e.Data);
89-
};
90-
processSha256.ErrorDataReceived += (sender, e) =>
91-
{
92-
OnSignToolOutput?.Invoke(e.Data);
93-
};
94-
processSha256.Exited += (sender, e) =>
95-
{
96-
OnSignToolOutput?.Invoke("Exited: " + e);
97-
};
98-
99-
// Start the process and begin reading the output and error streams
100-
try
31+
if (Timestamp && !string.IsNullOrEmpty(timestampUrl))
10132
{
102-
processSha256.Start();
103-
processSha256.BeginOutputReadLine();
104-
processSha256.BeginErrorReadLine();
33+
arguments = $@"sign {GlobalOptionSwitches()} /fd sha256 /tr ""{timestampUrl}"" /td sha256 /f ""{CertificatePath}"" /p ""{CertificatePassword}"" /a ""{targetAssembly}""";
10534
}
106-
catch (Exception ex)
107-
{
108-
OnSignToolOutput?.Invoke(ex.Message);
109-
}
110-
}
11135

112-
private string GlobalOptionSwitches()
113-
{
114-
switch (Verbose)
115-
{
116-
case true when Debug:
117-
return "/v /debug";
118-
case true:
119-
return "/v";
120-
default:
121-
return Debug ? "/debug" : string.Empty;
122-
}
36+
return arguments;
12337
}
12438
}
12539
}

0 commit comments

Comments
 (0)