Improve listing of available certificates (#120)

List certificates in groups based on potential use
This commit is contained in:
Bevan Arps 2019-01-25 16:21:06 +13:00 коммит произвёл peterbom
Родитель 7692988a07
Коммит 871796ab20
4 изменённых файлов: 139 добавлений и 12 удалений

Просмотреть файл

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;

Просмотреть файл

@ -0,0 +1,27 @@
using System.Linq;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.Azure.Batch.SoftwareEntitlement.Common
{
/// <summary>
/// Extension methods for working with <see cref="X509Certificate2"/>
/// </summary>
public static class X509Certificate2Extensions
{
/// <summary>
/// Test to see if a certificate supports a specific usage
/// </summary>
/// <param name="certificate">Certificate to test.</param>
/// <param name="use">The usage we want to know about.</param>
/// <returns>True if the certificate supports the specified usage; false otherwise.</returns>
public static bool SupportsUse(this X509Certificate2 certificate, X509KeyUsageFlags use)
{
if (certificate?.Extensions == null)
{
return false;
}
return certificate.Extensions.OfType<X509KeyUsageExtension>().FirstOrDefault()?.KeyUsages.HasFlag(use) ?? true;
}
}
}

Просмотреть файл

@ -31,26 +31,99 @@ namespace Microsoft.Azure.Batch.SoftwareEntitlement
{
var now = DateTime.Now;
if (commandLine.ShowExpired)
var showCerts = TryParseShow(commandLine.Show);
if (!showCerts.HasValue)
{
Logger.LogInformation("Including expired certificates.");
Logger.LogErrors(showCerts.Errors);
return Task.FromResult(-1);
}
var certificateStore = new CertificateStore();
var allCertificates = certificateStore.FindAll();
var query = allCertificates.Where(c => c.HasPrivateKey)
.Where(c => now < c.NotAfter || commandLine.ShowExpired)
var candidates = certificateStore.FindAll()
.Where(c => c.HasPrivateKey && c.RawData != null)
.ToList();
Logger.LogInformation("Found {Count} certificates with private keys", query.Count);
Logger.LogInformation("Found {Count} certificates with private keys", candidates.Count);
var rows = query.Select(DescribeCertificate).ToList();
rows.Insert(0, new List<string> { "Name", "Friendly Name", "Thumbprint", "Not Before", "Not After" });
if (showCerts.Value == ShowCertificates.All
|| showCerts.Value == ShowCertificates.ForEncrypting
|| showCerts.Value == ShowCertificates.ForSigning
|| showCerts.Value == ShowCertificates.NonExpired)
{
LogCertificates(
"Found {0} non-expired certificates with private keys that allow both encryption and signing",
candidates.Where(c => now < c.NotAfter
&& c.SupportsUse(X509KeyUsageFlags.DigitalSignature)
&& c.SupportsUse(X509KeyUsageFlags.DataEncipherment)));
}
Logger.LogInformationTable(rows);
if (showCerts.Value == ShowCertificates.All
|| showCerts.Value == ShowCertificates.ForSigning
|| showCerts.Value == ShowCertificates.NonExpired)
{
LogCertificates(
"Found {0} non-expired certificates with private keys that allow signing but not encryption",
candidates.Where(c => now < c.NotAfter
&& c.SupportsUse(X509KeyUsageFlags.DigitalSignature)
&& !c.SupportsUse(X509KeyUsageFlags.DataEncipherment)));
}
if (showCerts.Value == ShowCertificates.All
|| showCerts.Value == ShowCertificates.ForEncrypting
|| showCerts.Value == ShowCertificates.NonExpired)
{
LogCertificates(
"Found {0} non-expired certificates with private keys that allow encryption but not signing",
candidates.Where(c => now < c.NotAfter
&& !c.SupportsUse(X509KeyUsageFlags.DigitalSignature)
&& c.SupportsUse(X509KeyUsageFlags.DataEncipherment)));
}
if (showCerts.Value == ShowCertificates.All
|| showCerts.Value == ShowCertificates.NonExpired)
{
LogCertificates(
"Found {0} non-expired certificates with private keys that allow neither encryption nor signing",
candidates.Where(c => now < c.NotAfter
&& !c.SupportsUse(X509KeyUsageFlags.DigitalSignature)
&& !c.SupportsUse(X509KeyUsageFlags.DataEncipherment)));
}
if (showCerts.Value == ShowCertificates.All)
{
LogCertificates(
"Found {0} expired certificates",
candidates.Where(c => now >= c.NotAfter));
}
return Task.FromResult(0);
}
private void LogCertificates(string title, IEnumerable<X509Certificate2> certificates)
{
var rows = certificates.Select(DescribeCertificate).ToList();
if (rows.Count == 0)
{
// Nothing to log
return;
}
rows.Insert(0, new List<string>
{
"Name",
"Friendly Name",
"Thumbprint",
"Not Before",
"Not After"
});
Logger.LogInformation("");
Logger.LogInformation(title, rows.Count - 1);
Logger.LogInformation("");
Logger.LogInformationTable(rows);
}
private static IList<string> DescribeCertificate(X509Certificate2 cert)
{
var status = string.Empty;
@ -75,5 +148,32 @@ namespace Microsoft.Azure.Batch.SoftwareEntitlement
status
};
}
private static Errorable<ShowCertificates> TryParseShow(string show)
{
if (string.IsNullOrEmpty(show))
{
return Errorable.Success(ShowCertificates.NonExpired);
}
if (Enum.TryParse<ShowCertificates>(show, true, out var result))
{
// Successfully parsed the string
return Errorable.Success(result);
}
return Errorable.Failure<ShowCertificates>(
$"Failed to recognize '{show}'; valid choices are: `nonexpired` (default), 'forsigning', 'forencrypting', 'expired', and 'all'.");
}
[Flags]
private enum ShowCertificates
{
NonExpired = 1,
ForSigning = 2,
ForEncrypting = 4,
Expired = 8,
All = NonExpired | Expired | ForSigning | ForEncrypting
}
}
}

Просмотреть файл

@ -5,7 +5,7 @@ namespace Microsoft.Azure.Batch.SoftwareEntitlement
[Verb("list-certificates", HelpText = "List all available certificates.")]
public sealed class ListCertificatesCommandLine : CommandLineBase
{
[Option(HelpText = "Show expired certificates in list")]
public bool ShowExpired { get; set; }
[Option(HelpText = "Which certificates should be shown? (one of `nonexpired` (default), 'forsigning', 'forencrypting', 'expired', and 'all').")]
public string Show { get; set; }
}
}