Add CertSha256 support

This commit is contained in:
DHR60 2025-09-17 21:04:58 +08:00
parent 4e042295d2
commit 12b46e7c43
17 changed files with 138 additions and 2 deletions

View file

@ -253,6 +253,7 @@ public static class ConfigHandler
item.ShortId = profileItem.ShortId; item.ShortId = profileItem.ShortId;
item.SpiderX = profileItem.SpiderX; item.SpiderX = profileItem.SpiderX;
item.Mldsa65Verify = profileItem.Mldsa65Verify; item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.CertSha256 = profileItem.CertSha256;
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled; item.MuxEnabled = profileItem.MuxEnabled;
} }

View file

@ -94,6 +94,7 @@ public class ProfileItem : ReactiveObject
public string ShortId { get; set; } public string ShortId { get; set; }
public string SpiderX { get; set; } public string SpiderX { get; set; }
public string Mldsa65Verify { get; set; } public string Mldsa65Verify { get; set; }
public string CertSha256 { get; set; }
public string Extra { get; set; } public string Extra { get; set; }
public bool? MuxEnabled { get; set; } public bool? MuxEnabled { get; set; }
} }

View file

@ -182,6 +182,7 @@ public class Tls4Sbox
public bool? fragment { get; set; } public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; } public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; } public bool? record_fragment { get; set; }
public List<string>? certificate_sha256 { get; set; }
} }
public class Multiplex4Sbox public class Multiplex4Sbox

View file

@ -355,6 +355,7 @@ public class TlsSettings4Ray
public string? shortId { get; set; } public string? shortId { get; set; }
public string? spiderX { get; set; } public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; } public string? mldsa65Verify { get; set; }
public List<string>? pinnedPeerCertificateSha256 { get; set; }
} }
public class TcpSettings4Ray public class TcpSettings4Ray

View file

@ -2364,6 +2364,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Server Certificate Fingerprint (SHA-256) 的本地化字符串。
/// </summary>
public static string TbCertSha256 {
get {
return ResourceManager.GetString("TbCertSha256", resourceCulture);
}
}
/// <summary>
/// 查找类似 Hex SHA-256, comma-separated for certificate chain 的本地化字符串。
/// </summary>
public static string TbCertSha256Tips {
get {
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。 /// 查找类似 Clear system proxy 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1512,4 +1512,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value> <value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>服务器证书指纹 (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>十六进制 SHA-256证书链用逗号分隔</value>
</data>
</root> </root>

View file

@ -1512,4 +1512,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1,3 +1,5 @@
using System.Text.RegularExpressions;
namespace ServiceLib.Services.CoreConfig; namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
@ -251,6 +253,22 @@ public partial class CoreConfigSingboxService
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint 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) if (node.StreamSecurity == Global.StreamSecurityReality)
{ {
tls.reality = new Reality4Sbox() tls.reality = new Reality4Sbox()

View file

@ -1,3 +1,5 @@
using System.Text.RegularExpressions;
namespace ServiceLib.Services.CoreConfig; namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
@ -277,6 +279,17 @@ public partial class CoreConfigV2rayService
{ {
tlsSettings.serverName = Utils.String2List(host)?.First(); 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; streamSettings.tlsSettings = tlsSettings;
} }

View file

@ -709,9 +709,9 @@
<Grid <Grid
x:Name="gridTlsMore" x:Name="gridTlsMore"
Grid.Row="7" Grid.Row="7"
ColumnDefinitions="180,Auto" ColumnDefinitions="180,Auto,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -765,6 +765,26 @@
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256}" />
<TextBox
x:Name="txtCertSha256"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256Tips}" />
</Grid> </Grid>
<Grid <Grid
x:Name="gridRealityMore" x:Name="gridRealityMore"

View file

@ -189,6 +189,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables); 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.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.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.CertSha256, v => v.txtCertSha256.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); 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); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);

View file

@ -928,10 +928,12 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
@ -995,6 +997,29 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertSha256}" />
<TextBox
x:Name="txtCertSha256"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertSha256Tips}" />
</Grid> </Grid>
<Grid <Grid
x:Name="gridRealityMore" x:Name="gridRealityMore"

View file

@ -183,6 +183,7 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => 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.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.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.CertSha256, v => v.txtCertSha256.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); 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); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);