mirror of
https://github.com/QL-Win/QuickLook.git
synced 2026-01-13 07:05:24 +08:00
Add password support for protected certificates
Introduces UI and logic to handle password-protected certificate files. The CertViewerControl now prompts for a password if needed, and attempts to reload the certificate with the provided password. Refactored certificate loading flow to support this feature.
This commit is contained in:
@@ -8,15 +8,17 @@ internal sealed class CertLoadResult
|
|||||||
public X509Certificate2 Certificate { get; }
|
public X509Certificate2 Certificate { get; }
|
||||||
public string Message { get; }
|
public string Message { get; }
|
||||||
public string RawContent { get; }
|
public string RawContent { get; }
|
||||||
|
public bool NeedsPassword { get; }
|
||||||
|
|
||||||
public CertLoadResult(bool success, X509Certificate2 certificate, string message, string rawContent)
|
public CertLoadResult(bool success, X509Certificate2 certificate, string message, string rawContent, bool needsPassword = false)
|
||||||
{
|
{
|
||||||
Success = success;
|
Success = success;
|
||||||
Certificate = certificate;
|
Certificate = certificate;
|
||||||
Message = message;
|
Message = message;
|
||||||
RawContent = rawContent;
|
RawContent = rawContent;
|
||||||
|
NeedsPassword = needsPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CertLoadResult From(bool success, X509Certificate2 certificate, string message, string rawContent)
|
public static CertLoadResult From(bool success, X509Certificate2 certificate, string message, string rawContent, bool needsPassword = false)
|
||||||
=> new CertLoadResult(success, certificate, message, rawContent);
|
=> new CertLoadResult(success, certificate, message, rawContent, needsPassword);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ internal static class CertUtils
|
|||||||
/// - Message: an informational or error message
|
/// - Message: an informational or error message
|
||||||
/// - RawContent: original file text or hex when parsing failed
|
/// - RawContent: original file text or hex when parsing failed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static CertLoadResult TryLoadCertificate(string path)
|
public static CertLoadResult TryLoadCertificate(string path, string password = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -24,12 +24,16 @@ internal static class CertUtils
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cert = new X509Certificate2(path);
|
var cert = !string.IsNullOrEmpty(password)
|
||||||
|
? new X509Certificate2(path, password)
|
||||||
|
: new X509Certificate2(path);
|
||||||
return new CertLoadResult(true, cert, string.Empty, null);
|
return new CertLoadResult(true, cert, string.Empty, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new CertLoadResult(false, null, "Failed to load PFX/P12: " + ex.Message, null);
|
var isPasswordError = ex is System.Security.Cryptography.CryptographicException ||
|
||||||
|
(ex.Message?.IndexOf("password", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
return new CertLoadResult(false, null, "Failed to load PFX/P12: " + ex.Message, null, isPasswordError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,29 +12,41 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<DataGrid x:Name="PropertyList"
|
<!-- Password panel (hidden by default) -->
|
||||||
Grid.Column="0"
|
<StackPanel x:Name="PasswordPanel" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="8" Visibility="Collapsed" VerticalAlignment="Center">
|
||||||
Margin="8"
|
<TextBlock Text="This certificate appears to be password-protected. Enter password:" VerticalAlignment="Center" Margin="0,0,8,0" />
|
||||||
AutoGenerateColumns="False"
|
<PasswordBox x:Name="InlinePasswordBox" Width="220" Margin="0,0,8,0" />
|
||||||
IsReadOnly="True"
|
<Button x:Name="LoadWithPasswordButton" Content="Load" Width="80" Margin="0,0,8,0" Click="LoadWithPasswordButton_Click" />
|
||||||
HeadersVisibility="Column"
|
<Button x:Name="CancelPasswordButton" Content="Cancel" Width="80" Click="CancelPasswordButton_Click" />
|
||||||
RowHeaderWidth="0">
|
</StackPanel>
|
||||||
<DataGrid.Columns>
|
|
||||||
<DataGridTextColumn Header="Field" Binding="{Binding Key}" Width="150" />
|
|
||||||
<DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="*" />
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid>
|
|
||||||
|
|
||||||
<TextBox x:Name="RawText"
|
<TabControl Grid.Row="1" Grid.ColumnSpan="2" Margin="8">
|
||||||
Grid.Column="1"
|
<TabItem Header="Details">
|
||||||
Margin="8"
|
<DataGrid x:Name="PropertyList"
|
||||||
AcceptsReturn="True"
|
AutoGenerateColumns="False"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
IsReadOnly="True"
|
||||||
IsReadOnly="True"
|
HeadersVisibility="Column"
|
||||||
TextWrapping="Wrap"
|
RowHeaderWidth="0"
|
||||||
VerticalScrollBarVisibility="Auto" />
|
Margin="0">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Field" Binding="{Binding Key}" Width="150" />
|
||||||
|
<DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="*" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Raw">
|
||||||
|
<TextBox x:Name="RawText"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
Margin="0" />
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -7,13 +7,43 @@ namespace QuickLook.Plugin.CertViewer;
|
|||||||
|
|
||||||
public partial class CertViewerControl : UserControl, IDisposable
|
public partial class CertViewerControl : UserControl, IDisposable
|
||||||
{
|
{
|
||||||
|
private string _currentPath;
|
||||||
|
|
||||||
public CertViewerControl()
|
public CertViewerControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a certificate file from path. If the file appears password-protected,
|
||||||
|
/// the control will show an inline password input and allow the user to retry.
|
||||||
|
/// </summary>
|
||||||
|
public void LoadFromPath(string path)
|
||||||
|
{
|
||||||
|
_currentPath = path;
|
||||||
|
|
||||||
|
var result = CertUtils.TryLoadCertificate(path);
|
||||||
|
|
||||||
|
if (!result.Success && result.NeedsPassword)
|
||||||
|
{
|
||||||
|
// show password UI
|
||||||
|
PasswordPanel.Visibility = System.Windows.Visibility.Visible;
|
||||||
|
PropertyList.ItemsSource = null;
|
||||||
|
RawText.Text = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordPanel.Visibility = System.Windows.Visibility.Collapsed;
|
||||||
|
|
||||||
|
if (result.Success && result.Certificate != null)
|
||||||
|
LoadCertificate(result.Certificate);
|
||||||
|
else
|
||||||
|
LoadRaw(path, result.Message, result.RawContent);
|
||||||
|
}
|
||||||
|
|
||||||
public void LoadCertificate(X509Certificate2 cert)
|
public void LoadCertificate(X509Certificate2 cert)
|
||||||
{
|
{
|
||||||
|
PasswordPanel.Visibility = System.Windows.Visibility.Collapsed;
|
||||||
var items = new List<KeyValuePair<string, string>>
|
var items = new List<KeyValuePair<string, string>>
|
||||||
{
|
{
|
||||||
new("Subject", cert.Subject),
|
new("Subject", cert.Subject),
|
||||||
@@ -32,6 +62,7 @@ public partial class CertViewerControl : UserControl, IDisposable
|
|||||||
|
|
||||||
public void LoadRaw(string path, string message, string content)
|
public void LoadRaw(string path, string message, string content)
|
||||||
{
|
{
|
||||||
|
PasswordPanel.Visibility = System.Windows.Visibility.Collapsed;
|
||||||
PropertyList.ItemsSource = new List<KeyValuePair<string, string>>
|
PropertyList.ItemsSource = new List<KeyValuePair<string, string>>
|
||||||
{
|
{
|
||||||
new("Path", path),
|
new("Path", path),
|
||||||
@@ -44,4 +75,34 @@ public partial class CertViewerControl : UserControl, IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadWithPasswordButton_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var pwd = InlinePasswordBox.Password;
|
||||||
|
if (string.IsNullOrEmpty(_currentPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var result = CertUtils.TryLoadCertificate(_currentPath, pwd);
|
||||||
|
PasswordPanel.Visibility = System.Windows.Visibility.Collapsed;
|
||||||
|
|
||||||
|
if (result.Success && result.Certificate != null)
|
||||||
|
{
|
||||||
|
LoadCertificate(result.Certificate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LoadRaw(_currentPath, result.Message, result.RawContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelPasswordButton_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// show failure/raw view
|
||||||
|
if (string.IsNullOrEmpty(_currentPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var result = CertUtils.TryLoadCertificate(_currentPath);
|
||||||
|
PasswordPanel.Visibility = System.Windows.Visibility.Collapsed;
|
||||||
|
LoadRaw(_currentPath, result.Message, result.RawContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,18 +56,8 @@ public class Plugin : IViewer
|
|||||||
|
|
||||||
context.IsBusy = true;
|
context.IsBusy = true;
|
||||||
|
|
||||||
var result = CertUtils.TryLoadCertificate(path);
|
|
||||||
|
|
||||||
_control = new CertViewerControl();
|
_control = new CertViewerControl();
|
||||||
|
_control.LoadFromPath(path);
|
||||||
if (result.Success && result.Certificate != null)
|
|
||||||
{
|
|
||||||
_control.LoadCertificate(result.Certificate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_control.LoadRaw(path, result.Message, result.RawContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.ViewerContent = _control;
|
context.ViewerContent = _control;
|
||||||
context.Title = Path.GetFileName(path);
|
context.Title = Path.GetFileName(path);
|
||||||
|
|||||||
Reference in New Issue
Block a user