diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 786f3aff..035822dd 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -253,6 +253,7 @@ public static class ConfigHandler item.ShortId = profileItem.ShortId; item.SpiderX = profileItem.SpiderX; item.Mldsa65Verify = profileItem.Mldsa65Verify; + item.CertSha256 = profileItem.CertSha256; item.Extra = profileItem.Extra; item.MuxEnabled = profileItem.MuxEnabled; } diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index 998aa120..b4f69968 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -94,6 +94,7 @@ public class ProfileItem : ReactiveObject public string ShortId { get; set; } public string SpiderX { get; set; } public string Mldsa65Verify { get; set; } + public string CertSha256 { get; set; } public string Extra { get; set; } public bool? MuxEnabled { get; set; } } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index a5eec4ae..60b9720e 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -182,6 +182,7 @@ public class Tls4Sbox public bool? fragment { get; set; } public string? fragment_fallback_delay { get; set; } public bool? record_fragment { get; set; } + public List? certificate_sha256 { get; set; } } public class Multiplex4Sbox diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index cff3cf8b..24543d11 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -355,6 +355,7 @@ public class TlsSettings4Ray public string? shortId { get; set; } public string? spiderX { get; set; } public string? mldsa65Verify { get; set; } + public List? pinnedPeerCertificateSha256 { get; set; } } public class TcpSettings4Ray diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 7e848cf0..a1e2f943 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2364,6 +2364,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Server Certificate Fingerprint (SHA-256) 的本地化字符串。 + /// + public static string TbCertSha256 { + get { + return ResourceManager.GetString("TbCertSha256", resourceCulture); + } + } + + /// + /// 查找类似 Hex SHA-256, comma-separated for certificate chain 的本地化字符串。 + /// + public static string TbCertSha256Tips { + get { + return ResourceManager.GetString("TbCertSha256Tips", resourceCulture); + } + } + /// /// 查找类似 Clear system proxy 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index f79901eb..3e18243c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1515,4 +1515,10 @@ Applies globally by default, with built-in FakeIP filtering (sing-box only). + + Server Certificate Fingerprint (SHA-256) + + + Hex SHA-256, comma-separated for certificate chain + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index e801c796..8043934b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1515,4 +1515,10 @@ Applies globally by default, with built-in FakeIP filtering (sing-box only). + + Server Certificate Fingerprint (SHA-256) + + + Hex SHA-256, comma-separated for certificate chain + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index fc4b218f..320c008d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1515,4 +1515,10 @@ Applies globally by default, with built-in FakeIP filtering (sing-box only). + + Server Certificate Fingerprint (SHA-256) + + + Hex SHA-256, comma-separated for certificate chain + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 04e16eb8..97c198d5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1515,4 +1515,10 @@ Applies globally by default, with built-in FakeIP filtering (sing-box only). + + Server Certificate Fingerprint (SHA-256) + + + Hex SHA-256, comma-separated for certificate chain + \ 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 ced2d74b..e9a2fb74 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1512,4 +1512,10 @@ 默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效 + + 服务器证书指纹 (SHA-256) + + + 十六进制 SHA-256,证书链用逗号分隔 + \ 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 720b7269..9837de32 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1512,4 +1512,10 @@ Applies globally by default, with built-in FakeIP filtering (sing-box only). + + Server Certificate Fingerprint (SHA-256) + + + Hex SHA-256, comma-separated for certificate chain + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 3f03b93c..8433d08b 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -1,3 +1,5 @@ +using System.Text.RegularExpressions; + namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService @@ -251,6 +253,22 @@ public partial class CoreConfigSingboxService fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint }; } + if (node.CertSha256.IsNotEmpty()) + { + // hex to raw to base64 + var certSha256List = Utils.String2List(node.CertSha256) + .Select(s => s.Replace(":", "").Replace(" ", "")) + .Where(s => s.Length == 64 && Regex.IsMatch(s, @"\A[0-9a-fA-F]{64}\Z")) + .Select(s => Convert.ToBase64String( + Enumerable.Range(0, 32) + .Select(i => Convert.ToByte(s.Substring(i * 2, 2), 16)) + .ToArray())) + .ToList(); + if (certSha256List.Count > 0) + { + tls.certificate_sha256 = certSha256List; + } + } if (node.StreamSecurity == Global.StreamSecurityReality) { tls.reality = new Reality4Sbox() diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 11e8a8fa..fcf5b8bd 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -1,3 +1,5 @@ +using System.Text.RegularExpressions; + namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService @@ -277,6 +279,17 @@ public partial class CoreConfigV2rayService { tlsSettings.serverName = Utils.String2List(host)?.First(); } + if (node.CertSha256.IsNotEmpty()) + { + var certSha256List = Utils.String2List(node.CertSha256) + .Select(s => s.Replace(":", "").Replace(" ", "")) + .Where(s => s.Length == 64 && Regex.IsMatch(s, @"\A\b[0-9a-fA-F]+\b\Z")) + .ToList(); + if (certSha256List.Count > 0) + { + tlsSettings.pinnedPeerCertificateSha256 = certSha256List; + } + } streamSettings.tlsSettings = tlsSettings; } diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 7aa6a0be..9115d0eb 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -709,9 +709,9 @@ + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> + + + + this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.CertSha256, v => v.txtCertSha256.Text).DisposeWith(disposables); //reality this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index b293a115..5af7fceb 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -928,10 +928,12 @@ + + + + + + vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.CertSha256, v => v.txtCertSha256.Text).DisposeWith(disposables); //reality this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);