fixes
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -117,23 +117,11 @@ internal abstract class CertificateGeneratorBase<TAlgorithm, TSettings> : ICerti
|
||||
using (X509Certificate2 publicOnlyCert = request.Create(settings.Issuer.SubjectName, sgen, notBefore, notAfter, serial))
|
||||
{
|
||||
cert = JoinPrivateKey(publicOnlyCert, privateKey);
|
||||
// using (X509Certificate2 temp = JoinPrivateKey(publicOnlyCert, privateKey))
|
||||
// {
|
||||
// // Generated instance of the cert can't be added to cert-store (private key is missing) if requested by the caller.
|
||||
// // To avoid this recreate the cert
|
||||
// cert = Recreate(temp, settings);
|
||||
// }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cert = request.CreateSelfSigned(notBefore, notAfter);
|
||||
// using (X509Certificate2 temp = request.CreateSelfSigned(notBefore, notAfter))
|
||||
// {
|
||||
// // Generated instance of the cert can't be added to cert-store (private key is missing) if requested by the caller.
|
||||
// // To avoid this recreate the cert
|
||||
// cert = Recreate(temp, settings);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +158,7 @@ internal abstract class CertificateGeneratorBase<TAlgorithm, TSettings> : ICerti
|
||||
{
|
||||
try
|
||||
{
|
||||
AsymmetricAlgorithm pk = issuerCertificate.PrivateKey;
|
||||
AsymmetricAlgorithm? pk = issuerCertificate.PrivateKey;
|
||||
throw new CertGenException("Unsupported type of private-key: '{0}'", pk?.GetType().FullName ?? "<null>");
|
||||
}
|
||||
catch (CertGenException)
|
||||
@@ -201,22 +189,13 @@ internal abstract class CertificateGeneratorBase<TAlgorithm, TSettings> : ICerti
|
||||
return serial;
|
||||
}
|
||||
|
||||
private X509Certificate2 Recreate(X509Certificate2 source, CertificateSettings settings)
|
||||
{
|
||||
byte[] data = source.Export(X509ContentType.Pfx, string.Empty);
|
||||
X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet;
|
||||
if (!settings.ExportableKeys)
|
||||
{
|
||||
flags &= ~X509KeyStorageFlags.Exportable;
|
||||
}
|
||||
|
||||
X509Certificate2 target = X509CertificateLoader.LoadPkcs12(data, string.Empty);
|
||||
target.FriendlyName = settings.FriendlyName;
|
||||
return target;
|
||||
}
|
||||
|
||||
private string CreateCommonName(string? subjectName)
|
||||
{
|
||||
if (subjectName == null)
|
||||
{
|
||||
throw new CertGenException("Cannot create common-name for certificate as subject-name is null");
|
||||
}
|
||||
|
||||
string cn;
|
||||
|
||||
if (subjectName.StartsWith("CN=", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace CertMgr.CertGen.Utils;
|
||||
|
||||
public sealed class CollectionEquivalencyComparer<T> : IEqualityComparer<IEnumerable<T>>
|
||||
public sealed class CollectionEquivalencyComparer<T> : IEqualityComparer<IEnumerable<T>> where T : notnull
|
||||
{
|
||||
public bool Equals(IEnumerable<T>? x, IEnumerable<T>? y)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ public sealed class SettingAttribute : Attribute
|
||||
{
|
||||
Name = name;
|
||||
IsMandatory = false;
|
||||
AlternateNames = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public string Name { [DebuggerStepThrough] get; }
|
||||
|
||||
@@ -6,7 +6,7 @@ public sealed class CliParser
|
||||
{
|
||||
public Task<RawArguments> ParseAsync(string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
CLog.Info("parsing arguments...");
|
||||
CLog.Info("Parsing arguments...");
|
||||
|
||||
RawArguments rawArgs = new RawArguments(args.Length);
|
||||
|
||||
@@ -54,7 +54,7 @@ public sealed class CliParser
|
||||
}
|
||||
}
|
||||
|
||||
CLog.Info("parsing arguments... done (found {0} arguments)", rawArgs.Count);
|
||||
CLog.Info("Parsing arguments... done (found {0} arguments)", rawArgs.Count);
|
||||
|
||||
return Task.FromResult(rawArgs);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ public sealed class StringConverter : ValueConverter<string>
|
||||
{
|
||||
protected override Task<string?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(rawValue);
|
||||
return Task.FromResult((string?)rawValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ public sealed class TimeSpanConverter : ValueConverter<TimeSpan?>
|
||||
|
||||
TimeSpan? result;
|
||||
|
||||
if (int.TryParse(valueSpan, out int value))
|
||||
if (TimeSpan.TryParse(valueSpan, out TimeSpan tmp))
|
||||
{
|
||||
result = tmp;
|
||||
}
|
||||
else if (int.TryParse(valueSpan, out int value))
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using CertMgr.Core.Cli;
|
||||
@@ -24,10 +25,32 @@ internal sealed class JobExecutor
|
||||
|
||||
if (jobs.TryGet(rawArg.Values.First(), out JobDescriptor? descriptor))
|
||||
{
|
||||
IJob job = await CreateJobAsync(descriptor, rawArgs, cancellationToken).ConfigureAwait(false);
|
||||
IJob job;
|
||||
try
|
||||
{
|
||||
job = await CreateJobAsync(descriptor, rawArgs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CLog.Error(e, "Failed to instantiate job '{0}'", rawArg.Values.First());
|
||||
throw new JobException(e, "Failed to instantiate job '{0}'", rawArg.Values.First());
|
||||
}
|
||||
|
||||
JobResult result = await job.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
errorLevel = result.ErrorLevel;
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
CLog.Info("Executing job '{0}'...", job.Name);
|
||||
|
||||
JobResult result = await job.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
errorLevel = result.ErrorLevel;
|
||||
|
||||
CLog.Info("Executing job '{0}'... done (finished with error-level {1}, took {2})", job.Name, errorLevel, sw.Elapsed);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CLog.Info("Executing job '{0}'... done (threw exception after {1})", job.Name, sw.Elapsed);
|
||||
throw new JobException(e, "Failed to execute job '{0}'", job.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -49,7 +49,14 @@ internal sealed class JobRegistry
|
||||
{
|
||||
if (jobName == null && settingsType != null)
|
||||
{
|
||||
CLog.Error(errorMessage);
|
||||
if (errorMessage != null)
|
||||
{
|
||||
CLog.Error(errorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Failed to get name of job from type '{0}'", type.ToString(false));
|
||||
}
|
||||
}
|
||||
if (jobName != null && settingsType == null)
|
||||
{
|
||||
@@ -59,7 +66,7 @@ internal sealed class JobRegistry
|
||||
}
|
||||
}
|
||||
|
||||
CLog.Info("Loading job registry... done (registered {0} jobs)", _items.Count);
|
||||
CLog.Info("Loading job registry... done (found {0} jobs)", _items.Count);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using CertMgr.Core.Cli;
|
||||
using CertMgr.Core.Converters;
|
||||
using CertMgr.Core.Jobs;
|
||||
using CertMgr.Core.Log;
|
||||
using CertMgr.Core.Utils;
|
||||
using CertMgr.Core.Validation;
|
||||
|
||||
namespace CertMgr.Core;
|
||||
@@ -34,21 +35,29 @@ internal sealed class SettingsBuilder
|
||||
{
|
||||
(bool isCollection, Type elementType) = GetValueType(propertyInfo);
|
||||
|
||||
(bool converted, object? convertedValue) = await ConvertRawValueAsync(settingAttribute, rawArg, isCollection, elementType, cancellationToken).ConfigureAwait(false);
|
||||
if (converted)
|
||||
try
|
||||
{
|
||||
propertyInfo.SetValue(settings, convertedValue);
|
||||
|
||||
if (settingAttribute.Validator != null)
|
||||
(bool converted, object? convertedValue) = await ConvertRawValueAsync(settingAttribute, rawArg, isCollection, propertyInfo.PropertyType, elementType, cancellationToken).ConfigureAwait(false);
|
||||
if (converted)
|
||||
{
|
||||
ISettingValidator? validator = (ISettingValidator?)Activator.CreateInstance(settingAttribute.Validator, [settingAttribute.Name]);
|
||||
if (validator != null)
|
||||
propertyInfo.SetValue(settings, convertedValue);
|
||||
|
||||
if (settingAttribute.Validator != null)
|
||||
{
|
||||
ValidationResult valres = await validator.ValidateAsync(convertedValue, cancellationToken).ConfigureAwait(false);
|
||||
settings.ValidationResults.Add(valres);
|
||||
ISettingValidator? validator = (ISettingValidator?)Activator.CreateInstance(settingAttribute.Validator, [settingAttribute.Name]);
|
||||
if (validator != null)
|
||||
{
|
||||
ValidationResult valres = await validator.ValidateAsync(convertedValue, cancellationToken).ConfigureAwait(false);
|
||||
settings.ValidationResults.Add(valres);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CLog.Error(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", propertyInfo.Name, propertyInfo.PropertyType.ToString(false), _settingsType.ToString(false));
|
||||
throw new CertMgrException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", propertyInfo.Name, propertyInfo.PropertyType.ToString(false), _settingsType.ToString(false));
|
||||
}
|
||||
}
|
||||
else if (settingAttribute.IsMandatory)
|
||||
{
|
||||
@@ -65,7 +74,7 @@ internal sealed class SettingsBuilder
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Default value for argument '{0}' is specified, but its type is '{1}' instead of expected '{2}'", settingAttribute.Name, settingAttribute.Default?.GetType().Name ?? "<null>", elementType);
|
||||
CLog.Error("Default value for argument '{0}' is specified, but its type is '{1}' instead of expected '{2}'", settingAttribute.Name, settingAttribute.Default?.GetType().ToString(false) ?? "<null>", elementType.ToString(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +150,7 @@ internal sealed class SettingsBuilder
|
||||
return settings;
|
||||
}
|
||||
|
||||
private async Task<(bool success, object? convertedValue)> ConvertRawValueAsync(SettingAttribute settingAttribute, RawArgument rawArg, bool isCollection, Type targetType, CancellationToken cancellationToken)
|
||||
private async Task<(bool success, object? convertedValue)> ConvertRawValueAsync(SettingAttribute settingAttribute, RawArgument rawArg, bool isCollection, Type collectionType, Type elementType, CancellationToken cancellationToken)
|
||||
{
|
||||
bool success = false;
|
||||
object? convertedValue = null;
|
||||
@@ -150,10 +159,12 @@ internal sealed class SettingsBuilder
|
||||
{
|
||||
if (TryGetCustomConverter(settingAttribute, out IValueConverter? customConverter))
|
||||
{
|
||||
List<object?> values = new List<object?>();
|
||||
Type listType = typeof(List<>).MakeGenericType(elementType);
|
||||
IList values = (IList)Activator.CreateInstance(listType)!;
|
||||
|
||||
foreach (string rawValue in rawArg.Values)
|
||||
{
|
||||
convertedValue = await customConverter.ConvertAsync(rawValue, targetType, cancellationToken).ConfigureAwait(false);
|
||||
convertedValue = await customConverter.ConvertAsync(rawValue, elementType, cancellationToken).ConfigureAwait(false);
|
||||
values.Add(convertedValue);
|
||||
}
|
||||
convertedValue = values;
|
||||
@@ -161,15 +172,17 @@ internal sealed class SettingsBuilder
|
||||
}
|
||||
else
|
||||
{
|
||||
List<object?> values = new List<object?>();
|
||||
Type listType = typeof(List<>).MakeGenericType(elementType);
|
||||
IList values = (IList)Activator.CreateInstance(listType)!;
|
||||
|
||||
foreach (string rawValue in rawArg.Values)
|
||||
{
|
||||
if (TryConvertValue(rawValue, targetType, out convertedValue))
|
||||
if (TryConvertValue(rawValue, elementType, out convertedValue))
|
||||
{
|
||||
values.Add(convertedValue);
|
||||
}
|
||||
}
|
||||
convertedValue = values;
|
||||
convertedValue = values; // BuildCollectionValue(collectionType, elementType, values);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -177,10 +190,10 @@ internal sealed class SettingsBuilder
|
||||
{
|
||||
if (TryGetCustomConverter(settingAttribute, out IValueConverter? customConverter))
|
||||
{
|
||||
convertedValue = await customConverter.ConvertAsync(rawArg.Values.First(), targetType, cancellationToken).ConfigureAwait(false);
|
||||
convertedValue = await customConverter.ConvertAsync(rawArg.Values.First(), elementType, cancellationToken).ConfigureAwait(false);
|
||||
success = true;
|
||||
}
|
||||
else if (TryConvertValue(rawArg.Values.First(), targetType, out convertedValue))
|
||||
else if (TryConvertValue(rawArg.Values.First(), elementType, out convertedValue))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
@@ -192,6 +205,58 @@ internal sealed class SettingsBuilder
|
||||
return (success, convertedValue);
|
||||
}
|
||||
|
||||
private static object BuildCollectionValue(Type collectionType, Type elementType, IReadOnlyList<object?> items)
|
||||
{
|
||||
// convert source collection with 'items' of type 'object?' to collection with items of requested type:
|
||||
Type listType = typeof(List<>).MakeGenericType(elementType);
|
||||
IList typedList = (IList)Activator.CreateInstance(listType)!;
|
||||
|
||||
foreach (object? item in items)
|
||||
{
|
||||
typedList.Add(item);
|
||||
}
|
||||
|
||||
if (collectionType.IsArray)
|
||||
{
|
||||
Array array = Array.CreateInstance(elementType, typedList.Count);
|
||||
typedList.CopyTo(array, 0);
|
||||
return array;
|
||||
}
|
||||
|
||||
// it is either IEnumerable<T> or ICollection<T> or IList<T> or List<T>
|
||||
if (collectionType.IsAssignableFrom(listType))
|
||||
{
|
||||
return typedList;
|
||||
}
|
||||
|
||||
// we do not know the type, but try to instantiate 'collectionType' and if it implements ICollection<>, then fill it
|
||||
object? instance = Activator.CreateInstance(collectionType);
|
||||
if (instance != null)
|
||||
{
|
||||
Type? iCollectionT = collectionType
|
||||
.GetInterfaces()
|
||||
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
|
||||
|
||||
if (iCollectionT != null)
|
||||
{
|
||||
MethodInfo? add = iCollectionT.GetMethod("Add");
|
||||
if (add != null)
|
||||
{
|
||||
foreach (object? item in typedList)
|
||||
{
|
||||
add.Invoke(instance, new object?[] { item });
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try it as array, we fail anyway
|
||||
Array fallback = Array.CreateInstance(elementType, typedList.Count);
|
||||
typedList.CopyTo(fallback, 0);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private bool TryGetCustomConverter(SettingAttribute settingAttribute, [NotNullWhen(true)] out IValueConverter? customConverter)
|
||||
{
|
||||
customConverter = null;
|
||||
@@ -266,67 +331,14 @@ internal sealed class SettingsBuilder
|
||||
return (true, type.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
if (type.GetInterfaces().Any(i =>
|
||||
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
||||
foreach (Type i in type.GetInterfaces())
|
||||
{
|
||||
return (true, type.GetGenericArguments()[0]);
|
||||
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return (true, i.GetGenericArguments()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// if (type.IsGenericType && typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition()))
|
||||
// {
|
||||
// return (true, type.GetGenericArguments()[0]);
|
||||
// }
|
||||
// if (t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)))
|
||||
// {
|
||||
// return (true, t.GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)).GetGenericArguments()[0]);
|
||||
// }
|
||||
// if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
// {
|
||||
// return (true, t.GetGenericArguments()[0]);
|
||||
// }
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
private static bool IsEnumerableType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof(IEnumerable).IsAssignableFrom(type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.GetInterfaces().Any(i =>
|
||||
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Type UnwrapNullable(Type type)
|
||||
{
|
||||
Type? underlying = Nullable.GetUnderlyingType(type);
|
||||
if (underlying != null)
|
||||
{
|
||||
return underlying;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ internal static class Extenders
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Equivalent<T>(this IEnumerable<T?> left, IEnumerable<T?> right, IEqualityComparer<T?>? comparer = null)
|
||||
public static bool Equivalent<T>(this IEnumerable<T?> left, IEnumerable<T?> right, IEqualityComparer<T?>? comparer = null) where T : notnull
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
|
||||
@@ -99,7 +99,6 @@ internal static class StringBuilderCache
|
||||
if (Interlocked.Exchange(ref _disposed, 1) == 0)
|
||||
{
|
||||
StringBuilder? sb = _sb;
|
||||
_sb = null;
|
||||
Release(sb);
|
||||
}
|
||||
}
|
||||
@@ -121,7 +120,7 @@ internal static class StringBuilderCache
|
||||
return s;
|
||||
}
|
||||
|
||||
public StringBuilder Builder => _sb;
|
||||
public StringBuilder Builder => _disposed == 0 ? _sb : throw new ObjectDisposedException(nameof(ScopedBuilder));
|
||||
|
||||
public static implicit operator StringBuilder(ScopedBuilder b)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ internal static class StringFormatter
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.GetType();
|
||||
|
||||
using StringBuilderCache.ScopedBuilder lease = StringBuilderCache.AcquireScoped();
|
||||
StringBuilder sb = lease.Builder;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed class CertificateSettings : JobSettings
|
||||
[Setting("is-certificate-authority", Default = false, AlternateNames = ["isca"])]
|
||||
public bool IsCertificateAuthority { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("issuer", Converter = typeof(StorageConverter))]
|
||||
[Setting("issuer-certificate", Converter = typeof(StorageConverter))]
|
||||
public IStorage? Issuer { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("issuer-password")]
|
||||
|
||||
@@ -6,8 +6,17 @@ internal static class Program
|
||||
{
|
||||
private static async Task<int> Main(string[] args)
|
||||
{
|
||||
args = ["--job=create-certificate", "--issuer=file|o|c:\\friend2.pfx", "--issuer-password=aaa", "--subject=hello", "--san=world", "--algorithm=ecdsa", "--ecdsa-curve=p384", "--storage=file|w|c:\\mycert.pfx", "--validity-period=2d"];
|
||||
// args = ["--job=create-certificate", "--subject=hello", "--algorithm=ecdsa", "--curve=p384"];
|
||||
args = [
|
||||
"--job=create-certificate",
|
||||
"--issuer-certificate=file|o|c:\\friend2.pfx",
|
||||
"--issuer-password=aaa",
|
||||
"--subject=hello",
|
||||
"--san=world",
|
||||
"--algorithm=ecdsa",
|
||||
"--ecdsa-curve=p384",
|
||||
"--storage=file|w|c:\\mycert.pfx",
|
||||
"--validity-period=2d" ];
|
||||
|
||||
using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
|
||||
|
||||
JobExecutor executor = new JobExecutor();
|
||||
|
||||
Reference in New Issue
Block a user