mirror of
https://github.com/2dust/v2rayN.git
synced 2025-11-29 03:02:53 +00:00
Cert Pinning
This commit is contained in:
parent
7b5686cd8f
commit
bd7b7ca1e8
20 changed files with 394 additions and 51 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
|
|
@ -762,6 +764,83 @@ public class Utils
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<string?> GetCertPem(string target, string serverName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (domain, _, port, _) = ParseUrl(target);
|
||||||
|
|
||||||
|
using var client = new TcpClient();
|
||||||
|
await client.ConnectAsync(domain, port > 0 ? port : 443);
|
||||||
|
|
||||||
|
using var ssl = new SslStream(client.GetStream(), false,
|
||||||
|
(sender, cert, chain, errors) => true);
|
||||||
|
|
||||||
|
await ssl.AuthenticateAsClientAsync(serverName);
|
||||||
|
|
||||||
|
var remote = ssl.RemoteCertificate;
|
||||||
|
if (remote == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var leaf = new X509Certificate2(remote);
|
||||||
|
return ExportCertToPem(leaf);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<string>> GetCertChainPem(string target, string serverName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pemList = new List<string>();
|
||||||
|
var (domain, _, port, _) = ParseUrl(target);
|
||||||
|
|
||||||
|
using var client = new TcpClient();
|
||||||
|
await client.ConnectAsync(domain, port > 0 ? port : 443);
|
||||||
|
|
||||||
|
using var ssl = new SslStream(client.GetStream(), false,
|
||||||
|
(sender, cert, chain, errors) => true);
|
||||||
|
|
||||||
|
await ssl.AuthenticateAsClientAsync(serverName);
|
||||||
|
|
||||||
|
// TODO: Pinning Trusted CA certificates to avoid MITM
|
||||||
|
|
||||||
|
if (ssl.RemoteCertificate is not X509Certificate2 certChain)
|
||||||
|
{
|
||||||
|
return pemList;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chain = new X509Chain();
|
||||||
|
chain.Build(certChain);
|
||||||
|
|
||||||
|
foreach (var element in chain.ChainElements)
|
||||||
|
{
|
||||||
|
var pem = ExportCertToPem(element.Certificate);
|
||||||
|
pemList.Add(pem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pemList;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ExportCertToPem(X509Certificate2 cert)
|
||||||
|
{
|
||||||
|
var der = cert.Export(X509ContentType.Cert);
|
||||||
|
var b64 = Convert.ToBase64String(der, Base64FormattingOptions.InsertLineBreaks);
|
||||||
|
return $"-----BEGIN CERTIFICATE-----\n{b64}\n-----END CERTIFICATE-----\n";
|
||||||
|
}
|
||||||
|
|
||||||
#endregion 杂项
|
#endregion 杂项
|
||||||
|
|
||||||
#region TempPath
|
#region TempPath
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,7 @@ public static class ConfigHandler
|
||||||
item.Mldsa65Verify = profileItem.Mldsa65Verify;
|
item.Mldsa65Verify = profileItem.Mldsa65Verify;
|
||||||
item.Extra = profileItem.Extra;
|
item.Extra = profileItem.Extra;
|
||||||
item.MuxEnabled = profileItem.MuxEnabled;
|
item.MuxEnabled = profileItem.MuxEnabled;
|
||||||
|
item.Cert = profileItem.Cert;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = item.ConfigType switch
|
var ret = item.ConfigType switch
|
||||||
|
|
|
||||||
|
|
@ -141,4 +141,5 @@ public class ProfileItem : ReactiveObject
|
||||||
public string Mldsa65Verify { get; set; }
|
public string Mldsa65Verify { get; set; }
|
||||||
public string Extra { get; set; }
|
public string Extra { get; set; }
|
||||||
public bool? MuxEnabled { get; set; }
|
public bool? MuxEnabled { get; set; }
|
||||||
|
public string Cert { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,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 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Multiplex4Sbox
|
public class Multiplex4Sbox
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,14 @@ 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<CertificateSettings4Ray>? certificates { get; set; }
|
||||||
|
public bool? disableSystemRoot { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CertificateSettings4Ray
|
||||||
|
{
|
||||||
|
public List<string>? certificate { get; set; }
|
||||||
|
public string? usage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TcpSettings4Ray
|
public class TcpSettings4Ray
|
||||||
|
|
|
||||||
28
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
28
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
|
@ -2562,6 +2562,25 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Certificate Pinning 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbCertPinning {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbCertPinning", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
///Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbCertPinningTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbCertPinningTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Clear system proxy 的本地化字符串。
|
/// 查找类似 Clear system proxy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -2769,6 +2788,15 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Fetch Certificate 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFetchCert {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFetchCert", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Fingerprint 的本地化字符串。
|
/// 查找类似 Fingerprint 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1602,4 +1602,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>Auto add filtered configuration from subscription groups</value>
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1599,4 +1599,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>Auto add filtered configuration from subscription groups</value>
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1602,4 +1602,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>Auto add filtered configuration from subscription groups</value>
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1602,4 +1602,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>Auto add filtered configuration from subscription groups</value>
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1602,4 +1602,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>Auto add filtered configuration from subscription groups</value>
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1599,4 +1599,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>自动从订阅分组添加过滤后的配置</value>
|
<value>自动从订阅分组添加过滤后的配置</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>固定证书</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>服务器证书(PEM 格式,可选)。填入后将固定该证书。
|
||||||
|
启用“跳过证书验证”时,请勿使用 '获取证书'。</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>获取证书</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1599,4 +1599,14 @@
|
||||||
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
<value>自動從訂閱分組新增過濾後的配置</value>
|
<value>自動從訂閱分組新增過濾後的配置</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
|
||||||
|
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -204,54 +204,6 @@ public partial class CoreConfigSingboxService
|
||||||
return await Task.FromResult<BaseServer4Sbox?>(null);
|
return await Task.FromResult<BaseServer4Sbox?>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!node.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
|
|
||||||
if (hasCycle)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
|
|
||||||
if (childProfiles.Count <= 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
switch (node.ConfigType)
|
|
||||||
{
|
|
||||||
case EConfigType.PolicyGroup:
|
|
||||||
if (ignoreOriginChain)
|
|
||||||
{
|
|
||||||
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EConfigType.ProxyChain:
|
|
||||||
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
|
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -280,7 +232,7 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
|
if (node.StreamSecurity is Global.StreamSecurityReality or Global.StreamSecurity)
|
||||||
{
|
{
|
||||||
var server_name = string.Empty;
|
var server_name = string.Empty;
|
||||||
if (node.Sni.IsNotEmpty())
|
if (node.Sni.IsNotEmpty())
|
||||||
|
|
@ -307,7 +259,18 @@ public partial class CoreConfigSingboxService
|
||||||
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (node.StreamSecurity == Global.StreamSecurityReality)
|
if (node.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
var certs = node.Cert
|
||||||
|
?.Split("-----END CERTIFICATE-----", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => s.TrimEx())
|
||||||
|
.Where(s => !s.IsNullOrEmpty())
|
||||||
|
.Select(s => s + "\n-----END CERTIFICATE-----")
|
||||||
|
.Select(s => s.Replace("\r\n", "\n"))
|
||||||
|
.ToList() ?? new();
|
||||||
|
tls.certificate = certs.Count > 0 ? certs : null;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
tls.reality = new Reality4Sbox()
|
tls.reality = new Reality4Sbox()
|
||||||
{
|
{
|
||||||
|
|
@ -404,6 +367,54 @@ public partial class CoreConfigSingboxService
|
||||||
return await Task.FromResult(0);
|
return await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!node.ConfigType.IsGroupType())
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
|
||||||
|
if (hasCycle)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
|
||||||
|
if (childProfiles.Count <= 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
switch (node.ConfigType)
|
||||||
|
{
|
||||||
|
case EConfigType.PolicyGroup:
|
||||||
|
if (ignoreOriginChain)
|
||||||
|
{
|
||||||
|
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.ProxyChain:
|
||||||
|
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
|
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
|
||||||
{
|
{
|
||||||
if (node.Subid.IsNullOrEmpty())
|
if (node.Subid.IsNullOrEmpty())
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,13 @@ public partial class CoreConfigV2rayService
|
||||||
var host = node.RequestHost.TrimEx();
|
var host = node.RequestHost.TrimEx();
|
||||||
var path = node.Path.TrimEx();
|
var path = node.Path.TrimEx();
|
||||||
var sni = node.Sni.TrimEx();
|
var sni = node.Sni.TrimEx();
|
||||||
|
var certs = node.Cert
|
||||||
|
?.Split("-----END CERTIFICATE-----", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => s.TrimEx())
|
||||||
|
.Where(s => !s.IsNullOrEmpty())
|
||||||
|
.Select(s => s + "\n-----END CERTIFICATE-----")
|
||||||
|
.Select(s => s.Replace("\r\n", "\n"))
|
||||||
|
.ToList() ?? new();
|
||||||
var useragent = "";
|
var useragent = "";
|
||||||
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
|
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -277,6 +284,21 @@ public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
tlsSettings.serverName = Utils.String2List(host)?.First();
|
tlsSettings.serverName = Utils.String2List(host)?.First();
|
||||||
}
|
}
|
||||||
|
if (certs.Count > 0)
|
||||||
|
{
|
||||||
|
var certsettings = new List<CertificateSettings4Ray>();
|
||||||
|
foreach (var cert in certs)
|
||||||
|
{
|
||||||
|
var certPerLine = cert.Split("\n").ToList();
|
||||||
|
certsettings.Add(new CertificateSettings4Ray
|
||||||
|
{
|
||||||
|
certificate = certPerLine,
|
||||||
|
usage = "verify",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tlsSettings.certificates = certsettings;
|
||||||
|
tlsSettings.disableSystemRoot = true;
|
||||||
|
}
|
||||||
streamSettings.tlsSettings = tlsSettings;
|
streamSettings.tlsSettings = tlsSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ public class AddServerViewModel : MyReactiveObject
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? CoreType { get; set; }
|
public string? CoreType { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string Cert { get; set; }
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||||
|
|
||||||
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
|
|
@ -15,6 +19,10 @@ public class AddServerViewModel : MyReactiveObject
|
||||||
_config = AppManager.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
_updateView = updateView;
|
_updateView = updateView;
|
||||||
|
|
||||||
|
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
{
|
||||||
|
await FetchCert();
|
||||||
|
});
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await SaveServerAsync();
|
await SaveServerAsync();
|
||||||
|
|
@ -33,6 +41,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||||
SelectedSource = JsonUtils.DeepCopy(profileItem);
|
SelectedSource = JsonUtils.DeepCopy(profileItem);
|
||||||
}
|
}
|
||||||
CoreType = SelectedSource?.CoreType?.ToString();
|
CoreType = SelectedSource?.CoreType?.ToString();
|
||||||
|
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveServerAsync()
|
private async Task SaveServerAsync()
|
||||||
|
|
@ -77,6 +86,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
||||||
|
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
|
||||||
|
|
||||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -88,4 +98,23 @@ public class AddServerViewModel : MyReactiveObject
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task FetchCert()
|
||||||
|
{
|
||||||
|
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var domain = SelectedSource.Address;
|
||||||
|
var serverName = SelectedSource.Sni.IsNullOrEmpty() ? SelectedSource.Address : SelectedSource.Sni;
|
||||||
|
if (!Utils.IsDomain(serverName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SelectedSource.Port > 0)
|
||||||
|
{
|
||||||
|
domain += $":{SelectedSource.Port}";
|
||||||
|
}
|
||||||
|
Cert = await Utils.GetCertPem(domain, serverName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -713,7 +713,7 @@
|
||||||
Grid.Row="7"
|
Grid.Row="7"
|
||||||
ColumnDefinitions="180,Auto"
|
ColumnDefinitions="180,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"
|
||||||
|
|
@ -767,6 +767,53 @@
|
||||||
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.TbCertPinning}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Classes="IconButton"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="{StaticResource MarginLr8}">
|
||||||
|
<Button.Content>
|
||||||
|
<PathIcon Data="{StaticResource SemiIconMore}" >
|
||||||
|
<PathIcon.RenderTransform>
|
||||||
|
<RotateTransform Angle="90" />
|
||||||
|
</PathIcon.RenderTransform>
|
||||||
|
</PathIcon>
|
||||||
|
</Button.Content>
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbCertPinningTips}" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnFetchCert"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Content="{x:Static resx:ResUI.TbFetchCert}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtCert"
|
||||||
|
Width="400"
|
||||||
|
MinHeight="100"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Classes="TextArea"
|
||||||
|
MinLines="6"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="gridRealityMore"
|
x:Name="gridRealityMore"
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,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.Cert, v => v.txtCert.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);
|
||||||
|
|
@ -193,6 +194,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -928,6 +928,7 @@
|
||||||
<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" />
|
||||||
|
|
@ -995,6 +996,47 @@
|
||||||
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.TbCertPinning}" />
|
||||||
|
|
||||||
|
<materialDesign:PopupBox
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
StaysOpen="True"
|
||||||
|
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbCertPinningTips}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnFetchCert"
|
||||||
|
Width="100"
|
||||||
|
Margin="{StaticResource MarginLeftRight4}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Content="{x:Static resx:ResUI.TbFetchCert}"
|
||||||
|
Style="{StaticResource DefButton}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtCert"
|
||||||
|
Width="400"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
MinLines="6"
|
||||||
|
Style="{StaticResource MyOutlinedTextBox}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</materialDesign:PopupBox>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="gridRealityMore"
|
x:Name="gridRealityMore"
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,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.Cert, v => v.txtCert.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);
|
||||||
|
|
@ -188,6 +189,7 @@ public partial class AddServerWindow
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue