Cert Pinning (#8234)

* Cert Pinning

* Cert Chain Pinning

* Add Trusted Ca Pinning

* Tip

* Perf UI
This commit is contained in:
DHR60
2025-11-02 15:17:47 +08:00
committed by GitHub
parent 7b5686cd8f
commit b218f0b501
20 changed files with 858 additions and 55 deletions
@@ -2,12 +2,22 @@ namespace ServiceLib.ViewModels;
public class AddServerViewModel : MyReactiveObject
{
private string _certError = string.Empty;
[Reactive]
public ProfileItem SelectedSource { get; set; }
[Reactive]
public string? CoreType { get; set; }
[Reactive]
public string Cert { get; set; }
[Reactive]
public string CertTip { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
@@ -15,11 +25,22 @@ public class AddServerViewModel : MyReactiveObject
_config = AppManager.Instance.Config;
_updateView = updateView;
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCert();
});
FetchCertChainCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCertChain();
});
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
{
await SaveServerAsync();
});
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.Network = Global.DefaultNetwork;
@@ -33,6 +54,7 @@ public class AddServerViewModel : MyReactiveObject
SelectedSource = JsonUtils.DeepCopy(profileItem);
}
CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
}
private async Task SaveServerAsync()
@@ -77,6 +99,7 @@ public class AddServerViewModel : MyReactiveObject
}
}
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{
@@ -88,4 +111,72 @@ public class AddServerViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
}
}
private void UpdateCertTip()
{
CertTip = _certError.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
: _certError;
}
private async Task FetchCert()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
{
return;
}
var domain = SelectedSource.Address;
var serverName = SelectedSource.Sni;
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.RequestHost;
}
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
_certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
Cert = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
}
private async Task FetchCertChain()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
{
return;
}
var domain = SelectedSource.Address;
var serverName = SelectedSource.Sni;
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.RequestHost;
}
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
_certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
var certs = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
Cert = string.Join("\n", certs);
}
}