Add timeout and error handling to certificate fetching
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run

This commit is contained in:
2dust 2025-11-04 20:43:51 +08:00
parent 725b094fb1
commit 753e7b81b6
2 changed files with 40 additions and 22 deletions

View file

@ -202,14 +202,17 @@ public class CertPemManager
/// <summary> /// <summary>
/// Get certificate in PEM format from a server with CA pinning validation /// Get certificate in PEM format from a server with CA pinning validation
/// </summary> /// </summary>
public async Task<string?> GetCertPemAsync(string target, string serverName) public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 10)
{ {
try try
{ {
var (domain, _, port, _) = Utils.ParseUrl(target); var (domain, _, port, _) = Utils.ParseUrl(target);
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
using var client = new TcpClient(); using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
@ -218,31 +221,39 @@ public class CertPemManager
var remote = ssl.RemoteCertificate; var remote = ssl.RemoteCertificate;
if (remote == null) if (remote == null)
{ {
return null; return (null, null);
} }
var leaf = new X509Certificate2(remote); var leaf = new X509Certificate2(remote);
return ExportCertToPem(leaf); return (ExportCertToPem(leaf), null);
}
catch (OperationCanceledException)
{
Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds"));
return (null, $"Connection timeout after {timeout} seconds");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
return null; return (null, ex.Message);
} }
} }
/// <summary> /// <summary>
/// Get certificate chain in PEM format from a server with CA pinning validation /// Get certificate chain in PEM format from a server with CA pinning validation
/// </summary> /// </summary>
public async Task<List<string>> GetCertChainPemAsync(string target, string serverName) public async Task<(List<string>, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 10)
{
try
{ {
var pemList = new List<string>(); var pemList = new List<string>();
try
{
var (domain, _, port, _) = Utils.ParseUrl(target); var (domain, _, port, _) = Utils.ParseUrl(target);
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
using var client = new TcpClient(); using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
@ -250,7 +261,7 @@ public class CertPemManager
if (ssl.RemoteCertificate is not X509Certificate2 certChain) if (ssl.RemoteCertificate is not X509Certificate2 certChain)
{ {
return pemList; return (pemList, null);
} }
var chain = new X509Chain(); var chain = new X509Chain();
@ -262,12 +273,17 @@ public class CertPemManager
pemList.Add(pem); pemList.Add(pem);
} }
return pemList; return (pemList, null);
}
catch (OperationCanceledException)
{
Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds"));
return (pemList, $"Connection timeout after {timeout} seconds");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
return new List<string>(); return (pemList, ex.Message);
} }
} }

View file

@ -146,7 +146,8 @@ public class AddServerViewModel : MyReactiveObject
{ {
domain += $":{SelectedSource.Port}"; domain += $":{SelectedSource.Port}";
} }
Cert = await CertPemManager.Instance.GetCertPemAsync(domain, serverName); (Cert, _certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
UpdateCertTip();
} }
private async Task FetchCertChain() private async Task FetchCertChain()
@ -176,7 +177,8 @@ public class AddServerViewModel : MyReactiveObject
{ {
domain += $":{SelectedSource.Port}"; domain += $":{SelectedSource.Port}";
} }
var certs = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName); (var certs, _certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
UpdateCertTip();
Cert = string.Join("\n", certs); Cert = string.Join("\n", certs);
} }
} }