mirror of
https://github.com/QL-Win/QuickLook.git
synced 2026-01-12 07:04:16 +08:00
Add certificate viewer plugin
Introduces QuickLook.Plugin.CertViewer for viewing certificate files (.pfx, .cer, .pem, etc.) in QuickLook. The plugin loads and displays certificate details or raw content, and is integrated into the solution and project files.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace QuickLook.Plugin.CertViewer;
|
||||
|
||||
internal sealed class CertLoadResult
|
||||
{
|
||||
public bool Success { get; }
|
||||
public X509Certificate2 Certificate { get; }
|
||||
public string Message { get; }
|
||||
public string RawContent { get; }
|
||||
|
||||
public CertLoadResult(bool success, X509Certificate2 certificate, string message, string rawContent)
|
||||
{
|
||||
Success = success;
|
||||
Certificate = certificate;
|
||||
Message = message;
|
||||
RawContent = rawContent;
|
||||
}
|
||||
|
||||
public static CertLoadResult From(bool success, X509Certificate2 certificate, string message, string rawContent)
|
||||
=> new CertLoadResult(success, certificate, message, rawContent);
|
||||
}
|
||||
111
QuickLook.Plugin/QuickLook.Plugin.CertViewer/CertUtils.cs
Normal file
111
QuickLook.Plugin/QuickLook.Plugin.CertViewer/CertUtils.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace QuickLook.Plugin.CertViewer;
|
||||
|
||||
internal static class CertUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// TryLoadCertificate returns a <see cref="CertLoadResult"/> containing:
|
||||
/// - Success: whether loading/parsing succeeded
|
||||
/// - Certificate: the parsed X509Certificate2 (may be null)
|
||||
/// - Message: an informational or error message
|
||||
/// - RawContent: original file text or hex when parsing failed
|
||||
/// </summary>
|
||||
public static CertLoadResult TryLoadCertificate(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
if (ext == ".pfx" || ext == ".p12")
|
||||
{
|
||||
try
|
||||
{
|
||||
var cert = new X509Certificate2(path);
|
||||
return new CertLoadResult(true, cert, string.Empty, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CertLoadResult(false, null, "Failed to load PFX/P12: " + ex.Message, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Try DER/PEM style cert (.cer/.crt/.pem)
|
||||
var text = File.ReadAllText(path);
|
||||
|
||||
const string begin = "-----BEGIN CERTIFICATE-----";
|
||||
const string end = "-----END CERTIFICATE-----";
|
||||
|
||||
if (text.Contains(begin))
|
||||
{
|
||||
var startIdx = text.IndexOf(begin, StringComparison.Ordinal);
|
||||
var endIdx = text.IndexOf(end, StringComparison.Ordinal);
|
||||
if (startIdx >= 0 && endIdx > startIdx)
|
||||
{
|
||||
var b64 = text.Substring(startIdx + begin.Length, endIdx - (startIdx + begin.Length));
|
||||
b64 = new string(b64.Where(c => !char.IsWhiteSpace(c)).ToArray());
|
||||
try
|
||||
{
|
||||
var raw = Convert.FromBase64String(b64);
|
||||
var cert = new X509Certificate2(raw);
|
||||
return new CertLoadResult(true, cert, string.Empty, text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CertLoadResult(false, null, "PEM decode failed: " + ex.Message, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try raw DER
|
||||
try
|
||||
{
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
// heuristics: if starts with 0x30 (ASN.1 SEQUENCE) it's likely DER encoded
|
||||
if (bytes.Length > 0 && bytes[0] == 0x30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cert = new X509Certificate2(bytes);
|
||||
return new CertLoadResult(true, cert, string.Empty, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// not a certificate DER
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// Unsupported or not parseable: return raw text or hex
|
||||
try
|
||||
{
|
||||
var rawText = File.ReadAllText(path);
|
||||
return new CertLoadResult(false, null, "Could not parse as certificate; showing raw content.", rawText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fallback to hex
|
||||
try
|
||||
{
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
var hex = BitConverter.ToString(bytes).Replace("-", " ");
|
||||
return new CertLoadResult(false, null, "Could not parse as certificate; showing hex.", hex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CertLoadResult(false, null, "Failed to read file: " + ex.Message, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CertLoadResult(false, null, "Internal error: " + ex.Message, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<UserControl x:Class="QuickLook.Plugin.CertViewer.CertViewerControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="600"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ListView x:Name="PropertyList"
|
||||
Grid.Column="0"
|
||||
Margin="8">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Width="150"
|
||||
DisplayMemberBinding="{Binding Key}"
|
||||
Header="Field" />
|
||||
<GridViewColumn Width="300"
|
||||
DisplayMemberBinding="{Binding Value}"
|
||||
Header="Value" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
|
||||
<TextBox x:Name="RawText"
|
||||
Grid.Column="1"
|
||||
Margin="8"
|
||||
AcceptsReturn="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace QuickLook.Plugin.CertViewer;
|
||||
|
||||
public partial class CertViewerControl : UserControl, IDisposable
|
||||
{
|
||||
public CertViewerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void LoadCertificate(X509Certificate2 cert)
|
||||
{
|
||||
var items = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new("Subject", cert.Subject),
|
||||
new("Issuer", cert.Issuer),
|
||||
new("Thumbprint", cert.Thumbprint),
|
||||
new("SerialNumber", cert.SerialNumber),
|
||||
new("NotBefore", cert.NotBefore.ToString()),
|
||||
new("NotAfter", cert.NotAfter.ToString()),
|
||||
new("SignatureAlgorithm", cert.SignatureAlgorithm.FriendlyName ?? cert.SignatureAlgorithm.Value),
|
||||
new("PublicKey", cert.PublicKey.Oid.FriendlyName ?? cert.PublicKey.Oid.Value),
|
||||
};
|
||||
|
||||
PropertyList.ItemsSource = items;
|
||||
RawText.Text = string.Empty;
|
||||
}
|
||||
|
||||
public void LoadRaw(string path, string message, string content)
|
||||
{
|
||||
PropertyList.ItemsSource = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new("Path", path),
|
||||
new("Info", message)
|
||||
};
|
||||
|
||||
RawText.Text = content ?? "(No content to display)";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
82
QuickLook.Plugin/QuickLook.Plugin.CertViewer/Plugin.cs
Normal file
82
QuickLook.Plugin/QuickLook.Plugin.CertViewer/Plugin.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using QuickLook.Common.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace QuickLook.Plugin.CertViewer;
|
||||
|
||||
public class Plugin : IViewer
|
||||
{
|
||||
private static readonly HashSet<string> WellKnownExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".p12",
|
||||
".pfx",
|
||||
".cer",
|
||||
".crt",
|
||||
".pem",
|
||||
".snk",
|
||||
".pvk",
|
||||
".spc",
|
||||
".mobileprovision",
|
||||
".certSigningRequest",
|
||||
".csr",
|
||||
".keystore",
|
||||
};
|
||||
|
||||
private CertViewerControl _control;
|
||||
private string _currentPath;
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public bool CanHandle(string path)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
return false;
|
||||
|
||||
var ext = Path.GetExtension(path);
|
||||
if (!string.IsNullOrEmpty(ext) && WellKnownExtensions.Contains(ext))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Prepare(string path, ContextObject context)
|
||||
{
|
||||
context.PreferredSize = new Size { Width = 800, Height = 600 };
|
||||
}
|
||||
|
||||
public void View(string path, ContextObject context)
|
||||
{
|
||||
_currentPath = path;
|
||||
|
||||
context.IsBusy = true;
|
||||
|
||||
var result = CertUtils.TryLoadCertificate(path);
|
||||
|
||||
_control = new CertViewerControl();
|
||||
|
||||
if (result.Success && result.Certificate != null)
|
||||
{
|
||||
_control.LoadCertificate(result.Certificate);
|
||||
}
|
||||
else
|
||||
{
|
||||
_control.LoadRaw(path, result.Message, result.RawContent);
|
||||
}
|
||||
|
||||
context.ViewerContent = _control;
|
||||
context.Title = Path.GetFileName(path);
|
||||
context.IsBusy = false;
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_control?.Dispose();
|
||||
_control = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<RootNamespace>QuickLook.Plugin.CertViewer</RootNamespace>
|
||||
<AssemblyName>QuickLook.Plugin.CertViewer</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<UseWPF>true</UseWPF>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<ProjectGuid>{C8B9F6D1-1234-4AAB-9C3D-ABCDEF123456}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.CertViewer\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\Build\Release\QuickLook.Plugin\QuickLook.Plugin.CertViewer\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.CertViewer\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\Build\Release\QuickLook.Plugin\QuickLook.Plugin.CertViewer\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\QuickLook.Common\QuickLook.Common.csproj">
|
||||
<Project>{85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}</Project>
|
||||
<Name>QuickLook.Common</Name>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\GitVersion.cs">
|
||||
<Link>Properties\GitVersion.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34728.123
|
||||
@@ -21,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.PdfViewer"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.TextViewer", "QuickLook.Plugin\QuickLook.Plugin.TextViewer\QuickLook.Plugin.TextViewer.csproj", "{AE041682-E3A1-44F6-8BB4-916A98D89FBE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.CertViewer", "QuickLook.Plugin\QuickLook.Plugin.CertViewer\QuickLook.Plugin.CertViewer.csproj", "{C8B9F6D1-1234-4AAB-9C3D-ABCDEF123456}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.VideoViewer", "QuickLook.Plugin\QuickLook.Plugin.VideoViewer\QuickLook.Plugin.VideoViewer.csproj", "{1B746D92-49A5-4A37-9D75-DCC490393290}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BAE81497-98FA-4A7A-A0FB-2B86C9694B9C}"
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<Project Path="QuickLook.Plugin/QuickLook.Plugin.PEViewer/QuickLook.Plugin.PEViewer.csproj" />
|
||||
<Project Path="QuickLook.Plugin/QuickLook.Plugin.PluginInstaller/QuickLook.Plugin.PluginInstaller.csproj" />
|
||||
<Project Path="QuickLook.Plugin/QuickLook.Plugin.TextViewer/QuickLook.Plugin.TextViewer.csproj" />
|
||||
<Project Path="QuickLook.Plugin/QuickLook.Plugin.CertViewer/QuickLook.Plugin.CertViewer.csproj" />
|
||||
<Project Path="QuickLook.Plugin/QuickLook.Plugin.ThumbnailViewer/QuickLook.Plugin.ThumbnailViewer.csproj" />
|
||||
<Project Path="QuickLook.Plugin/QuickLook.Plugin.VideoViewer/QuickLook.Plugin.VideoViewer.csproj" />
|
||||
</Folder>
|
||||
@@ -57,6 +58,7 @@
|
||||
<BuildDependency Project="QuickLook.Plugin/QuickLook.Plugin.PEViewer/QuickLook.Plugin.PEViewer.csproj" />
|
||||
<BuildDependency Project="QuickLook.Plugin/QuickLook.Plugin.PluginInstaller/QuickLook.Plugin.PluginInstaller.csproj" />
|
||||
<BuildDependency Project="QuickLook.Plugin/QuickLook.Plugin.TextViewer/QuickLook.Plugin.TextViewer.csproj" />
|
||||
<BuildDependency Project="QuickLook.Plugin/QuickLook.Plugin.CertViewer/QuickLook.Plugin.CertViewer.csproj" />
|
||||
<BuildDependency Project="QuickLook.Plugin/QuickLook.Plugin.ThumbnailViewer/QuickLook.Plugin.ThumbnailViewer.csproj" />
|
||||
<BuildDependency Project="QuickLook.Plugin/QuickLook.Plugin.VideoViewer/QuickLook.Plugin.VideoViewer.csproj" />
|
||||
<BuildDependency Project="QuickLook/QuickLook.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user