Keeping an eye on PKI

Introduction

As Microsoft recently saw with Azure and I have personally run into many times, SSL certificates are becoming more and more of key piece of infrastructure that can be very difficult to keep a handle on.  It’s one of the few technologies that serves mission critical roles from granting access to secured websites to encrypting traffic for LDAP and other protocols that has a built in count down timer to disaster.  Now several monitoring solutions implement solutions for monitoring certificates on hosts by defining a certificates port and days ahead of time to alert you but if you are dealing with heavily secured systems that is not always ideal and it requires that the person setting up the service remembers to put that check in place.

I propose that there is a better way, at least if you are lucky enough to be using an in-house Microsoft Certificate Authority.  I recently developed an application to keep our server list in sync with what systems we actually have online in the datacenter and the idea of tracking expired certificates somehow worked its way into the project toward the end.  The idea is that we connect to the CA and ask it what certs are up for expiration.  Now that would be that if all certificates were important enough to be notified about via email but you probably don’t want to know every time a user’s certificate expires and auto renews.  Luckily most mission critical SSL certificates are from a narrowly used template the “Web Server” template or a customized version of this template.

The CODE – C# with .NET 4.5

Your code sheet must be using Interop.CERTADMINLib.dll – this can be found on any system that can manage your CA

//Configure Certificate Authority settings and prepare error parameters and containers
string strServer = "YourCA.company.domain";
string strCAName = "YourCAName";
TimeSpan twoWeeks = new System.TimeSpan(14, 0, 0, 0);
TimeSpan aMonth = new System.TimeSpan(30, 0, 0, 0);
StringBuilder certList = new StringBuilder();
StringBuilder critcertList = new StringBuilder();
int pkiErr = 0;
int critpkiErr = 0;

try
{
//Configure CA Variables
CCertView certView = null;
IEnumCERTVIEWROW certViewRow = null;
IEnumCERTVIEWCOLUMN certViewColumn = null;

// Connecting to the Certificate Authority
certView = new CCertView();
certView.OpenConnection(strServer + "\\" + strCAName);

//Configure required columns
certView.SetResultColumnCount(5);
var Index0 = certView.GetColumnIndex(0, "CommonName");
var Index1 = certView.GetColumnIndex(0, "NotAfter");
var Index2 = certView.GetColumnIndex(0, "Certificate Template");
var Index3 = certView.GetColumnIndex(0, "Request ID");
var Index4 = certView.GetColumnIndex(0, "Revocation Reason");
certView.SetResultColumn(Index0);
certView.SetResultColumn(Index1);
certView.SetResultColumn(Index2);
certView.SetResultColumn(Index3);
certView.SetResultColumn(Index4);

//Open the view and iterate column values
certViewRow = certView.OpenView();
for (int x = 0; certViewRow.Next() != -1; x++)
{
certViewColumn = certViewRow.EnumCertViewColumn();
string certName = "Missing certificate name";
DateTime certExpire = DateTime.Now;
string certTemplate = "";
string certID = "";
bool certRev = false;

while (certViewColumn.Next() != -1)
{
if (certViewColumn.GetValue(1) != null)
{
if (certViewColumn.GetDisplayName() == "Issued Common Name")
{
certName = certViewColumn.GetValue(1).ToString();
}
if (certViewColumn.GetDisplayName() == "Certificate Expiration Date")
{
certExpire = (DateTime)certViewColumn.GetValue(1);
}
if (certViewColumn.GetDisplayName() == "Certificate Template")
{
certTemplate = certViewColumn.GetValue(1).ToString();
}
if (certViewColumn.GetDisplayName() == "Request ID")
{
certID = certViewColumn.GetValue(1).ToString();
}
if (certViewColumn.GetDisplayName() == "Revocation Reason")
{
certRev = true;
}
}
}
//Check for certs expiring a month out and notify the CA administrator once
if (certExpire == DateTime.Now.Add(aMonth) & certTemplate == "WebServer" | certTemplate == "CustomTemplateInternalID")
{
pkiErr = pkiErr + 1;
certList.AppendLine("ID: " + certID + " " + certName + " will expire on: " + certExpire.ToString() + Environment.NewLine);
}
//Check for certs expiring two weeks out and notify EVERYONE everyday
else if (certExpire >= DateTime.Now & certExpire <= DateTime.Now.Add(twoWeeks) & certRev == false) { if (certTemplate == "WebServer" | certTemplate == "CustomTemplateInternalID") { critpkiErr = critpkiErr + 1; critcertList.AppendLine("ID: " + certID + " " + certName + " will expire on: " + certExpire.ToString() + Environment.NewLine); } } } //close CA connection certViewRow.Reset(); certView = null; //Define mail message MailMessage mail = new MailMessage(); SmtpClient SmtpServer = new SmtpClient("yourSMTP.Server"); mail.From = new MailAddress("pkimanager@noreply.nrp"); mail.Subject = "PKI Alert!"; // Send e-mail message with errors if (pkiErr >= 1)
{
mail.To.Add("Someone@your.company");
mail.Body = certList.ToString();
SmtpServer.Send(mail);
}
else if (critpkiErr >= 1)
{
mail.To.Add("LotsofPeople@your.company");
mail.Body = critcertList.ToString();
SmtpServer.Send(mail);
}
}
catch (Exception e)
{
//Do something to make your CA talk to this application
}

If you run this small app once a day it will quickly spit out any certs that are about to expire which will need to be renewed, revoked or both to clean up the alert.  You will need to use an account that has privileges to read the CA but I recall this being rather painless.  Hopefully you will find this useful as a centralized check for SSL expiration rather than host based checks that are always prone to false positives and require significantly more work to maintain.