diff --git a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
index 51535990..2b819940 100644
--- a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
+++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
@@ -136,7 +136,9 @@ public class NodeValidator
}
// Check for deprecated allowInsecure property when TLS is enabled
- if (item.AllowInsecure == "true")
+ if (item.AllowInsecure == "true"
+ && item.Cert.IsNullOrEmpty()
+ && item.CertSha.IsNullOrEmpty())
{
v.Warning(ResUI.MsgAllowInsecureDeprecated);
}
diff --git a/v2rayN/ServiceLib/Manager/CertPemManager.cs b/v2rayN/ServiceLib/Manager/CertPemManager.cs
index 0a58e2de..7f5d3889 100644
--- a/v2rayN/ServiceLib/Manager/CertPemManager.cs
+++ b/v2rayN/ServiceLib/Manager/CertPemManager.cs
@@ -4,16 +4,15 @@ using System.Security.Cryptography.X509Certificates;
namespace ServiceLib.Manager;
///
-/// Manager for certificate operations with CA pinning to prevent MITM attacks
+/// Manager for certificate operations with CA pinning to prevent MITM attacks
///
public class CertPemManager
{
private static readonly string _tag = "CertPemManager";
private static readonly Lazy _instance = new(() => new());
- public static CertPemManager Instance => _instance.Value;
///
- /// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
+ /// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
///
private static readonly HashSet TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
{
@@ -24,21 +23,17 @@ public class CertPemManager
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
- "CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root
"C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA
"1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA
"3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA
"4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA
"7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA
"62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2
- "F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA
- "4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA
"0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority
"1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority
"41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA
"E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna
"C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority
- "EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA
"6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány
"3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009
"CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3
@@ -46,10 +41,6 @@ public class CertPemManager
"45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2
"2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2
"568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2
- "0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial
- "0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking
- "70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium
- "BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC
"5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA
"BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority
"513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2
@@ -62,7 +53,6 @@ public class CertPemManager
"E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2
"9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1
"59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA
- "DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1
"91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2
"F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011
"8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3
@@ -116,16 +106,12 @@ public class CertPemManager
"C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017
"BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017
"657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2
- "97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority
- "945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority
- "55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority
"88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority
"554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS
"319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45
"5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45
"4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46
"CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46
- "9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020
"FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA
"6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA
"FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA
@@ -179,7 +165,6 @@ public class CertPemManager
"578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020
"78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023
"EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023
- "BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB
"3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA
"3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2
"3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12
@@ -200,10 +185,13 @@ public class CertPemManager
"B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023
};
+ public static CertPemManager Instance => _instance.Value;
+
///
- /// Get certificate in PEM format from a server with CA pinning validation
+ /// 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, bool allowInsecure = false)
+ public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4,
+ List? verifyPeerCertByName = null, bool allowInsecure = false)
{
try
{
@@ -216,13 +204,14 @@ public class CertPemManager
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) =>
- ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure));
+ ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, verifyPeerCertByName ?? [],
+ allowInsecure));
await using var ssl = new SslStream(client.GetStream(), false, callback);
var sslOptions = new SslClientAuthenticationOptions
{
TargetHost = serverName,
- RemoteCertificateValidationCallback = callback
+ RemoteCertificateValidationCallback = callback,
};
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
@@ -249,9 +238,10 @@ public class CertPemManager
}
///
- /// Get certificate chain in PEM format from a server with CA pinning validation
+ /// 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, bool allowInsecure = false)
+ public async Task<(List, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4,
+ List? verifyPeerCertByName = null, bool allowInsecure = false)
{
var pemList = new List();
try
@@ -265,13 +255,14 @@ public class CertPemManager
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) =>
- ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure));
+ ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, verifyPeerCertByName ?? [],
+ allowInsecure));
await using var ssl = new SslStream(client.GetStream(), false, callback);
var sslOptions = new SslClientAuthenticationOptions
{
TargetHost = serverName,
- RemoteCertificateValidationCallback = callback
+ RemoteCertificateValidationCallback = callback,
};
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
@@ -301,13 +292,14 @@ public class CertPemManager
}
///
- /// Validate server certificate with CA pinning
+ /// Validate server certificate with CA pinning
///
- private bool ValidateServerCertificate(
+ private static bool ValidateServerCertificate(
object _,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors,
+ List verifyPeerCertByName,
bool allowInsecure)
{
if (certificate == null)
@@ -321,22 +313,21 @@ public class CertPemManager
return true;
}
- // Check certificate name mismatch
- if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
- {
- return false;
- }
-
// Build certificate chain
var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);
var certChain = chain ?? new X509Chain();
- certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
- certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
- certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
- certChain.ChainPolicy.VerificationTime = DateTime.Now;
+ if (chain == null)
+ {
+ certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
+ certChain.ChainPolicy.VerificationTime = DateTime.UtcNow;
- certChain.Build(cert2);
+ if (!certChain.Build(cert2))
+ {
+ return false;
+ }
+ }
// Find root CA
if (certChain.ChainElements.Count == 0)
@@ -344,10 +335,37 @@ public class CertPemManager
return false;
}
- var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate;
+ var rootCert = certChain.ChainElements[^1].Certificate;
var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256);
- return TrustedCaThumbprints.Contains(rootThumbprint);
+ if (!TrustedCaThumbprints.Contains(rootThumbprint))
+ {
+ return false;
+ }
+
+ if (!sslPolicyErrors.HasFlag(
+ SslPolicyErrors.RemoteCertificateNameMismatch))
+ {
+ return true;
+ }
+
+ if (verifyPeerCertByName.Count == 0)
+ {
+ return false;
+ }
+
+ foreach (var ext in cert2.Extensions)
+ {
+ if (ext is not X509SubjectAlternativeNameExtension san)
+ {
+ continue;
+ }
+
+ return san.EnumerateDnsNames().Any(dnsName =>
+ verifyPeerCertByName.Contains(dnsName, StringComparer.OrdinalIgnoreCase));
+ }
+
+ return false;
}
public static string ExportCertToPem(X509Certificate2 cert)
@@ -358,8 +376,8 @@ public class CertPemManager
}
///
- /// Parse concatenated PEM certificates string into a list of individual certificates
- /// Normalizes format: removes line breaks from base64 content for better compatibility
+ /// Parse concatenated PEM certificates string into a list of individual certificates
+ /// Normalizes format: removes line breaks from base64 content for better compatibility
///
/// Concatenated PEM certificates string (supports both \r\n and \n line endings)
/// List of individual PEM certificate strings with normalized format
@@ -411,18 +429,13 @@ public class CertPemManager
}
///
- /// Concatenate a list of PEM certificates into a single string
+ /// Concatenate a list of PEM certificates into a single string
///
/// List of individual PEM certificate strings
/// Concatenated PEM certificates string
- public static string ConcatenatePemChain(IEnumerable pemList)
+ public static string ConcatenatePemChain(IEnumerable? pemList)
{
- if (pemList == null)
- {
- return string.Empty;
- }
-
- return string.Concat(pemList);
+ return pemList == null ? string.Empty : string.Concat(pemList);
}
public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false)
@@ -431,11 +444,7 @@ public class CertPemManager
{
var cert = X509Certificate2.CreateFromPem(pemCert);
var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256);
- if (includeColon)
- {
- return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c)));
- }
- return thumbprint;
+ return includeColon ? string.Join(":", thumbprint.Chunk(2).Select(c => new string(c))) : thumbprint;
}
catch
{
diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs
index 169e8a44..f5906f21 100644
--- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs
@@ -464,7 +464,9 @@ public class AddServerViewModel : MyReactiveObject
domain += $":{SelectedSource.Port}";
}
- (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch);
+ (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName,
+ verifyPeerCertByName: Utils.String2List(SelectedSource.VerifyPeerCertByName),
+ allowInsecure: AllowInsecureCertFetch);
UpdateCertTip(certError);
}
@@ -489,7 +491,9 @@ public class AddServerViewModel : MyReactiveObject
domain += $":{SelectedSource.Port}";
}
- var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch);
+ var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName,
+ verifyPeerCertByName: Utils.String2List(SelectedSource.VerifyPeerCertByName),
+ allowInsecure: AllowInsecureCertFetch);
Cert = CertPemManager.ConcatenatePemChain(certs);
UpdateCertTip(certError);
}