diff --git a/v2rayN/ServiceLib/Manager/CertPemManager.cs b/v2rayN/ServiceLib/Manager/CertPemManager.cs index b1c8d82a..0a58e2de 100644 --- a/v2rayN/ServiceLib/Manager/CertPemManager.cs +++ b/v2rayN/ServiceLib/Manager/CertPemManager.cs @@ -203,7 +203,7 @@ public class CertPemManager /// /// Get certificate in PEM format from a server with CA pinning validation /// - public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4) + public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4, bool allowInsecure = false) { try { @@ -215,12 +215,14 @@ public class CertPemManager using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); - await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); + var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => + ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure)); + await using var ssl = new SslStream(client.GetStream(), false, callback); var sslOptions = new SslClientAuthenticationOptions { TargetHost = serverName, - RemoteCertificateValidationCallback = ValidateServerCertificate + RemoteCertificateValidationCallback = callback }; await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); @@ -249,7 +251,7 @@ public class CertPemManager /// /// Get certificate chain in PEM format from a server with CA pinning validation /// - public async Task<(List, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4) + public async Task<(List, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4, bool allowInsecure = false) { var pemList = new List(); try @@ -262,12 +264,14 @@ public class CertPemManager using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); - await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); + var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => + ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure)); + await using var ssl = new SslStream(client.GetStream(), false, callback); var sslOptions = new SslClientAuthenticationOptions { TargetHost = serverName, - RemoteCertificateValidationCallback = ValidateServerCertificate + RemoteCertificateValidationCallback = callback }; await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); @@ -300,16 +304,23 @@ public class CertPemManager /// Validate server certificate with CA pinning /// private bool ValidateServerCertificate( - object sender, + object _, X509Certificate? certificate, X509Chain? chain, - SslPolicyErrors sslPolicyErrors) + SslPolicyErrors sslPolicyErrors, + bool allowInsecure) { if (certificate == null) { return false; } + // In insecure mode, accept any certificate so self-signed certs can be fetched. + if (allowInsecure) + { + return true; + } + // Check certificate name mismatch if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch)) { diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 4498d999..7f50a8b5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2571,6 +2571,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Allow insecure cert fetch (self-signed) 的本地化字符串。 + /// + public static string TbAllowInsecureCertFetch { + get { + return ResourceManager.GetString("TbAllowInsecureCertFetch", resourceCulture); + } + } + + /// + /// 查找类似 Only for fetching self-signed certificates. This may expose you to MITM risks. 的本地化字符串。 + /// + public static string TbAllowInsecureCertFetchTips { + get { + return ResourceManager.GetString("TbAllowInsecureCertFetchTips", resourceCulture); + } + } + /// /// 查找类似 ALPN 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 61ccbf7b..96630b69 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1698,4 +1698,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Legacy TUN Protect + + Allow insecure cert fetch (self-signed) + + + Only for fetching self-signed certificates. This may expose you to MITM risks. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index d466eedd..5bf822ea 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1695,4 +1695,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Legacy TUN Protect + + Allow insecure cert fetch (self-signed) + + + Only for fetching self-signed certificates. This may expose you to MITM risks. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 89456f99..23b85499 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1698,4 +1698,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Legacy TUN Protect + + Allow insecure cert fetch (self-signed) + + + Only for fetching self-signed certificates. This may expose you to MITM risks. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 569877d7..e3358b39 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1698,4 +1698,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Legacy TUN Protect + + Allow insecure cert fetch (self-signed) + + + Only for fetching self-signed certificates. This may expose you to MITM risks. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 401c237e..d767ccfb 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1698,4 +1698,10 @@ Legacy TUN Protect + + Allow insecure cert fetch (self-signed) + + + Only for fetching self-signed certificates. This may expose you to MITM risks. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 8c628644..b5d5db1b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1695,4 +1695,10 @@ 旧版 TUN 保护 + + 允许不安全获取证书(自签名) + + + 仅用于抓取自签证书,存在中间人风险。 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 59a9fc9f..57881c46 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1695,4 +1695,10 @@ Legacy TUN Protect + + 允許不安全獲取證書(自簽名) + + + 僅用於抓取自簽證書,存在中間人風險。 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index e5c307c8..424d464f 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -73,6 +73,9 @@ public class AddServerViewModel : MyReactiveObject [Reactive] public bool NaiveQuic { get; set; } + [Reactive] + public bool AllowInsecureCertFetch { get; set; } + public ReactiveCommand FetchCertCmd { get; } public ReactiveCommand FetchCertChainCmd { get; } public ReactiveCommand SaveCmd { get; } @@ -272,7 +275,7 @@ public class AddServerViewModel : MyReactiveObject domain += $":{SelectedSource.Port}"; } - (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName); + (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch); UpdateCertTip(certError); } @@ -297,7 +300,7 @@ public class AddServerViewModel : MyReactiveObject domain += $":{SelectedSource.Port}"; } - var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName); + var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch); Cert = CertPemManager.ConcatenatePemChain(certs); UpdateCertTip(certError); } diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 4dc378b4..d975cab9 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -1008,6 +1008,25 @@ Margin="{StaticResource Margin4}" Content="{x:Static resx:ResUI.TbFetchCertChain}" /> + + + + + this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.togAllowInsecureCertFetch.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.txtAllowInsecureCertFetchTips.IsVisible).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index 1eb28d9d..68d5301d 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -1269,6 +1269,27 @@ Content="{x:Static resx:ResUI.TbFetchCertChain}" Style="{StaticResource DefButton}" /> + + + + + vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.togAllowInsecureCertFetch.IsChecked).DisposeWith(disposables); + this.WhenAnyValue(x => x.ViewModel.AllowInsecureCertFetch) + .Select(b => b ? Visibility.Visible : Visibility.Collapsed) + .BindTo(this, v => v.txtAllowInsecureCertFetchTips.Visibility); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.Text).DisposeWith(disposables);