Add Cert SHA-256 pinning support

This commit is contained in:
DHR60 2026-01-08 17:38:21 +08:00
parent f3b894015e
commit e75aa6826b
20 changed files with 183 additions and 16 deletions

View file

@ -253,6 +253,7 @@ public static class ConfigHandler
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled; item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert; item.Cert = profileItem.Cert;
item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList; item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery; item.EchForceQuery = profileItem.EchForceQuery;
} }

View file

@ -74,6 +74,10 @@ public class BaseFmt
{ {
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList)); dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
} }
if (item.CertSha.IsNotEmpty())
{
dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha));
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@ -214,6 +218,7 @@ public class BaseFmt
item.SpiderX = GetQueryDecoded(query, "spx"); item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv"); item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
item.EchConfigList = GetQueryDecoded(query, "ech"); item.EchConfigList = GetQueryDecoded(query, "ech");
item.CertSha = GetQueryDecoded(query, "pcs");
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{ {

View file

@ -168,7 +168,9 @@ public class ActionPrecheckManager
if (item.StreamSecurity == Global.StreamSecurity) if (item.StreamSecurity == Global.StreamSecurity)
{ {
// check certificate validity // check certificate validity
if ((!item.Cert.IsNullOrEmpty()) && (CertPemManager.ParsePemChain(item.Cert).Count == 0)) if (!item.Cert.IsNullOrEmpty()
&& (CertPemManager.ParsePemChain(item.Cert).Count == 0)
&& !item.CertSha.IsNullOrEmpty())
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate")); errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
} }

View file

@ -416,4 +416,22 @@ public class CertPemManager
return string.Concat(pemList); return string.Concat(pemList);
} }
public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false)
{
try
{
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;
}
catch
{
return string.Empty;
}
}
} }

View file

@ -161,6 +161,7 @@ public class ProfileItem : ReactiveObject
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; } public string Cert { get; set; }
public string CertSha { get; set; }
public string EchConfigList { get; set; } public string EchConfigList { get; set; }
public string EchForceQuery { get; set; } public string EchForceQuery { get; set; }
} }

View file

@ -355,6 +355,7 @@ public class TlsSettings4Ray
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 List<CertificateSettings4Ray>? certificates { get; set; }
public string? pinnedPeerCertSha256 { get; set; }
public bool? disableSystemRoot { get; set; } public bool? disableSystemRoot { get; set; }
public string? echConfigList { get; set; } public string? echConfigList { get; set; }
public string? echForceQuery { get; set; } public string? echForceQuery { get; set; }

View file

@ -2617,7 +2617,7 @@ namespace ServiceLib.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Server Certificate (PEM format, optional) /// 查找类似 Pinned certificate (fill in either one)
///When specified, the certificate will be pinned, and &quot;Allow Insecure&quot; will be disabled. ///When specified, the certificate will be pinned, and &quot;Allow Insecure&quot; will be disabled.
/// ///
///The &quot;Get Certificate&quot; action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。 ///The &quot;Get Certificate&quot; action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。
@ -2628,6 +2628,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Certificate fingerprint (SHA-256) 的本地化字符串。
/// </summary>
public static string TbCertSha256Tips {
get {
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。 /// 查找类似 Clear system proxy 的本地化字符串。
/// </summary> /// </summary>
@ -2889,6 +2898,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Full certificate (chain), PEM format 的本地化字符串。
/// </summary>
public static string TbFullCertTips {
get {
return ResourceManager.GetString("TbFullCertTips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core&apos;s basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。 /// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core&apos;s basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1647,4 +1647,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root> </root>

View file

@ -1606,10 +1606,10 @@
<value>Certificate Pinning</value> <value>Certificate Pinning</value>
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>Certificat serveur (format PEM, facultatif) <value>Pinned certificate (fill in either one)
Si le certificat est défini, il est fixé et loption « Ignorer la vérification » est désactivée. When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
Si un certificat auto-signé est utilisé ou si le système contient une CA non fiable ou malveillante, laction « Obtenir le certificat » peut échouer.</value> The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data> </data>
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Obtenir le certificat</value> <value>Obtenir le certificat</value>
@ -1644,4 +1644,10 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root> </root>

View file

@ -1609,7 +1609,7 @@
<value>Certificate Pinning</value> <value>Certificate Pinning</value>
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional) <value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value> The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@ -1647,4 +1647,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root> </root>

View file

@ -1609,7 +1609,7 @@
<value>Certificate Pinning</value> <value>Certificate Pinning</value>
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional) <value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value> The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@ -1647,4 +1647,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root> </root>

View file

@ -1609,7 +1609,7 @@
<value>Certificate Pinning</value> <value>Certificate Pinning</value>
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional) <value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value> The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@ -1647,4 +1647,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root> </root>

View file

@ -1606,7 +1606,7 @@
<value>固定证书</value> <value>固定证书</value>
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选 <value>固定证书(二选一填写即可
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。 当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
“获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任或恶意的 CA。</value> “获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任或恶意的 CA。</value>
@ -1644,4 +1644,10 @@
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>完整证书PEM 格式</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>证书指纹Sha256</value>
</data>
</root> </root>

View file

@ -1606,7 +1606,7 @@
<value>憑證綁定</value> <value>憑證綁定</value>
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>伺服器憑證PEM 格式,可選 <value>固定憑證(二選一填寫即可
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。 若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
若使用自簽憑證,或系統中存在不受信任或惡意的 CA「取得憑證」動作可能會失敗。</value> 若使用自簽憑證,或系統中存在不受信任或惡意的 CA「取得憑證」動作可能會失敗。</value>
@ -1644,4 +1644,10 @@
<data name="TbEchForceQuery" xml:space="preserve"> <data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root> </root>

View file

@ -301,6 +301,10 @@ public partial class CoreConfigV2rayService
tlsSettings.disableSystemRoot = true; tlsSettings.disableSystemRoot = true;
tlsSettings.allowInsecure = false; tlsSettings.allowInsecure = false;
} }
else if (!node.CertSha.IsNullOrEmpty())
{
tlsSettings.pinnedPeerCertSha256 = node.CertSha;
}
streamSettings.tlsSettings = tlsSettings; streamSettings.tlsSettings = tlsSettings;
} }

View file

@ -14,6 +14,9 @@ public class AddServerViewModel : MyReactiveObject
[Reactive] [Reactive]
public string CertTip { get; set; } public string CertTip { get; set; }
[Reactive]
public string CertSha { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; } public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@ -39,6 +42,12 @@ public class AddServerViewModel : MyReactiveObject
this.WhenAnyValue(x => x.Cert) this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip()); .Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.CertSha)
.Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertSha());
if (profileItem.IndexId.IsNullOrEmpty()) if (profileItem.IndexId.IsNullOrEmpty())
{ {
profileItem.Network = Global.DefaultNetwork; profileItem.Network = Global.DefaultNetwork;
@ -97,7 +106,8 @@ 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; SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{ {
@ -113,10 +123,36 @@ public class AddServerViewModel : MyReactiveObject
private void UpdateCertTip(string? errorMessage = null) private void UpdateCertTip(string? errorMessage = null)
{ {
CertTip = errorMessage.IsNullOrEmpty() CertTip = errorMessage.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet) ? ((Cert.IsNullOrEmpty() && CertSha.IsNullOrEmpty()) ? ResUI.CertNotSet : ResUI.CertSet)
: errorMessage; : errorMessage;
} }
private void UpdateCertSha()
{
if (Cert.IsNullOrEmpty())
{
return;
}
var certList = CertPemManager.ParsePemChain(Cert);
if (certList.Count == 0)
{
return;
}
List<string> shaList = new();
foreach (var cert in certList)
{
var sha = CertPemManager.GetCertSha256Thumbprint(cert);
if (sha.IsNullOrEmpty())
{
return;
}
shaList.Add(sha);
}
CertSha = string.Join('|', shaList);
}
private async Task FetchCert() private async Task FetchCert()
{ {
if (SelectedSource.StreamSecurity != Global.StreamSecurity) if (SelectedSource.StreamSecurity != Global.StreamSecurity)
@ -142,8 +178,8 @@ public class AddServerViewModel : MyReactiveObject
{ {
domain += $":{SelectedSource.Port}"; domain += $":{SelectedSource.Port}";
} }
string certError;
(Cert, certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName); (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
UpdateCertTip(certError); UpdateCertTip(certError);
} }
@ -172,8 +208,8 @@ public class AddServerViewModel : MyReactiveObject
{ {
domain += $":{SelectedSource.Port}"; domain += $":{SelectedSource.Port}";
} }
string certError;
(var certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName); var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
Cert = CertPemManager.ConcatenatePemChain(certs); Cert = CertPemManager.ConcatenatePemChain(certs);
UpdateCertTip(certError); UpdateCertTip(certError);
} }

View file

@ -841,6 +841,23 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" /> Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel> </StackPanel>
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCertSha256Pinning"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFullCertTips}"
TextWrapping="Wrap" />
<TextBox <TextBox
x:Name="txtCert" x:Name="txtCert"
Width="400" Width="400"

View file

@ -186,6 +186,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.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);

View file

@ -1072,6 +1072,26 @@
Content="{x:Static resx:ResUI.TbFetchCertChain}" Content="{x:Static resx:ResUI.TbFetchCertChain}"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
</StackPanel> </StackPanel>
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCertSha256Pinning"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFullCertTips}"
TextWrapping="Wrap" />
<TextBox <TextBox
x:Name="txtCert" x:Name="txtCert"
Width="400" Width="400"

View file

@ -181,6 +181,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.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);