diff --git a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs index 8e12ecf4..7b7153e4 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs @@ -223,6 +223,11 @@ public class Hysteria2Fmt : BaseFmt var sha = item.CertSha; dicQuery.Add("pinSHA256", Utils.UrlEncode(sha)); } + else if (!item.Cert.IsNullOrEmpty() + && CertPemManager.GetLeafCertSha256Thumbprint(item.Cert) is { Length: > 0 } thumbprint) + { + dicQuery.Add("pinSHA256", Utils.UrlEncode(thumbprint)); + } if (!item.EchConfigList.IsNullOrEmpty()) { dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList)); diff --git a/v2rayN/ServiceLib/Manager/CertPemManager.cs b/v2rayN/ServiceLib/Manager/CertPemManager.cs index adb48368..0df8301d 100644 --- a/v2rayN/ServiceLib/Manager/CertPemManager.cs +++ b/v2rayN/ServiceLib/Manager/CertPemManager.cs @@ -281,6 +281,37 @@ public class CertPemManager } } + public static string GetLeafCertSha256Thumbprint(string pemCertChain, bool includeColon = false) + { + var certs = ParsePemChain(pemCertChain); + if (certs.Count == 0) + { + return string.Empty; + } + foreach (var certStr in certs) + { + try + { + var cert = X509Certificate2.CreateFromPem(certStr); + var extension = cert.Extensions + .OfType() + .FirstOrDefault(); + var isCa = extension?.CertificateAuthority == true; + if (isCa) + { + continue; + } + var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256); + return includeColon ? string.Join(":", thumbprint.Chunk(2).Select(c => new string(c))) : thumbprint; + } + catch + { + // Ignore + } + } + return string.Empty; + } + private static readonly Lazy _chromeRootCerts = new(() => { var pemText = EmbedUtils.GetEmbedText(Global.ChromeRootCertFileName);