Compare commits

...

7 commits

Author SHA1 Message Date
DHR60
5c4f1ea38f
Merge 18ccabd193 into 18ac76e683 2025-09-21 21:15:55 +08:00
2dust
18ac76e683 up 7.14.12
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-09-21 14:50:01 +08:00
2dust
3e1e23a524 Update Directory.Packages.props 2025-09-21 14:48:54 +08:00
2dust
534c7ab444 Optimize and improve QR code display 2025-09-21 14:35:49 +08:00
DHR60
18ccabd193 Code clean 2025-09-17 21:25:08 +08:00
DHR60
942335db61 Refactor 2025-09-17 21:11:05 +08:00
DHR60
12b46e7c43 Add CertSha256 support 2025-09-17 21:04:58 +08:00
25 changed files with 216 additions and 24 deletions

View file

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.14.11</Version>
<Version>7.14.12</Version>
</PropertyGroup>
<PropertyGroup>

View file

@ -18,8 +18,8 @@
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.10" />
<PackageVersion Include="Splat.NLog" Version="16.2.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />

View file

@ -1,4 +1,5 @@
using QRCoder;
using QRCoder.Exceptions;
using SkiaSharp;
using ZXing.SkiaSharp;
@ -8,10 +9,45 @@ public class QRCodeUtils
{
public static byte[]? GenQRCode(string? url)
{
if (url.IsNullOrEmpty())
{
return null;
}
using QRCodeGenerator qrGenerator = new();
using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
DataTooLongException? lastDtle = null;
var levels = new[]
{
QRCodeGenerator.ECCLevel.H,
QRCodeGenerator.ECCLevel.Q,
QRCodeGenerator.ECCLevel.M,
QRCodeGenerator.ECCLevel.L
};
foreach (var level in levels)
{
try
{
using var qrCodeData = qrGenerator.CreateQrCode(url, level);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
}
catch (DataTooLongException ex)
{
lastDtle = ex;
continue;
}
catch
{
throw;
}
}
if (lastDtle != null)
{
throw lastDtle;
}
return null;
}
public static string? ParseBarcode(string? fileName)

View file

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using CliWrap;
using CliWrap.Buffered;
@ -357,6 +358,14 @@ public class Utils
return userHostsMap;
}
public static List<string> ParseCertSha256ToList(string certSha256Content)
{
return String2List(certSha256Content)
.Select(s => s.Replace(":", "").Replace(" ", ""))
.Where(s => s.Length == 64 && Regex.IsMatch(s, @"\A\b[0-9a-fA-F]+\b\Z"))
.ToList();
}
#endregion
#region

View file

@ -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;
}

View file

@ -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; }
}

View file

@ -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<string>? certificate_sha256 { get; set; }
}
public class Multiplex4Sbox

View file

@ -355,6 +355,7 @@ public class TlsSettings4Ray
public string? shortId { get; set; }
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
public List<string>? pinnedPeerCertificateSha256 { get; set; }
}
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>
/// 查找类似 Clear system proxy 的本地化字符串。
/// </summary>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</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>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</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>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</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>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</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>

View file

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

View file

@ -1512,4 +1512,10 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</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>

View file

@ -251,6 +251,20 @@ 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.ParseCertSha256ToList(node.CertSha256)
.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()

View file

@ -277,6 +277,14 @@ public partial class CoreConfigV2rayService
{
tlsSettings.serverName = Utils.String2List(host)?.First();
}
if (node.CertSha256.IsNotEmpty())
{
var certSha256List = Utils.ParseCertSha256ToList(node.CertSha256);
if (certSha256List.Count > 0)
{
tlsSettings.pinnedPeerCertificateSha256 = certSha256List;
}
}
streamSettings.tlsSettings = tlsSettings;
}

View file

@ -709,9 +709,9 @@
<Grid
x:Name="gridTlsMore"
Grid.Row="7"
ColumnDefinitions="180,Auto"
ColumnDefinitions="180,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
@ -765,6 +765,26 @@
Grid.Column="1"
Width="200"
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
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.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);

View file

@ -4,19 +4,25 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="480"
d:DesignWidth="400"
xmlns:sys="clr-namespace:System;assembly=netstandard"
d:DesignHeight="600"
d:DesignWidth="600"
mc:Ignorable="d">
<UserControl.Resources>
<sys:Double x:Key="QrcodeWidth">500</sys:Double>
</UserControl.Resources>
<Grid Margin="32" RowDefinitions="Auto,Auto">
<Image
Name="imgQrcode"
Width="300"
Height="300" />
Width="{StaticResource QrcodeWidth}"
Height="{StaticResource QrcodeWidth}" />
<TextBox
x:Name="txtContent"
Grid.Row="1"
Width="300"
Width="{StaticResource QrcodeWidth}"
MaxHeight="100"
Margin="{StaticResource MarginTb8}"
VerticalAlignment="Center"

View file

@ -23,8 +23,16 @@ public partial class QrcodeView : UserControl
private Bitmap? GetQRCode(string? url)
{
var bytes = QRCodeUtils.GenQRCode(url);
return ByteToBitmap(bytes);
try
{
var bytes = QRCodeUtils.GenQRCode(url);
return ByteToBitmap(bytes);
}
catch (Exception ex)
{
Logging.SaveLog("GetQRCode", ex);
return null;
}
}
private Bitmap? ByteToBitmap(byte[]? bytes)

View file

@ -20,8 +20,9 @@ public class QRCodeUtils
var qrCodeImage = ServiceLib.Common.QRCodeUtils.GenQRCode(strContent);
return qrCodeImage is null ? null : ByteToImage(qrCodeImage);
}
catch
catch (Exception ex)
{
Logging.SaveLog("GetQRCode", ex);
return null;
}
}

View file

@ -928,10 +928,12 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
@ -995,6 +997,29 @@
Width="200"
Margin="{StaticResource Margin4}"
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
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.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);

View file

@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="v2rayN.Views.QrcodeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -6,28 +6,33 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
d:DesignHeight="300"
d:DesignWidth="300"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
d:DesignHeight="600"
d:DesignWidth="600"
Style="{StaticResource ViewGlobal}"
mc:Ignorable="d">
<UserControl.Resources>
<sys:Double x:Key="QrcodeWidth">500</sys:Double>
</UserControl.Resources>
<Grid Margin="32">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="60" />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<Image
x:Name="imgQrcode"
Grid.Row="0"
Width="300"
Height="300"
Width="{StaticResource QrcodeWidth}"
Height="{StaticResource QrcodeWidth}"
Stretch="UniformToFill" />
<TextBox
x:Name="txtContent"
Grid.Row="1"
Width="300"
Width="{StaticResource QrcodeWidth}"
Margin="0,8"
VerticalAlignment="Center"
IsReadOnly="True"
@ -46,4 +51,4 @@
IsDefault="True"
Style="{StaticResource DefButton}" />
</Grid>
</UserControl>
</UserControl>