How to Verify the Validity of IIS Certificates on Remote Servers Using ABO – Part 2
Nawal Kishore Gupta here. I am a developer on the Information Security Tools team focused on building host security assessment tools.
This is the second part of a two part blog on how to verify the validity of IIS certificates on remote servers. The first part is posted here.
Reading and Displaying the Certificate.
ReadAndDisplayCertificate(strServerName,strStoreName,dwBinHashSize,&binSSLHash[0]);
This function contains the logic to parse the certificate.
The steps for this function are.
1. Add server name with store name
HRESULT hr=S_OK;
HCERTSTORE hSystemStore=NULL; // The system store handle.
PCCERT_CONTEXT pDesiredCert=NULL ; // Set to NULL for the first time
CString strStoreFullPath;
if (lpServerName && lpStoreName)
{
strStoreFullPath.Format(L"\\\\%s\\%s",lpServerName,lpStoreName);
}
else if (lpStoreName)
{
strStoreFullPath=lpStoreName;
}
2. Open Certificate Store
hSystemStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0, // Encoding type not needed
// with this PROV.
NULL, // Accept the default HCRYPTPROV.
CERT_SYSTEM_STORE_LOCAL_MACHINE,
// Set the system store location in
// the registry.
strStoreFullPath);
3. Search Certificate in Certificate Store using CertFindCertificateInStore
CRYPT_UINT_BLOB pBlob;
pBlob.pbData=binSSLHash;
pBlob.cbData=nBinHashSize;
pDesiredCert=CertFindCertificateInStore(
hSystemStore,
(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING), // Use X509_ASN_ENCODING.
0, // No dwFlags needed.
CERT_FIND_HASH, // Find a certificate with a // subject that matches the string // in the next parameter.
&pBlob , // The Unicode string to be found // in a certificate's subject.
NULL) ;
if (!pDesiredCert)
return FALSE;
4. Read the Values from the Certificate using CertGetNameString.
You can use the following definitions.
#define CERT_NAME_ISSUEDTO_FLAG 0
int cbSize = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
NULL,
0);
CString strName;
CString strIssuerName;
CString strIssuedToName;
TCHAR * pszName =NULL;
if (cbSize>1)
{
pszName = strName.GetBuffer( cbSize+1);
BOOL bConv = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
pszName,
cbSize);
strName.ReleaseBuffer();
if (bConv)
{
strIssuerName=strName;
printf("Certificate Issued By: %S\n",(LPCTSTR) strIssuerName);
}
}
cbSize = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUEDTO_FLAG,
NULL,
NULL,
0);
pszName =NULL;
if (cbSize>1)
{
pszName = strName.GetBuffer( cbSize+1);
BOOL bConv = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUEDTO_FLAG,
NULL,
pszName,
cbSize);
strName.ReleaseBuffer();
if (bConv)
{
strIssuedToName=strName;
printf("Certificate Issued To: %S\n",(LPCTSTR) strIssuedToName);
//printf
}
}
CERT_INFO * pCertInfo = pDesiredCert->pCertInfo;
SYSTEMTIME st;
FILETIME lft;
FileTimeToLocalFileTime((const FILETIME *)&pCertInfo->NotBefore, &lft);
FileTimeToSystemTime(( const FILETIME *) &pCertInfo->NotBefore, &st);
printf(("Certificate Not Valid Before (MM/DD/YY HH:MM) : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
FileTimeToSystemTime(( const FILETIME *) &pCertInfo->NotAfter, &st);
printf(("Certificate Not Valid After (MM/DD/YY HH:MM) : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
int nValid= CertVerifyTimeValidity(
NULL, // Use current time.
pCertInfo);
CString strMessage;
switch(nValid) // Pointer to CERT_INFO.
{
case -1 :
{
strMessage = (L"Certificate is not valid yet. \n");
break;
}
case 1:
{
strMessage = (L"Certificate is expired. \n");
break;
}
case 0:
{
strMessage = (L"Certificate's time is valid. \n");
break;
}
}
printf("%S\n", (LPCTSTR) strMessage);
return TRUE;
Hope this article will help you to manage your certificates automatically and alert you whenever any certificate is expired.
Complete Code Listing
// AboDemo.cpp : Defines the entry point for the console application.
//
//
#include "StdAfx.h"
// StdAfx.h should contains only
//#include "targetver.h"
//#include <stdio.h>
//#include <tchar.h>
//Remove other files from StdAfx.h
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
#include <initguid.h>
#include <wchar.h>
#include <objbase.h>
#include <ks.h>
#include <stdio.h>
#include <iadmw.h> // COM Interface header file.
#include <iiscnfg.h> // MD_ & IIS_MD_ #defines header file.
#include <objidl.h>
#include <atlBase.h> // ATL support header file
#include <atlstr.h>
//For Certificate
#include <Wincrypt.h>
#pragma comment(lib, "Crypt32.lib")
//This definations are missing in iadmw.
// Got from some research on net and MSDN
#define MD_SSL_CERT_HASH 5506
#define MD_SSL_STORE_NAME 5511
#define MD_READ_TIMEOUT 40
#define MAX_CERT_SIZE 1024
#define CERT_NAME_ISSUEDTO_FLAG 0
#define BUFFER_LEN 1024
#define MAX_BUFFER_SIZE 65536
#define HTAB_SIZE 517
//Must Change
#define SERVERNAME L"remoteservername"
//You must be an admin in remote machine or use admin token to run this application
BOOL ReadAndDisplayCertificate(LPCTSTR lpszServerName,LPCTSTR lpszStoreName,int nBinHashSize, BYTE *binSSLHash);
int _tmain(int argc, _TCHAR* argv[])
{
//Some error handling omitted for sake of brevity
HRESULT hresError = CoInitializeEx(NULL, COINIT_MULTITHREADED);
//IMSAdminBase DCOM Object Pointer
CComPtr <IMSAdminBase> pIAdminBase;
IClassFactory * pcsfFactory = NULL;
COSERVERINFO csiMachineName;
memset(&csiMachineName,0, sizeof(COSERVERINFO));
if (argc>=2)
{
csiMachineName.pwszName = argv[1];
}
else
{
csiMachineName.pwszName = SERVERNAME;
}
CString strServerName(csiMachineName.pwszName);
//pcsiParam = &csiMachineName;
hresError = CoGetClassObject(GETAdminBaseCLSID(TRUE), CLSCTX_SERVER, &csiMachineName,IID_IClassFactory, (void**) &pcsfFactory);
if (FAILED(hresError))
{
printf ("ERROR: CoGetClassObject Failed! Error: %d (%#x)\n", hresError, hresError);
return hresError;
}
else
{
hresError = pcsfFactory->CreateInstance(NULL, IID_IMSAdminBase, (void **) &pIAdminBase);
if (FAILED(hresError))
{
printf ("ERROR: CreateInstance Failed! Error: %d (%#x)\n", hresError, hresError);
pcsfFactory->Release();
return hresError;
}
pcsfFactory->Release();
}
//Open Root Handle
METADATA_HANDLE hMetaDataRoot=NULL;
METADATA_RECORD mdrRoot;
HRESULT hRes = pIAdminBase->OpenKey(METADATA_MASTER_ROOT_HANDLE, TEXT("/LM"),
METADATA_PERMISSION_READ, 20, &hMetaDataRoot);
BYTE buffer[BUFFER_LEN];
DWORD dwReturnBufferSize=0;
CString strStoreName;
mdrRoot.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrRoot.dwMDUserType = IIS_MD_UT_SERVER;
mdrRoot.dwMDDataType = ALL_METADATA;
mdrRoot.dwMDDataLen = BUFFER_LEN;
mdrRoot.pbMDData = buffer;
mdrRoot.dwMDIdentifier = MD_SSL_STORE_NAME; // MD_SERVER_COMMENT or so
hRes = pIAdminBase->GetData(hMetaDataRoot, L"/W3SVC/1", &mdrRoot, &dwReturnBufferSize);
if (hRes==S_OK)
{
strStoreName= (WCHAR*)mdrRoot.pbMDData;
}
BYTE binSSLHash[MAX_CERT_SIZE];
DWORD dwBinHashSize;
mdrRoot.dwMDAttributes = METADATA_NO_ATTRIBUTES;
mdrRoot.dwMDUserType = IIS_MD_UT_SERVER;
mdrRoot.dwMDDataType = BINARY_METADATA;
mdrRoot.dwMDDataLen = MAX_CERT_SIZE;
mdrRoot.pbMDData = (unsigned char *)&binSSLHash;
mdrRoot.dwMDIdentifier = MD_SSL_CERT_HASH ;
//Read the first web site
hRes = pIAdminBase->GetData(hMetaDataRoot,L"/W3SVC/1", &mdrRoot, &dwBinHashSize);
if (!SUCCEEDED(hRes))
{
return hRes;
}
dwBinHashSize=mdrRoot.dwMDDataLen;
//CString strHashString= GetHashString(dwBinHashSize,&binSSLHash[0]);
ReadAndDisplayCertificate(strServerName,strStoreName,dwBinHashSize,&binSSLHash[0]);
return hRes;
}
BOOL ReadAndDisplayCertificate(LPCTSTR lpServerName,LPCTSTR lpStoreName,int nBinHashSize, BYTE *binSSLHash)
{
HRESULT hr=S_OK;
HCERTSTORE hSystemStore=NULL; // The system store handle.
PCCERT_CONTEXT pDesiredCert=NULL ; // Set to NULL for the first time
CString strStoreFullPath;
if (lpServerName && lpStoreName)
{
strStoreFullPath.Format(L"\\\\%s\\%s",lpServerName,lpStoreName);
}
else if (lpStoreName)
{
strStoreFullPath=lpStoreName;
}
else
{
return S_FALSE;
}
hSystemStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0, // Encoding type not needed
// with this PROV.
NULL, // Accept the default HCRYPTPROV.
CERT_SYSTEM_STORE_LOCAL_MACHINE,
// Set the system store location in
// the registry.
strStoreFullPath);
if (!hSystemStore)
{
return S_FALSE;
}
CRYPT_UINT_BLOB pBlob;
pBlob.pbData=binSSLHash;
pBlob.cbData=nBinHashSize;
pDesiredCert=CertFindCertificateInStore(
hSystemStore,
(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING), // Use X509_ASN_ENCODING.
0, // No dwFlags needed.
CERT_FIND_HASH, // Find a certificate with a // subject that matches the string // in the next parameter.
&pBlob , // The Unicode string to be found // in a certificate's subject.
NULL) ;
if (!pDesiredCert)
return FALSE;
int cbSize = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
NULL,
0);
CString strName;
CString strIssuerName;
CString strIssuedToName;
TCHAR * pszName =NULL;
if (cbSize>1)
{
pszName = strName.GetBuffer( cbSize+1);
BOOL bConv = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
pszName,
cbSize);
strName.ReleaseBuffer();
if (bConv)
{
strIssuerName=strName;
printf("Certificate Issued By: %S\n",(LPCTSTR) strIssuerName);
}
}
cbSize = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUEDTO_FLAG,
NULL,
NULL,
0);
pszName =NULL;
if (cbSize>1)
{
pszName = strName.GetBuffer( cbSize+1);
BOOL bConv = CertGetNameString(
pDesiredCert,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUEDTO_FLAG,
NULL,
pszName,
cbSize);
strName.ReleaseBuffer();
if (bConv)
{
strIssuedToName=strName;
printf("Certificate Issued To: %S\n",(LPCTSTR) strIssuedToName);
//printf
}
}
CERT_INFO * pCertInfo = pDesiredCert->pCertInfo;
SYSTEMTIME st;
FILETIME lft;
FileTimeToLocalFileTime((const FILETIME *)&pCertInfo->NotBefore, &lft);
FileTimeToSystemTime(( const FILETIME *) &pCertInfo->NotBefore, &st);
printf(("Certificate Not Valid Before (MM/DD/YY HH:MM) : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
FileTimeToSystemTime(( const FILETIME *) &pCertInfo->NotAfter, &st);
printf(("Certificate Not Valid After (MM/DD/YY HH:MM) : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
int nValid= CertVerifyTimeValidity(
NULL, // Use current time.
pCertInfo);
CString strMessage;
switch(nValid) // Pointer to CERT_INFO.
{
case -1 :
{
strMessage = (L"Certificate is not valid yet. \n");
break;
}
case 1:
{
strMessage = (L"Certificate is expired. \n");
break;
}
case 0:
{
strMessage = (L"Certificate's time is valid. \n");
break;
}
}
printf("%S\n", (LPCTSTR) strMessage);
return TRUE;
}