diff --git a/BuildOutput/bin/Debug/net9.0/certmgr.dll b/BuildOutput/bin/Debug/net9.0/certmgr.dll index a5fcb21..1e2cbdd 100644 Binary files a/BuildOutput/bin/Debug/net9.0/certmgr.dll and b/BuildOutput/bin/Debug/net9.0/certmgr.dll differ diff --git a/BuildOutput/bin/Debug/net9.0/certmgr.exe b/BuildOutput/bin/Debug/net9.0/certmgr.exe index 9a5164c..fcbc90a 100644 Binary files a/BuildOutput/bin/Debug/net9.0/certmgr.exe and b/BuildOutput/bin/Debug/net9.0/certmgr.exe differ diff --git a/BuildOutput/bin/Debug/net9.0/certmgr.pdb b/BuildOutput/bin/Debug/net9.0/certmgr.pdb index fdd65cb..f20692e 100644 Binary files a/BuildOutput/bin/Debug/net9.0/certmgr.pdb and b/BuildOutput/bin/Debug/net9.0/certmgr.pdb differ diff --git a/certmgr/CertGen/CertGenException.cs b/certmgr/Certificates/CertGen/CertGenException.cs similarity index 89% rename from certmgr/CertGen/CertGenException.cs rename to certmgr/Certificates/CertGen/CertGenException.cs index a3042de..07d4690 100644 --- a/certmgr/CertGen/CertGenException.cs +++ b/certmgr/Certificates/CertGen/CertGenException.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public class CertGenException : Exception { diff --git a/certmgr/CertGen/CertificateAlgorithm.cs b/certmgr/Certificates/CertGen/CertificateAlgorithm.cs similarity index 58% rename from certmgr/CertGen/CertificateAlgorithm.cs rename to certmgr/Certificates/CertGen/CertificateAlgorithm.cs index 88b611a..0ec5043 100644 --- a/certmgr/CertGen/CertificateAlgorithm.cs +++ b/certmgr/Certificates/CertGen/CertificateAlgorithm.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public enum CertificateAlgorithm { diff --git a/certmgr/CertGen/CertificateGeneratorBase.cs b/certmgr/Certificates/CertGen/CertificateGeneratorBase.cs similarity index 99% rename from certmgr/CertGen/CertificateGeneratorBase.cs rename to certmgr/Certificates/CertGen/CertificateGeneratorBase.cs index ea3ee47..e5c2945 100644 --- a/certmgr/CertGen/CertificateGeneratorBase.cs +++ b/certmgr/Certificates/CertGen/CertificateGeneratorBase.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates; using CertMgr.Core.Exceptions; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; internal abstract class CertificateGeneratorBase : ICertificateGenerator where TAlgorithm : AsymmetricAlgorithm diff --git a/certmgr/CertGen/CertificateManager.cs b/certmgr/Certificates/CertGen/CertificateManager.cs similarity index 95% rename from certmgr/CertGen/CertificateManager.cs rename to certmgr/Certificates/CertGen/CertificateManager.cs index a2343b3..6c3fcfc 100644 --- a/certmgr/CertGen/CertificateManager.cs +++ b/certmgr/Certificates/CertGen/CertificateManager.cs @@ -2,7 +2,7 @@ using CertMgr.Core.Exceptions; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public sealed class CertificateManager { diff --git a/certmgr/CertGen/CertificateSettings.cs b/certmgr/Certificates/CertGen/CertificateSettings.cs similarity index 98% rename from certmgr/CertGen/CertificateSettings.cs rename to certmgr/Certificates/CertGen/CertificateSettings.cs index 0037189..e4d4003 100644 --- a/certmgr/CertGen/CertificateSettings.cs +++ b/certmgr/Certificates/CertGen/CertificateSettings.cs @@ -4,7 +4,7 @@ using System.Security.Cryptography.X509Certificates; using CertMgr.Core.Utils; using CertMgr.Core.Validation; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public sealed class CertificateSettings { diff --git a/certmgr/CertGen/EcdsaCertificateGenerator.cs b/certmgr/Certificates/CertGen/EcdsaCertificateGenerator.cs similarity index 97% rename from certmgr/CertGen/EcdsaCertificateGenerator.cs rename to certmgr/Certificates/CertGen/EcdsaCertificateGenerator.cs index 92aaaed..22d0b13 100644 --- a/certmgr/CertGen/EcdsaCertificateGenerator.cs +++ b/certmgr/Certificates/CertGen/EcdsaCertificateGenerator.cs @@ -3,7 +3,7 @@ using System.Security.Cryptography.X509Certificates; using CertMgr.Core.Exceptions; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; internal sealed class EcdsaCertificateGenerator : CertificateGeneratorBase { diff --git a/certmgr/CertGen/EcdsaCurve.cs b/certmgr/Certificates/CertGen/EcdsaCurve.cs similarity index 58% rename from certmgr/CertGen/EcdsaCurve.cs rename to certmgr/Certificates/CertGen/EcdsaCurve.cs index b4baf94..1ecb2f2 100644 --- a/certmgr/CertGen/EcdsaCurve.cs +++ b/certmgr/Certificates/CertGen/EcdsaCurve.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public enum EcdsaCurve { diff --git a/certmgr/CertGen/EcdsaGeneratorSettings.cs b/certmgr/Certificates/CertGen/EcdsaGeneratorSettings.cs similarity index 96% rename from certmgr/CertGen/EcdsaGeneratorSettings.cs rename to certmgr/Certificates/CertGen/EcdsaGeneratorSettings.cs index d56e0f3..404d34b 100644 --- a/certmgr/CertGen/EcdsaGeneratorSettings.cs +++ b/certmgr/Certificates/CertGen/EcdsaGeneratorSettings.cs @@ -2,7 +2,7 @@ using CertMgr.Core.Exceptions; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public sealed class EcdsaGeneratorSettings : GeneratorSettings { diff --git a/certmgr/CertGen/GeneratorSettings.cs b/certmgr/Certificates/CertGen/GeneratorSettings.cs similarity index 92% rename from certmgr/CertGen/GeneratorSettings.cs rename to certmgr/Certificates/CertGen/GeneratorSettings.cs index d56b111..bf507a2 100644 --- a/certmgr/CertGen/GeneratorSettings.cs +++ b/certmgr/Certificates/CertGen/GeneratorSettings.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public abstract class GeneratorSettings { diff --git a/certmgr/CertGen/GeneratorType.cs b/certmgr/Certificates/CertGen/GeneratorType.cs similarity index 55% rename from certmgr/CertGen/GeneratorType.cs rename to certmgr/Certificates/CertGen/GeneratorType.cs index 71f1f8a..a1c03c4 100644 --- a/certmgr/CertGen/GeneratorType.cs +++ b/certmgr/Certificates/CertGen/GeneratorType.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public enum GeneratorType { diff --git a/certmgr/CertGen/HashAlgorithm.cs b/certmgr/Certificates/CertGen/HashAlgorithm.cs similarity index 61% rename from certmgr/CertGen/HashAlgorithm.cs rename to certmgr/Certificates/CertGen/HashAlgorithm.cs index 81f4407..32ef9b0 100644 --- a/certmgr/CertGen/HashAlgorithm.cs +++ b/certmgr/Certificates/CertGen/HashAlgorithm.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public enum HashAlgorithm { diff --git a/certmgr/CertGen/ICertificateGenerator.cs b/certmgr/Certificates/CertGen/ICertificateGenerator.cs similarity index 85% rename from certmgr/CertGen/ICertificateGenerator.cs rename to certmgr/Certificates/CertGen/ICertificateGenerator.cs index 2ebbd96..50bbd69 100644 --- a/certmgr/CertGen/ICertificateGenerator.cs +++ b/certmgr/Certificates/CertGen/ICertificateGenerator.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography.X509Certificates; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public interface ICertificateGenerator : IAsyncDisposable { diff --git a/certmgr/CertGen/KeyUsage.cs b/certmgr/Certificates/CertGen/KeyUsage.cs similarity index 97% rename from certmgr/CertGen/KeyUsage.cs rename to certmgr/Certificates/CertGen/KeyUsage.cs index 9d946be..de316c2 100644 --- a/certmgr/CertGen/KeyUsage.cs +++ b/certmgr/Certificates/CertGen/KeyUsage.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography.X509Certificates; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; [Flags] public enum KeyUsage diff --git a/certmgr/CertGen/RsaCertificateGenerator.cs b/certmgr/Certificates/CertGen/RsaCertificateGenerator.cs similarity index 97% rename from certmgr/CertGen/RsaCertificateGenerator.cs rename to certmgr/Certificates/CertGen/RsaCertificateGenerator.cs index 86938f3..4286f6e 100644 --- a/certmgr/CertGen/RsaCertificateGenerator.cs +++ b/certmgr/Certificates/CertGen/RsaCertificateGenerator.cs @@ -3,7 +3,7 @@ using System.Security.Cryptography.X509Certificates; using CertMgr.Core.Exceptions; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; internal sealed class RsaCertificateGenerator : CertificateGeneratorBase { diff --git a/certmgr/CertGen/RsaGeneratorSettings.cs b/certmgr/Certificates/CertGen/RsaGeneratorSettings.cs similarity index 96% rename from certmgr/CertGen/RsaGeneratorSettings.cs rename to certmgr/Certificates/CertGen/RsaGeneratorSettings.cs index dc0a3c3..87762bb 100644 --- a/certmgr/CertGen/RsaGeneratorSettings.cs +++ b/certmgr/Certificates/CertGen/RsaGeneratorSettings.cs @@ -2,7 +2,7 @@ using CertMgr.Core.Exceptions; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public sealed class RsaGeneratorSettings : GeneratorSettings { diff --git a/certmgr/CertGen/RsaKeySize.cs b/certmgr/Certificates/CertGen/RsaKeySize.cs similarity index 65% rename from certmgr/CertGen/RsaKeySize.cs rename to certmgr/Certificates/CertGen/RsaKeySize.cs index 87e6bad..e8e65f5 100644 --- a/certmgr/CertGen/RsaKeySize.cs +++ b/certmgr/Certificates/CertGen/RsaKeySize.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public enum RsaKeySize { diff --git a/certmgr/CertGen/SANKind.cs b/certmgr/Certificates/CertGen/SANKind.cs similarity index 51% rename from certmgr/CertGen/SANKind.cs rename to certmgr/Certificates/CertGen/SANKind.cs index 28efb1a..5409b01 100644 --- a/certmgr/CertGen/SANKind.cs +++ b/certmgr/Certificates/CertGen/SANKind.cs @@ -1,4 +1,4 @@ -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public enum SANKind { diff --git a/certmgr/CertGen/SubjectAlternateName.cs b/certmgr/Certificates/CertGen/SubjectAlternateName.cs similarity index 96% rename from certmgr/CertGen/SubjectAlternateName.cs rename to certmgr/Certificates/CertGen/SubjectAlternateName.cs index 93c0d63..d185d7f 100644 --- a/certmgr/CertGen/SubjectAlternateName.cs +++ b/certmgr/Certificates/CertGen/SubjectAlternateName.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public sealed class SubjectAlternateName : IEquatable { diff --git a/certmgr/CertGen/SubjectAlternateNames.cs b/certmgr/Certificates/CertGen/SubjectAlternateNames.cs similarity index 98% rename from certmgr/CertGen/SubjectAlternateNames.cs rename to certmgr/Certificates/CertGen/SubjectAlternateNames.cs index 95e2a22..440705b 100644 --- a/certmgr/CertGen/SubjectAlternateNames.cs +++ b/certmgr/Certificates/CertGen/SubjectAlternateNames.cs @@ -3,7 +3,7 @@ using CertMgr.Core.Log; using CertMgr.Core.Utils; -namespace CertMgr.CertGen; +namespace CertMgr.Certificates.CertGen; public sealed class SubjectAlternateNames : IReadOnlyCollection { diff --git a/certmgr/CertGen/Utils/CollectionEquivalencyComparer.cs b/certmgr/Certificates/CertGen/Utils/CollectionEquivalencyComparer.cs similarity index 92% rename from certmgr/CertGen/Utils/CollectionEquivalencyComparer.cs rename to certmgr/Certificates/CertGen/Utils/CollectionEquivalencyComparer.cs index 1dee062..539e2b7 100644 --- a/certmgr/CertGen/Utils/CollectionEquivalencyComparer.cs +++ b/certmgr/Certificates/CertGen/Utils/CollectionEquivalencyComparer.cs @@ -1,6 +1,6 @@ using CertMgr.Core.Utils; -namespace CertMgr.CertGen.Utils; +namespace CertMgr.Certificates.CertGen.Utils; public sealed class CollectionEquivalencyComparer : IEqualityComparer> where T : notnull { diff --git a/certmgr/CertGen/Utils/SubjectValidator.cs b/certmgr/Certificates/CertGen/Utils/SubjectValidator.cs similarity index 99% rename from certmgr/CertGen/Utils/SubjectValidator.cs rename to certmgr/Certificates/CertGen/Utils/SubjectValidator.cs index ad35d20..1fa1e04 100644 --- a/certmgr/CertGen/Utils/SubjectValidator.cs +++ b/certmgr/Certificates/CertGen/Utils/SubjectValidator.cs @@ -4,7 +4,7 @@ using System.Security.Cryptography.X509Certificates; using CertMgr.Core.Validation; -namespace CertMgr.CertGen.Utils; +namespace CertMgr.Certificates.CertGen.Utils; public sealed class SubjectValidator : IValueValidator { diff --git a/certmgr/Certificates/CertStoreSearcher.cs b/certmgr/Certificates/CertStoreSearcher.cs new file mode 100644 index 0000000..5778ffc --- /dev/null +++ b/certmgr/Certificates/CertStoreSearcher.cs @@ -0,0 +1,42 @@ +using System.Diagnostics; +using System.Security.Cryptography.X509Certificates; + +using CertMgr.Core.Filters; +using CertMgr.Jobs; + +namespace CertMgr.Certificates; + +public sealed class CertStoreSearcher +{ + public CertStoreSearcher(CertStore store) + { + Store = store; + } + + private CertStore Store { [DebuggerStepThrough] get; } + + public async Task> SearchAsync(IFilter filter, CancellationToken cancellationToken) + { + List certificates = new List(); + + using (X509Store store = new X509Store((StoreName)Store.Name, (StoreLocation)Store.Location)) + { + store.Open(OpenFlags.ReadOnly); + + foreach (X509Certificate2 cert in store.Certificates) + { + if (await filter.IsMatchAsync(cert, cancellationToken)) + { + certificates.Add(cert); + } + } + } + + return certificates; + } + + public override string ToString() + { + return string.Format("store = {0}/{1}", Store.Location, Store.Name); + } +} diff --git a/certmgr/Certificates/Filters/ByThumbprintFilter.cs b/certmgr/Certificates/Filters/ByThumbprintFilter.cs new file mode 100644 index 0000000..6bacfdf --- /dev/null +++ b/certmgr/Certificates/Filters/ByThumbprintFilter.cs @@ -0,0 +1,21 @@ +using System.Diagnostics; +using System.Security.Cryptography.X509Certificates; + +namespace CertMgr.Certificates.Filters; + +internal sealed class ByThumbprintFilter : CertificateFilter +{ + internal ByThumbprintFilter(string thumbprint) + : base(CertificateFilterType.Thumbprint) + { + Thumbprint = thumbprint; + } + + private string Thumbprint { [DebuggerStepThrough] get; } + + protected override Task DoIsMatchAsync(X509Certificate2 value, CancellationToken cancellationToken) + { + bool result = string.Equals(value.Thumbprint, Thumbprint, StringComparison.OrdinalIgnoreCase); + return Task.FromResult(result); + } +} diff --git a/certmgr/Certificates/Filters/CertificateFilter.cs b/certmgr/Certificates/Filters/CertificateFilter.cs new file mode 100644 index 0000000..7cb7b9e --- /dev/null +++ b/certmgr/Certificates/Filters/CertificateFilter.cs @@ -0,0 +1,21 @@ +using System.Diagnostics; +using System.Security.Cryptography.X509Certificates; + +using CertMgr.Core.Filters; + +namespace CertMgr.Certificates.Filters; + +public abstract class CertificateFilter : Filter +{ + internal CertificateFilter(CertificateFilterType type) + { + Type = type; + } + + public CertificateFilterType Type { [DebuggerStepThrough] get; } + + public override string ToString() + { + return string.Format("type = {0}", Type); + } +} diff --git a/certmgr/Certificates/Filters/CertificateFilterType.cs b/certmgr/Certificates/Filters/CertificateFilterType.cs new file mode 100644 index 0000000..5eb5d9a --- /dev/null +++ b/certmgr/Certificates/Filters/CertificateFilterType.cs @@ -0,0 +1,6 @@ +namespace CertMgr.Certificates.Filters; + +public enum CertificateFilterType +{ + Thumbprint = 1 +} diff --git a/certmgr/Certificates/Filters/KnownFilters.cs b/certmgr/Certificates/Filters/KnownFilters.cs new file mode 100644 index 0000000..0e401fb --- /dev/null +++ b/certmgr/Certificates/Filters/KnownFilters.cs @@ -0,0 +1,16 @@ +using System.Security.Cryptography.X509Certificates; + +using CertMgr.Core.Filters; + +namespace CertMgr.Certificates.Filters; + +public static class KnownFilters +{ + public static class Certificate + { + public static IFilter ByThumbprint(string thumbprint) + { + return new ByThumbprintFilter(thumbprint); + } + } +} diff --git a/certmgr/Core/CertMgrException.cs b/certmgr/Core/CertMgrException.cs index d8c6d6e..4638c22 100644 --- a/certmgr/Core/CertMgrException.cs +++ b/certmgr/Core/CertMgrException.cs @@ -1,4 +1,4 @@ -using CertMgr.Core.Utils; +/*using CertMgr.Core.Utils; namespace CertMgr.Core; @@ -13,3 +13,4 @@ public class CertMgrException : Exception { } } +*/ \ No newline at end of file diff --git a/certmgr/Core/Converters/ConverterContext.cs b/certmgr/Core/Converters/ConverterContext.cs deleted file mode 100644 index 7c230b4..0000000 --- a/certmgr/Core/Converters/ConverterContext.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace CertMgr.Core.Converters; - -public class ConverterContext -{ - public static readonly ConverterContext Empty = new ConverterContext(); -} diff --git a/certmgr/Core/Converters/EnumConverterContext.cs b/certmgr/Core/Converters/EnumConverterContext.cs deleted file mode 100644 index eab1eb3..0000000 --- a/certmgr/Core/Converters/EnumConverterContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Diagnostics; - -namespace CertMgr.Core.Converters; - -public class EnumConverterContext : ConverterContext -{ - internal EnumConverterContext(Type targetType) - { - TargetType = targetType; - } - - public Type TargetType { [DebuggerStepThrough] get; } -} diff --git a/certmgr/Core/Converters/Impl/StorageKindConverter.cs b/certmgr/Core/Converters/Impl/StorageKindConverter.cs index 16b16ce..8b3e6e2 100644 --- a/certmgr/Core/Converters/Impl/StorageKindConverter.cs +++ b/certmgr/Core/Converters/Impl/StorageKindConverter.cs @@ -4,38 +4,20 @@ namespace CertMgr.Core.Converters.Impl; internal class StorageKindConverter : ValueConverter { - // private const char Separator = '|'; - protected override Task DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken) { ReadOnlySpan storageTypeSpan = rawValue.AsSpan(); - // int storageTypeSplitIndex = rawSpan.IndexOf(Separator); - // if (storageTypeSplitIndex == -1) - // { - // return Task.FromResult((IStorage?)EmptyStorage.Empty); - // } - IStorage? storage; - // ReadOnlySpan storageTypeSpan = rawSpan.Slice(0, storageTypeSplitIndex); - // ReadOnlySpan storageDefinition = rawSpan.Slice(storageTypeSplitIndex + 1); switch (storageTypeSpan) { case "file": storage = new FileStorage(); - // if (!TryGetFileStore(storageDefinition, out storage)) - // { - // storage = EmptyStorage.Empty; - // } break; case "certstore": case "cert-store": storage = new CertStoreStorage(); - //if (!TryGetCertStore(storageDefinition, out storage)) - //{ - // storage = EmptyStorage.Empty; - //} break; default: storage = EmptyStorage.Empty; diff --git a/certmgr/Core/Storage/Filter.cs b/certmgr/Core/Filters/Filter.cs similarity index 97% rename from certmgr/Core/Storage/Filter.cs rename to certmgr/Core/Filters/Filter.cs index d9156cd..f00ab82 100644 --- a/certmgr/Core/Storage/Filter.cs +++ b/certmgr/Core/Filters/Filter.cs @@ -2,7 +2,7 @@ using CertMgr.Core.Utils; -namespace CertMgr.Core.Storage; +namespace CertMgr.Core.Filters; public abstract class Filter : IFilter { diff --git a/certmgr/Core/Storage/IFilter.cs b/certmgr/Core/Filters/IFilter.cs similarity index 75% rename from certmgr/Core/Storage/IFilter.cs rename to certmgr/Core/Filters/IFilter.cs index d756dae..44c7e6f 100644 --- a/certmgr/Core/Storage/IFilter.cs +++ b/certmgr/Core/Filters/IFilter.cs @@ -1,4 +1,4 @@ -namespace CertMgr.Core.Storage; +namespace CertMgr.Core.Filters; public interface IFilter { diff --git a/certmgr/Core/JobExecutor.cs b/certmgr/Core/JobExecutor.cs index 4512452..145844e 100644 --- a/certmgr/Core/JobExecutor.cs +++ b/certmgr/Core/JobExecutor.cs @@ -45,6 +45,10 @@ internal sealed class JobExecutor errorLevel = result.ErrorLevel; CLog.Info("Executing job '{0}'... done (finished with error-level {1}, took {2})", job.Name, errorLevel, sw.Elapsed); + if (!string.IsNullOrEmpty(result.Message)) + { + CLog.Info(result.Message); + } } catch (Exception e) { diff --git a/certmgr/Core/Storage/CertStoreStorageContext.cs b/certmgr/Core/Storage/CertStoreStorageContext.cs index eb017e7..2450a4a 100644 --- a/certmgr/Core/Storage/CertStoreStorageContext.cs +++ b/certmgr/Core/Storage/CertStoreStorageContext.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Security.Cryptography.X509Certificates; +using CertMgr.Core.Filters; using CertMgr.Core.Utils; namespace CertMgr.Core.Storage; diff --git a/certmgr/Core/Storage/CertificateFilter.cs b/certmgr/Core/Storage/CertificateFilter.cs deleted file mode 100644 index 4d57c2f..0000000 --- a/certmgr/Core/Storage/CertificateFilter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Diagnostics; -using System.Security.Cryptography.X509Certificates; - -namespace CertMgr.Core.Storage; - -public static class CertificateFilter -{ - private abstract class CertFilter : Filter - { - } - - private sealed class ByThumbprintFilter : CertFilter - { - public ByThumbprintFilter(string thumbprint) - { - Thumbprint = thumbprint; - } - - public string Thumbprint { [DebuggerStepThrough] get; } - - protected override Task DoIsMatchAsync(X509Certificate2 value, CancellationToken cancellationToken) - { - bool result = string.Equals(value.Thumbprint, Thumbprint, StringComparison.OrdinalIgnoreCase); - return Task.FromResult(result); - } - } -} diff --git a/certmgr/Core/Storage/StorageType.cs b/certmgr/Core/Storage/StorageType.cs index 25e2487..e4138b6 100644 --- a/certmgr/Core/Storage/StorageType.cs +++ b/certmgr/Core/Storage/StorageType.cs @@ -1,6 +1,7 @@ -namespace CertMgr.Core.Storage; +/*namespace CertMgr.Core.Storage; public enum StorageType { File = 1 } +*/ \ No newline at end of file diff --git a/certmgr/Core/Utils/Extenders.cs b/certmgr/Core/Utils/Extenders.cs index ffac83b..558b962 100644 --- a/certmgr/Core/Utils/Extenders.cs +++ b/certmgr/Core/Utils/Extenders.cs @@ -25,7 +25,7 @@ internal static class Extenders { typeof(object), "object" }, }; - public static string ToSeparatedList(this IEnumerable items, Func formatter, string itemSeparator, string? lastItemSeparator = null) + public static string ToSeparatedList(this IEnumerable items, Func? formatter = null, string? itemSeparator = ",", string? lastItemSeparator = null) { using StringBuilderCache.ScopedBuilder lease = StringBuilderCache.AcquireScoped(); StringBuilder sb = lease.Builder; @@ -35,7 +35,7 @@ internal static class Extenders return sb.ToString(); } - public static void ToSeparatedList(this IEnumerable items, StringBuilder sb, Func formatter, string itemSeparator, string? lastItemSeparator = null) + public static void ToSeparatedList(this IEnumerable items, StringBuilder sb, Func? formatter, string? itemSeparator = null, string? lastItemSeparator = null) { if (!items.Any()) { @@ -245,4 +245,9 @@ internal static class Extenders return isOneOf; } + public static void AppendFormatLine(this StringBuilder sb, string format, params object[] args) + { + sb.AppendFormat(format, args); + sb.AppendLine(); + } } diff --git a/certmgr/Jobs/CertStore.cs b/certmgr/Jobs/CertStore.cs new file mode 100644 index 0000000..d88f9f7 --- /dev/null +++ b/certmgr/Jobs/CertStore.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; + +using CertMgr.Core.Storage; + +namespace CertMgr.Jobs; + +public sealed class CertStore +{ + public CertStore(CertStoreLocation location, CertStoreName name) + { + Location = location; + Name = name; + } + + public CertStoreLocation Location { [DebuggerStepThrough] get; } + + public CertStoreName Name { [DebuggerStepThrough] get; } + + public override string ToString() + { + return string.Format("{0}/{1}", Location, Name); + } +} diff --git a/certmgr/Jobs/CertStoreConverter.cs b/certmgr/Jobs/CertStoreConverter.cs new file mode 100644 index 0000000..b8c6c60 --- /dev/null +++ b/certmgr/Jobs/CertStoreConverter.cs @@ -0,0 +1,48 @@ +using CertMgr.Core.Converters; +using CertMgr.Core.Log; +using CertMgr.Core.Storage; +using CertMgr.Core.Utils; + +namespace CertMgr.Jobs; + +internal sealed class CertStoreConverter : ValueConverter +{ + protected override Task DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken) + { + CertStore? store = null; + + if (string.IsNullOrEmpty(rawValue)) + { + return Task.FromResult(store); + } + + ReadOnlySpan span = rawValue.AsSpan(); + int separatorIndex = span.IndexOf('/'); + if (separatorIndex > -1 && separatorIndex < span.Length - 1) + { + ReadOnlySpan locationSpan = span.Slice(0, separatorIndex); + if (Enum.TryParse(locationSpan, true, out CertStoreLocation location) && Enum.IsDefined(location)) + { + ReadOnlySpan nameSpan = span.Slice(separatorIndex + 1, span.Length - separatorIndex - 1); + if (Enum.TryParse(nameSpan, true, out CertStoreName name) && Enum.IsDefined(name)) + { + store = new CertStore(location, name); + } + else + { + CLog.Error("Failed to parse cert-store name from value '{0}'. Value '{1}' is not valid name (available values: {2})", rawValue, nameSpan.ToString(), Enum.GetNames().ToSeparatedList()); + } + } + else + { + CLog.Error("Failed to parse cert-store location from value '{0}'. Value '{1}' is not valid location (available values: {2})", rawValue, locationSpan.ToString(), Enum.GetNames().ToSeparatedList()); + } + } + else + { + CLog.Error("Cannot parse cert-store from value '{0}'. Value must be '/'", rawValue); + } + + return Task.FromResult(store); + } +} diff --git a/certmgr/Jobs/CertificateFilterConverter.cs b/certmgr/Jobs/CertificateFilterConverter.cs new file mode 100644 index 0000000..f64a0ef --- /dev/null +++ b/certmgr/Jobs/CertificateFilterConverter.cs @@ -0,0 +1,52 @@ +using System.Security.Cryptography.X509Certificates; + +using CertMgr.Certificates.Filters; +using CertMgr.Core.Converters; +using CertMgr.Core.Filters; +using CertMgr.Core.Log; +using CertMgr.Core.Utils; + +namespace CertMgr.Jobs; + +internal sealed class CertificateFilterConverter : ValueConverter> +{ + protected override Task?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken) + { + IFilter? filter = null; + + if (string.IsNullOrEmpty(rawValue)) + { + return Task.FromResult(filter); + } + + ReadOnlySpan span = rawValue.AsSpan(); + int separatorIndex = span.IndexOf(':'); + if (separatorIndex > -1 && separatorIndex < span.Length - 1) + { + ReadOnlySpan filterTypeSpan = span.Slice(0, separatorIndex); + if (Enum.TryParse(filterTypeSpan, true, out CertificateFilterType filterType) && Enum.IsDefined(filterType)) + { + ReadOnlySpan nameSpan = span.Slice(separatorIndex + 1, span.Length - separatorIndex - 1); + string value = nameSpan.ToString(); + switch (filterType) + { + case CertificateFilterType.Thumbprint: + filter = KnownFilters.Certificate.ByThumbprint(value); + break; + default: + break; + } + } + else + { + CLog.Error("Failed to parse filter type from value '{0}'. (available values: {1})", rawValue, Enum.GetNames().ToSeparatedList()); + } + } + else + { + CLog.Error("Cannot parse filter from value '{0}'. Value must be ':'", rawValue); + } + + return Task.FromResult(filter); + } +} diff --git a/certmgr/Jobs/CreateCertificateJob.cs b/certmgr/Jobs/CreateCertificateJob.cs index 967eeee..87e16b3 100644 --- a/certmgr/Jobs/CreateCertificateJob.cs +++ b/certmgr/Jobs/CreateCertificateJob.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography.X509Certificates; -using CertMgr.CertGen; +using CertMgr.Certificates.CertGen; using CertMgr.Core.Exceptions; using CertMgr.Core.Jobs; using CertMgr.Core.Log; @@ -9,17 +9,17 @@ using CertMgr.Core.Utils; namespace CertMgr.Jobs; -public sealed class CreateCertificateJob : Job +public sealed class CreateCertificateJob : Job { public const string ID = "create-certificate"; protected override async Task DoExecuteAsync(CancellationToken cancellationToken) { - CertificateSettings cs = Settings; + CreateCertificateSettings cs = Settings; CLog.Info("creating certificate using settings: subject = '{0}', algorithm = '{1}', curve = '{2}'", cs.Subject, cs.Algorithm?.ToString() ?? "", cs.Curve); GeneratorSettings gs = CreateGeneratorSettings(); - CertGen.CertificateSettings cgcs = await CreateCertificateSettingsAsync(cancellationToken).ConfigureAwait(false); + CertificateSettings cgcs = await CreateCertificateSettingsAsync(cancellationToken).ConfigureAwait(false); CertificateManager cm = new CertificateManager(); using (X509Certificate2 cert = await cm.CreateAsync(cgcs, gs, cancellationToken).ConfigureAwait(false)) @@ -65,11 +65,11 @@ public sealed class CreateCertificateJob : Job return gs; } - private async Task CreateCertificateSettingsAsync(CancellationToken cancellationToken) + private async Task CreateCertificateSettingsAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - CertGen.CertificateSettings cgcs = new CertGen.CertificateSettings(); + CertificateSettings cgcs = new CertificateSettings(); cgcs.SubjectName = Settings.Subject; cgcs.ValidityPeriod = Settings.ValidityPeriod.HasValue ? Settings.ValidityPeriod.Value : TimeSpan.FromDays(365); diff --git a/certmgr/Jobs/CertificateSettings.cs b/certmgr/Jobs/CreateCertificateSettings.cs similarity index 95% rename from certmgr/Jobs/CertificateSettings.cs rename to certmgr/Jobs/CreateCertificateSettings.cs index b54e217..2c8b05c 100644 --- a/certmgr/Jobs/CertificateSettings.cs +++ b/certmgr/Jobs/CreateCertificateSettings.cs @@ -1,7 +1,7 @@ using System.Diagnostics; -using CertMgr.CertGen; -using CertMgr.CertGen.Utils; +using CertMgr.Certificates.CertGen; +using CertMgr.Certificates.CertGen.Utils; using CertMgr.Core.Attributes; using CertMgr.Core.Converters.Impl; using CertMgr.Core.Jobs; @@ -11,13 +11,13 @@ using CertMgr.Core.Validation; namespace CertMgr.Jobs; -public sealed class CertificateSettings : JobSettings +public sealed class CreateCertificateSettings : JobSettings { - public CertificateSettings() + public CreateCertificateSettings() { Algorithm = CertificateAlgorithm.ECDsa; Curve = EcdsaCurve.P384; - HashAlgorithm = CertGen.HashAlgorithm.Sha384; + HashAlgorithm = Certificates.CertGen.HashAlgorithm.Sha384; ValidityPeriod = TimeSpan.FromDays(365); } @@ -35,7 +35,7 @@ public sealed class CertificateSettings : JobSettings [Setting("friendly-name")] public string? FriendlyName { [DebuggerStepThrough] get; [DebuggerStepThrough] set; } - [Setting("key-usage", Default = CertGen.KeyUsage.None, Converter = typeof(EnumConverter))] + [Setting("key-usage", Default = Certificates.CertGen.KeyUsage.None, Converter = typeof(EnumConverter))] public KeyUsage? KeyUsage { [DebuggerStepThrough] get; [DebuggerStepThrough] set; } [Setting("algorithm", Default = CertificateAlgorithm.ECDsa, Converter = typeof(EnumConverter))] diff --git a/certmgr/Jobs/GetCertificateInfoJob.cs b/certmgr/Jobs/GetCertificateInfoJob.cs new file mode 100644 index 0000000..5c7079c --- /dev/null +++ b/certmgr/Jobs/GetCertificateInfoJob.cs @@ -0,0 +1,107 @@ +using System.Security.Cryptography.X509Certificates; +using System.Text; + +using CertMgr.Certificates; +using CertMgr.Core.Jobs; +using CertMgr.Core.Log; +using CertMgr.Core.Utils; + +namespace CertMgr.Jobs; + +public sealed class GetCertificateInfoJob : Job +{ + public const string ID = "get-certificate-info"; + + protected override async Task DoExecuteAsync(CancellationToken cancellationToken) + { + X509Certificate2? cert = null; + + if (!string.IsNullOrEmpty(Settings.File)) + { + if (File.Exists(Settings.File)) + { + try + { + cert = X509CertificateLoader.LoadPkcs12FromFile(Settings.File, Settings.Password); + } + catch (Exception e) + { + throw new Exception(string.Format("Failed to load certificate from file '{0}'", Settings.File), e); + } + } + else + { + CLog.Error("File doesn't exist: '{0}'", Settings.File); + } + } + else if (Settings.Store != null) + { + if (Settings.Filter != null) + { + CertStoreSearcher searcher = new CertStoreSearcher(Settings.Store); + IReadOnlyList certs = await searcher.SearchAsync(Settings.Filter, cancellationToken).ConfigureAwait(false); + cert = certs.FirstOrDefault(); + } + else + { + CLog.Error("Filter must be specified when info on certificate from cert-store is requested"); + } + } + else + { + CLog.Error("Either file or cert-store must be specified when requesting info on certificate"); + } + + if (cert != null) + { + using (StringBuilderCache.ScopedBuilder lease = StringBuilderCache.AcquireScoped(256)) + { + 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)) + { + 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) + { + AppendWithPadding(sb, ext.Oid?.FriendlyName ?? ext.Oid?.Value, string.Empty, padSize); + foreach (string value in values) + { + if (string.IsNullOrEmpty(value)) + { + continue; + } + sb.AppendFormatLine(" - {0}", value); + } + } + else + { + AppendWithPadding(sb, ext.Oid?.FriendlyName ?? ext.Oid?.Value, values[0], padSize); + } + } + + return CreateSuccess(sb.ToString()); + } + } + else + { + return CreateFailure("No certificate"); + } + } + + private static void AppendWithPadding(StringBuilder sb, string name, string value, int padSize) + { + string padStr = padSize >= name.Length ? new string(' ', padSize - name.Length) : string.Empty; + sb.AppendFormatLine("{0}{1}: {2}", name, padStr, value); + } +} diff --git a/certmgr/Jobs/GetCertificateInfoSettings.cs b/certmgr/Jobs/GetCertificateInfoSettings.cs new file mode 100644 index 0000000..f79ccee --- /dev/null +++ b/certmgr/Jobs/GetCertificateInfoSettings.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; +using System.Security.Cryptography.X509Certificates; + +using CertMgr.Core.Attributes; +using CertMgr.Core.Filters; +using CertMgr.Core.Jobs; + +namespace CertMgr.Jobs; + +public sealed class GetCertificateInfoSettings : JobSettings +{ + [Setting("file")] + public string? File { [DebuggerStepThrough] get; [DebuggerStepThrough] set; } + + [Setting("cert-store", Converter = typeof(CertStoreConverter))] + public CertStore? Store { [DebuggerStepThrough] get; [DebuggerStepThrough] set; } + + [Setting("filter", Converter = typeof(CertificateFilterConverter))] + public IFilter? Filter { [DebuggerStepThrough] get; [DebuggerStepThrough] set; } + + [Setting("password", IsSecret = true)] + public string? Password { [DebuggerStepThrough] get; [DebuggerStepThrough] set; } +} diff --git a/certmgr/Jobs/RsaKeySizeConverter.cs b/certmgr/Jobs/RsaKeySizeConverter.cs index 0425d27..3831cf3 100644 --- a/certmgr/Jobs/RsaKeySizeConverter.cs +++ b/certmgr/Jobs/RsaKeySizeConverter.cs @@ -1,4 +1,4 @@ -using CertMgr.CertGen; +using CertMgr.Certificates.CertGen; using CertMgr.Core.Converters; namespace CertMgr.Jobs; diff --git a/certmgr/Program.cs b/certmgr/Program.cs index 483f59a..aac4ffe 100644 --- a/certmgr/Program.cs +++ b/certmgr/Program.cs @@ -6,30 +6,41 @@ internal static class Program { private static async Task Main(string[] args) { + // args = [ + // "--job=get-certificate-info", + // "--file=c:\\mycert-ecdsa.pfx", + // "--password=" + // ]; args = [ - "--job=create-certificate", - "--issuer-kind=file", - "--issuer-file=o|c:\\friend2.pfx", - "--issuer-password=aaa", - "--subject=CN=hello", - "--san=world", - "--san=DNS:zdrastvujte", - "--san=IP:192.168.131.1", - "--algorithm=ecdsa", - "--ecdsa-curve=p384", - "--validity-period=2d", + "--job=get-certificate-info", + "--cert-store=user/my", + "--filter=thumbprint:b395149b6079553eea92d9a73ffc97463e8976ff" + ]; - // "--certificate-target=file|w|c:\\mycert-ecdsa.pfx", - // "--certificate-password=aaa", - - // "--target-kind=file", - // "--target-file=w|c:\\mycert-ecdsa.pfx", - // "--target-password=aaa", - - "--target-kind=certstore", - "--target-certstore=machine|my|exportable", - "--target-password=aaa" - ]; + // args = [ + // "--job=create-certificate", + // "--issuer-kind=file", + // "--issuer-file=o|c:\\friend2.pfx", + // "--issuer-password=aaa", + // "--subject=CN=hello", + // "--san=world", + // "--san=DNS:zdrastvujte", + // "--san=IP:192.168.131.1", + // "--algorithm=ecdsa", + // "--ecdsa-curve=p384", + // "--validity-period=2d", + // + // // "--certificate-target=file|w|c:\\mycert-ecdsa.pfx", + // // "--certificate-password=aaa", + // + // // "--target-kind=file", + // // "--target-file=w|c:\\mycert-ecdsa.pfx", + // // "--target-password=aaa", + // + // "--target-kind=certstore", + // "--target-certstore=machine|my|exportable", + // "--target-password=aaa" + // ]; // --certificate-target=certstore|machine|my diff --git a/certmgrTest/SubjectValidatorTest.cs b/certmgrTest/SubjectValidatorTest.cs index 5e08ed2..b902e27 100644 --- a/certmgrTest/SubjectValidatorTest.cs +++ b/certmgrTest/SubjectValidatorTest.cs @@ -1,4 +1,4 @@ -using CertMgr.CertGen.Utils; +using CertMgr.Certificates.CertGen.Utils; using CertMgr.Core.Validation; using NUnit.Framework;