Cert Chain Pinning

This commit is contained in:
DHR60 2025-11-01 11:56:24 +08:00
parent bd7b7ca1e8
commit 5cc9ab5327
13 changed files with 88 additions and 19 deletions

View file

@ -2797,6 +2797,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Fetch Certificate Chain 的本地化字符串。
/// </summary>
public static string TbFetchCertChain {
get {
return ResourceManager.GetString("TbFetchCertChain", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Fingerprint 的本地化字符串。 /// 查找类似 Fingerprint 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1612,4 +1612,7 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value> <value>Fetch Certificate</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
</root> </root>

View file

@ -1609,4 +1609,7 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value> <value>Fetch Certificate</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
</root> </root>

View file

@ -1612,4 +1612,7 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value> <value>Fetch Certificate</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
</root> </root>

View file

@ -1612,4 +1612,7 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value> <value>Fetch Certificate</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
</root> </root>

View file

@ -1612,4 +1612,7 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value> <value>Fetch Certificate</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
</root> </root>

View file

@ -1609,4 +1609,7 @@
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value> <value>获取证书</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>获取证书链</value>
</data>
</root> </root>

View file

@ -1609,4 +1609,7 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="TbFetchCert" xml:space="preserve"> <data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value> <value>Fetch Certificate</value>
</data> </data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
</root> </root>

View file

@ -12,6 +12,7 @@ public class AddServerViewModel : MyReactiveObject
public string Cert { get; set; } public string Cert { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { 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)
@ -23,6 +24,10 @@ public class AddServerViewModel : MyReactiveObject
{ {
await FetchCert(); await FetchCert();
}); });
FetchCertChainCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCertChain();
});
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await SaveServerAsync(); await SaveServerAsync();
@ -117,4 +122,24 @@ public class AddServerViewModel : MyReactiveObject
} }
Cert = await Utils.GetCertPem(domain, serverName); Cert = await Utils.GetCertPem(domain, serverName);
} }
private async Task FetchCertChain()
{
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}";
}
var certs = await Utils.GetCertChainPem(domain, serverName);
Cert = string.Join("\n", certs);
}
} }

View file

@ -607,10 +607,10 @@
<Button <Button
x:Name="btnExtra" x:Name="btnExtra"
Classes="IconButton" Margin="{StaticResource MarginLr8}"
Margin="{StaticResource MarginLr8}"> Classes="IconButton">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}" > <PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform> <PathIcon.RenderTransform>
<RotateTransform Angle="90" /> <RotateTransform Angle="90" />
</PathIcon.RenderTransform> </PathIcon.RenderTransform>
@ -768,7 +768,7 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="5"
Grid.Column="0" Grid.Column="0"
@ -778,11 +778,11 @@
<Button <Button
Grid.Row="5" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
Classes="IconButton" Margin="{StaticResource MarginLr8}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="{StaticResource MarginLr8}"> Classes="IconButton">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}" > <PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform> <PathIcon.RenderTransform>
<RotateTransform Angle="90" /> <RotateTransform Angle="90" />
</PathIcon.RenderTransform> </PathIcon.RenderTransform>
@ -795,11 +795,16 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinningTips}" /> Text="{x:Static resx:ResUI.TbCertPinningTips}" />
<Button <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
x:Name="btnFetchCert" <Button
HorizontalAlignment="Left" x:Name="btnFetchCert"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCert}" /> Content="{x:Static resx:ResUI.TbFetchCert}" />
<Button
x:Name="btnFetchCertChain"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel>
<TextBox <TextBox
x:Name="txtCert" x:Name="txtCert"
Width="400" Width="400"

View file

@ -195,6 +195,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
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.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });

View file

@ -1018,13 +1018,20 @@
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertPinningTips}" Text="{x:Static resx:ResUI.TbCertPinningTips}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<Button <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
x:Name="btnFetchCert" <Button
Width="100" x:Name="btnFetchCert"
Margin="{StaticResource MarginLeftRight4}" Width="100"
HorizontalAlignment="Left" Margin="{StaticResource MarginLeftRight4}"
Content="{x:Static resx:ResUI.TbFetchCert}" Content="{x:Static resx:ResUI.TbFetchCert}"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button
x:Name="btnFetchCertChain"
Width="100"
Margin="{StaticResource MarginLeftRight4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}"
Style="{StaticResource DefButton}" />
</StackPanel>
<TextBox <TextBox
x:Name="txtCert" x:Name="txtCert"
Width="400" Width="400"

View file

@ -190,6 +190,7 @@ public partial class AddServerWindow
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.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });