Files
QuickLook/QuickLook.Plugin/QuickLook.Plugin.CertViewer/CertUtils.cs
ema 06694e0b16
Some checks failed
build / build (push) Has been cancelled
build / publish (push) Has been cancelled
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.
2025-12-23 14:41:59 +08:00

116 lines
4.3 KiB
C#

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, string password = null)
{
try
{
var ext = Path.GetExtension(path)?.ToLowerInvariant();
if (ext == ".pfx" || ext == ".p12")
{
try
{
var cert = !string.IsNullOrEmpty(password)
? new X509Certificate2(path, password)
: new X509Certificate2(path);
return new CertLoadResult(true, cert, string.Empty, null);
}
catch (Exception ex)
{
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);
}
}
// 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);
}
}
}