Improve listing of available certificates (#120)
List certificates in groups based on potential use
This commit is contained in:
Родитель
7692988a07
Коммит
871796ab20
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче