filtering (cert-store)
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
11
certmgr/Certificates/Filters/ByIssuerFilter.cs
Normal file
11
certmgr/Certificates/Filters/ByIssuerFilter.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using CertMgr.Core.Filters;
|
||||
|
||||
namespace CertMgr.Certificates.Filters;
|
||||
|
||||
internal sealed class ByIssuerFilter : CertificateFilter
|
||||
{
|
||||
public ByIssuerFilter(string pattern, FilteringFlags flags)
|
||||
: base(pattern, flags, cert => cert.Issuer)
|
||||
{
|
||||
}
|
||||
}
|
||||
11
certmgr/Certificates/Filters/BySubjectFilter.cs
Normal file
11
certmgr/Certificates/Filters/BySubjectFilter.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using CertMgr.Core.Filters;
|
||||
|
||||
namespace CertMgr.Certificates.Filters;
|
||||
|
||||
internal sealed class BySubjectFilter : CertificateFilter
|
||||
{
|
||||
public BySubjectFilter(string pattern, FilteringFlags flags)
|
||||
: base(pattern, flags, cert => cert.Subject)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,11 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using CertMgr.Core.Filters;
|
||||
|
||||
namespace CertMgr.Certificates.Filters;
|
||||
|
||||
internal sealed class ByThumbprintFilter : CertificateFilter
|
||||
{
|
||||
internal ByThumbprintFilter(string thumbprint)
|
||||
: base(CertificateFilterType.Thumbprint)
|
||||
public ByThumbprintFilter(string pattern, FilteringFlags flags)
|
||||
: base(pattern, flags, cert => cert.Thumbprint)
|
||||
{
|
||||
Thumbprint = thumbprint;
|
||||
}
|
||||
|
||||
private string Thumbprint { [DebuggerStepThrough] get; }
|
||||
|
||||
protected override Task<bool> DoIsMatchAsync(X509Certificate2 value, CancellationToken cancellationToken)
|
||||
{
|
||||
bool result = string.Equals(value.Thumbprint, Thumbprint, StringComparison.OrdinalIgnoreCase);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,51 @@ using CertMgr.Core.Filters;
|
||||
|
||||
namespace CertMgr.Certificates.Filters;
|
||||
|
||||
public abstract class CertificateFilter : Filter<X509Certificate2>
|
||||
internal abstract class CertificateFilter : Filter<X509Certificate2>
|
||||
{
|
||||
internal CertificateFilter(CertificateFilterType type)
|
||||
protected CertificateFilter(string pattern, FilteringFlags flags, Func<X509Certificate2, string> propertyGetter)
|
||||
{
|
||||
Type = type;
|
||||
Pattern = pattern;
|
||||
PropertyGetter = propertyGetter;
|
||||
|
||||
Worker = GetWorker(flags);
|
||||
}
|
||||
|
||||
public CertificateFilterType Type { [DebuggerStepThrough] get; }
|
||||
private string Pattern { [DebuggerStepThrough] get; }
|
||||
|
||||
public override string ToString()
|
||||
private Func<X509Certificate2, string> PropertyGetter { [DebuggerStepThrough] get; }
|
||||
|
||||
private IFilter<string> Worker { [DebuggerStepThrough] get; }
|
||||
|
||||
protected sealed override async Task<bool> DoIsMatchAsync(X509Certificate2 value, CancellationToken cancellationToken)
|
||||
{
|
||||
return string.Format("type = {0}", Type);
|
||||
string property = PropertyGetter(value);
|
||||
bool result = await Worker.IsMatchAsync(property, cancellationToken).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
private IFilter<string> GetWorker(FilteringFlags flags)
|
||||
{
|
||||
IFilter<string> worker;
|
||||
|
||||
if (flags.HasFlag(FilteringFlags.Exact))
|
||||
{
|
||||
worker = Core.Filters.KnownFilters.String.ExactMatch(Pattern, flags.HasFlag(FilteringFlags.CaseSensitive));
|
||||
}
|
||||
else if (flags.HasFlag(FilteringFlags.Wildcard))
|
||||
{
|
||||
worker = Core.Filters.KnownFilters.String.Wildcard(Pattern, flags.HasFlag(FilteringFlags.CaseSensitive));
|
||||
}
|
||||
else if (flags.HasFlag(FilteringFlags.Regex))
|
||||
{
|
||||
worker = Core.Filters.KnownFilters.String.Regex(Pattern, flags.HasFlag(FilteringFlags.CaseSensitive));
|
||||
}
|
||||
else
|
||||
{
|
||||
// default search is 'Exact'
|
||||
worker = Core.Filters.KnownFilters.String.ExactMatch(Pattern, flags.HasFlag(FilteringFlags.CaseSensitive));
|
||||
}
|
||||
|
||||
return worker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
|
||||
public enum CertificateFilterType
|
||||
{
|
||||
Thumbprint = 1
|
||||
Thumbprint = 1,
|
||||
Subject,
|
||||
Issuer
|
||||
}
|
||||
|
||||
@@ -8,9 +8,19 @@ public static class KnownFilters
|
||||
{
|
||||
public static class Certificate
|
||||
{
|
||||
public static IFilter<X509Certificate2> ByThumbprint(string thumbprint)
|
||||
public static IFilter<X509Certificate2> ByThumbprint(string thumbprint, FilteringFlags flags)
|
||||
{
|
||||
return new ByThumbprintFilter(thumbprint);
|
||||
return new ByThumbprintFilter(thumbprint, flags);
|
||||
}
|
||||
|
||||
public static IFilter<X509Certificate2> BySubject(string subjectName, FilteringFlags flags)
|
||||
{
|
||||
return new BySubjectFilter(subjectName, flags);
|
||||
}
|
||||
|
||||
public static IFilter<X509Certificate2> ByIssuer(string issuerName, FilteringFlags flags)
|
||||
{
|
||||
return new ByIssuerFilter(issuerName, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ public abstract class Filter<T> : IFilter<T>
|
||||
|
||||
protected abstract Task<bool> DoIsMatchAsync(T value, CancellationToken cancellationToken);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("type = {0}", GetType().ToString(false));
|
||||
}
|
||||
|
||||
private sealed class EmptyFilter : Filter<T>
|
||||
{
|
||||
internal EmptyFilter(bool result)
|
||||
|
||||
13
certmgr/Core/Filters/FilteringFlags.cs
Normal file
13
certmgr/Core/Filters/FilteringFlags.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace CertMgr.Core.Filters;
|
||||
|
||||
[Flags]
|
||||
public enum FilteringFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
CaseSensitive = 1 << 1,
|
||||
|
||||
Exact = 1 << 3,
|
||||
Wildcard = 1 << 4,
|
||||
Regex = 1 << 5,
|
||||
}
|
||||
29
certmgr/Core/Filters/Impl/ExactMatchStringFilter.cs
Normal file
29
certmgr/Core/Filters/Impl/ExactMatchStringFilter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Filters.Impl;
|
||||
|
||||
internal sealed class ExactMatchStringFilter : Filter<string>
|
||||
{
|
||||
internal ExactMatchStringFilter(string pattern, bool caseSensitive)
|
||||
{
|
||||
Pattern = pattern;
|
||||
CaseSensitive = caseSensitive;
|
||||
}
|
||||
|
||||
private string Pattern { [DebuggerStepThrough] get; }
|
||||
|
||||
private bool CaseSensitive { [DebuggerStepThrough] get; }
|
||||
|
||||
protected override Task<bool> DoIsMatchAsync(string value, CancellationToken cancellationToken)
|
||||
{
|
||||
bool ismatch = string.Equals(Pattern, value, CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
|
||||
return Task.FromResult(ismatch);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("type = {0}, pattern = '{1}', case-sensitive = {2}", GetType().ToString(false), Pattern, CaseSensitive ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
27
certmgr/Core/Filters/Impl/RegexStringFilter.cs
Normal file
27
certmgr/Core/Filters/Impl/RegexStringFilter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Filters.Impl;
|
||||
|
||||
internal sealed class RegexStringFilter : Filter<string>
|
||||
{
|
||||
internal RegexStringFilter(string regexPattern, bool caseSensitive)
|
||||
{
|
||||
Regex = new Regex(regexPattern, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private Regex Regex { [DebuggerStepThrough] get; }
|
||||
|
||||
protected override Task<bool> DoIsMatchAsync(string value, CancellationToken cancellationToken)
|
||||
{
|
||||
bool ismatch = Regex.IsMatch(value);
|
||||
return Task.FromResult(ismatch);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("type = {0}, pattern = '{1}', case-sensitive = {2}", GetType().ToString(false), Regex, Regex.Options.HasFlag(RegexOptions.IgnoreCase) ? "no" : "yes");
|
||||
}
|
||||
}
|
||||
21
certmgr/Core/Filters/Impl/WildcardStringFilter.cs
Normal file
21
certmgr/Core/Filters/Impl/WildcardStringFilter.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CertMgr.Core.Filters.Impl;
|
||||
|
||||
internal sealed class WildcardStringFilter : Filter<string>
|
||||
{
|
||||
internal WildcardStringFilter(string pattern, bool caseSensitive)
|
||||
{
|
||||
string regexPattern = "^" + Regex.Escape(pattern).Replace("\\?", ".").Replace("\\*", ".*") + "$";
|
||||
Regex = new Regex(regexPattern, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private Regex Regex { [DebuggerStepThrough] get; }
|
||||
|
||||
protected override Task<bool> DoIsMatchAsync(string value, CancellationToken cancellationToken)
|
||||
{
|
||||
bool ismatch = Regex.IsMatch(value);
|
||||
return Task.FromResult(ismatch);
|
||||
}
|
||||
}
|
||||
24
certmgr/Core/Filters/KnownFilters.cs
Normal file
24
certmgr/Core/Filters/KnownFilters.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using CertMgr.Core.Filters.Impl;
|
||||
|
||||
namespace CertMgr.Core.Filters;
|
||||
|
||||
public static class KnownFilters
|
||||
{
|
||||
public static class String
|
||||
{
|
||||
public static IFilter<string> ExactMatch(string pattern, bool caseSensitive)
|
||||
{
|
||||
return new ExactMatchStringFilter(pattern, caseSensitive);
|
||||
}
|
||||
|
||||
public static IFilter<string> Wildcard(string pattern, bool caseSensitive)
|
||||
{
|
||||
return new WildcardStringFilter(pattern, caseSensitive);
|
||||
}
|
||||
|
||||
public static IFilter<string> Regex(string pattern, bool caseSensitive)
|
||||
{
|
||||
return new RegexStringFilter(pattern, caseSensitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
certmgr/Core/Filters/StringFilterType.cs
Normal file
8
certmgr/Core/Filters/StringFilterType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CertMgr.Core.Filters;
|
||||
|
||||
public enum StringFilterType
|
||||
{
|
||||
ExactMatch = 1,
|
||||
Wildcard,
|
||||
Regex
|
||||
}
|
||||
@@ -26,12 +26,34 @@ internal sealed class CertificateFilterConverter : ValueConverter<IFilter<X509Ce
|
||||
ReadOnlySpan<char> filterTypeSpan = span.Slice(0, separatorIndex);
|
||||
if (Enum.TryParse(filterTypeSpan, true, out CertificateFilterType filterType) && Enum.IsDefined(filterType))
|
||||
{
|
||||
ReadOnlySpan<char> nameSpan = span.Slice(separatorIndex + 1, span.Length - separatorIndex - 1);
|
||||
string value = nameSpan.ToString();
|
||||
ReadOnlySpan<char> valueSpan = span.Slice(separatorIndex + 1, span.Length - separatorIndex - 1);
|
||||
separatorIndex = valueSpan.IndexOf(':');
|
||||
|
||||
string pattern;
|
||||
FilteringFlags flags;
|
||||
|
||||
if (separatorIndex == -1)
|
||||
{
|
||||
pattern = valueSpan.ToString();
|
||||
flags = FilteringFlags.Exact;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadOnlySpan<char> flagsSpan = valueSpan.Slice(0, separatorIndex);
|
||||
flags = GetFilteringFlags(flagsSpan);
|
||||
pattern = valueSpan.Slice(separatorIndex + 1).ToString();
|
||||
}
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case CertificateFilterType.Thumbprint:
|
||||
filter = KnownFilters.Certificate.ByThumbprint(value);
|
||||
filter = Certificates.Filters.KnownFilters.Certificate.ByThumbprint(pattern, flags);
|
||||
break;
|
||||
case CertificateFilterType.Subject:
|
||||
filter = Certificates.Filters.KnownFilters.Certificate.BySubject(pattern, flags);
|
||||
break;
|
||||
case CertificateFilterType.Issuer:
|
||||
filter = Certificates.Filters.KnownFilters.Certificate.ByIssuer(pattern, flags);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -39,7 +61,7 @@ internal sealed class CertificateFilterConverter : ValueConverter<IFilter<X509Ce
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Failed to parse filter type from value '{0}'. (available values: {1})", rawValue, Enum.GetNames<CertificateFilterType>().ToSeparatedList());
|
||||
CLog.Error("Failed to parse filter type from value '{0}'. (available values = {1})", rawValue, Enum.GetNames<CertificateFilterType>().ToSeparatedList());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -49,4 +71,21 @@ internal sealed class CertificateFilterConverter : ValueConverter<IFilter<X509Ce
|
||||
|
||||
return Task.FromResult(filter);
|
||||
}
|
||||
|
||||
private FilteringFlags GetFilteringFlags(ReadOnlySpan<char> flagsSpan)
|
||||
{
|
||||
FilteringFlags flags = FilteringFlags.None;
|
||||
|
||||
MemoryExtensions.SpanSplitEnumerator<char> enu = flagsSpan.Split(',');
|
||||
while (enu.MoveNext())
|
||||
{
|
||||
ReadOnlySpan<char> currentFlag = flagsSpan[enu.Current].Trim();
|
||||
if (Enum.TryParse(currentFlag, true, out FilteringFlags tmp) && Enum.IsDefined(tmp))
|
||||
{
|
||||
flags |= tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public sealed class GetCertificateInfoJob : Job<GetCertificateInfoSettings>
|
||||
|
||||
protected override async Task<JobResult> DoExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
X509Certificate2? cert = null;
|
||||
List<X509Certificate2> certs = new List<X509Certificate2>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Settings.File))
|
||||
{
|
||||
@@ -22,7 +22,8 @@ public sealed class GetCertificateInfoJob : Job<GetCertificateInfoSettings>
|
||||
{
|
||||
try
|
||||
{
|
||||
cert = X509CertificateLoader.LoadPkcs12FromFile(Settings.File, Settings.Password);
|
||||
X509Certificate2 cert = X509CertificateLoader.LoadPkcs12FromFile(Settings.File, Settings.Password);
|
||||
certs.Add(cert);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -39,8 +40,8 @@ public sealed class GetCertificateInfoJob : Job<GetCertificateInfoSettings>
|
||||
if (Settings.Filter != null)
|
||||
{
|
||||
CertStoreSearcher searcher = new CertStoreSearcher(Settings.Store);
|
||||
IReadOnlyList<X509Certificate2> certs = await searcher.SearchAsync(Settings.Filter, cancellationToken).ConfigureAwait(false);
|
||||
cert = certs.FirstOrDefault();
|
||||
IReadOnlyList<X509Certificate2> searchResult = await searcher.SearchAsync(Settings.Filter, cancellationToken).ConfigureAwait(false);
|
||||
certs.AddRange(searchResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -52,42 +53,48 @@ public sealed class GetCertificateInfoJob : Job<GetCertificateInfoSettings>
|
||||
CLog.Error("Either file or cert-store must be specified when requesting info on certificate");
|
||||
}
|
||||
|
||||
if (cert != null)
|
||||
if (certs.Count > 0)
|
||||
{
|
||||
using (StringBuilderCache.ScopedBuilder lease = StringBuilderCache.AcquireScoped(256))
|
||||
using (StringBuilderCache.ScopedBuilder lease = StringBuilderCache.AcquireScoped(512 * certs.Count))
|
||||
{
|
||||
int padSize = 24;
|
||||
StringBuilder sb = lease.Builder;
|
||||
sb.AppendLine();
|
||||
AppendWithPadding(sb, "Subject", cert.Subject.StartsWith("CN=") ? cert.Subject.Substring(3) : cert.Subject, padSize);
|
||||
AppendWithPadding(sb, "Issuer", cert.Issuer.StartsWith("CN=") ? cert.Issuer.Substring(3) : cert.Issuer, padSize);
|
||||
AppendWithPadding(sb, "Not Before", cert.NotBefore.ToString("s"), padSize);
|
||||
AppendWithPadding(sb, "Not After", cert.NotAfter.ToString("s"), padSize);
|
||||
AppendWithPadding(sb, "Signature Algorithm", cert.SignatureAlgorithm.FriendlyName, padSize);
|
||||
AppendWithPadding(sb, "Thumbprint", cert.Thumbprint, padSize);
|
||||
if (!string.IsNullOrEmpty(cert.FriendlyName))
|
||||
|
||||
foreach (X509Certificate2 cert in certs)
|
||||
{
|
||||
AppendWithPadding(sb, "FriendlyName", cert.FriendlyName, padSize);
|
||||
}
|
||||
foreach (X509Extension ext in cert.Extensions)
|
||||
{
|
||||
string[] values = ext.Format(true).TrimEnd(Environment.NewLine.ToCharArray()).Split(Environment.NewLine);
|
||||
if ((values.Length > 1 && !string.IsNullOrEmpty(values[values.Length - 1])) || values.Length > 2)
|
||||
int padSize = 24;
|
||||
sb.AppendLine();
|
||||
AppendWithPadding(sb, "Subject", cert.Subject.StartsWith("CN=") ? cert.Subject.Substring(3) : cert.Subject, padSize);
|
||||
AppendWithPadding(sb, "Issuer", cert.Issuer.StartsWith("CN=") ? cert.Issuer.Substring(3) : cert.Issuer, padSize);
|
||||
AppendWithPadding(sb, "Not Before", cert.NotBefore.ToString("s"), padSize);
|
||||
AppendWithPadding(sb, "Not After", cert.NotAfter.ToString("s"), padSize);
|
||||
AppendWithPadding(sb, "Signature Algorithm", cert.SignatureAlgorithm.FriendlyName, padSize);
|
||||
AppendWithPadding(sb, "Thumbprint", cert.Thumbprint, padSize);
|
||||
if (!string.IsNullOrEmpty(cert.FriendlyName))
|
||||
{
|
||||
AppendWithPadding(sb, ext.Oid?.FriendlyName ?? ext.Oid?.Value, string.Empty, padSize);
|
||||
foreach (string value in values)
|
||||
AppendWithPadding(sb, "FriendlyName", cert.FriendlyName, padSize);
|
||||
}
|
||||
foreach (X509Extension ext in cert.Extensions)
|
||||
{
|
||||
string[] values = ext.Format(true).TrimEnd(Environment.NewLine.ToCharArray()).Split(Environment.NewLine);
|
||||
if ((values.Length > 1 && !string.IsNullOrEmpty(values[values.Length - 1])) || values.Length > 2)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
AppendWithPadding(sb, ext.Oid?.FriendlyName ?? ext.Oid?.Value, string.Empty, padSize);
|
||||
foreach (string value in values)
|
||||
{
|
||||
continue;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sb.AppendFormatLine(" - {0}", value);
|
||||
}
|
||||
sb.AppendFormatLine(" - {0}", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendWithPadding(sb, ext.Oid?.FriendlyName ?? ext.Oid?.Value, values[0], padSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendWithPadding(sb, ext.Oid?.FriendlyName ?? ext.Oid?.Value, values[0], padSize);
|
||||
}
|
||||
|
||||
sb.Append("----------------------------------------------------------------");
|
||||
}
|
||||
|
||||
return CreateSuccess(sb.ToString());
|
||||
|
||||
@@ -14,7 +14,7 @@ internal static class Program
|
||||
args = [
|
||||
"--job=get-certificate-info",
|
||||
"--cert-store=user/my",
|
||||
"--filter=thumbprint:b395149b6079553eea92d9a73ffc97463e8976ff"
|
||||
"--filter=thumbprint:regex:b39.+"
|
||||
];
|
||||
|
||||
// args = [
|
||||
|
||||
Reference in New Issue
Block a user