Recently I started another work on PKI task automation with PowerShell – PKI Health Tool (aka Enterprise PKI or pkiview.msc). As a start point I took pkiview.msc MMC snap-in functionality which consist of:

  1. Enumerate all Enterprise CAs in the forest. Filter inaccessible CAs;
  2. Retrieve the most recent CA Exchange certificate for each CA;
    1. Execute chain for each certificate to select trusted anchors and to go through the chain;
  3. retrieve all Issuer URLs from AIA;
    1. Validate each url (must be either http or ldap) and attempt to download the contents;
    2. If contents is downloaded, verify whether it is a certificate;
      1. Verify if the downloaded certificate is an issuer of CA Exchange certificate;
      2. Validate other cert properties;
  4. Extract URLs from CDP extension;
    1. Validate each url (must be either http or ldap) and attempt to download the contents;
    2. If contents is downloaded, verify whether it is a CRL;
      1. Validate basic CRL properties, like validity (not yet valid, expired, about to expire);
      2. Validate whether the CRL has valid signature (against CA certificate);
    3. Do the same for DeltaCRLs;
  5. Extract all OCSP URLs from AIA extension;
    1. Validate OCSP response by sending OCSP request and processing response;
  6. Compose status report (managed, I maintain report object and you can access report properties);
  7. Repeat steps 3-6 for each subsequent certificate in the chain up to root certificate;
  8. Compose summary report.

Here is a sample output of the script:

PS C:\> .\enterprise.ps1
==================== Contoso CA ====================


Name        : Contoso CA
Status      : Ok
ChainStatus : NoError
URLs        : {AIA Location #1: http://www.contoso.com/pki/Contoso_RCA(1).crt, expire: 2029.05.19. 10:11:24, Status: Ok
              , CDP Location #1: http://www.contoso.com/pki/contoso_RCA.crl, expire: 2015.01.28. 1:38:30, Status: Ok}
Childs      :

==================== contoso-DC2-CA ====================
Name        : contoso-DC2-CA
Status      : Error
ChainStatus : NoError
URLs        : {AIA Location #1: http://www.contoso.com/pki/dc2ica(2).crt, expire: 2015.03.05. 13:10:31, Status: Ok, CDP
               Location #1: http://www.contoso.com/pki/contoso-DC2-CA(2).crl, expire: 2014.12.29. 19:54:23, Status: Ok,
               DeltaCRL Location #1: http://www.contoso.com/pki/contoso-DC2-CA(2)+.crl, expire: , Status: FailedToDownl
              oad, OCSP Location #1: http://dc2.contoso.com/ocsp, expire: 2014.12.27. 19:54:32, Status: Ok}
Childs      :

Name        : Contoso CA
Status      : Ok
ChainStatus : NoError
URLs        : {AIA Location #1: http://www.contoso.com/pki/Contoso_RCA(1).crt, expire: 2029.05.19. 10:11:24, Status: Ok
              , CDP Location #1: http://www.contoso.com/pki/contoso_RCA.crl, expire: 2015.01.28. 1:38:30, Status: Ok}
Childs      :

==================== Contoso SHA2 CA ====================
Name        : Contoso SHA2 CA
Status      : Offline
ChainStatus : NoError
URLs        :
Childs      :



PS C:\>

At this point I decided to not compose tree view like in pkiview.msc because trees aren’t convenient for scripters and do not allow to compose status report in the way to display everything on top, without having to traverse the tree. The output list is plain. As the result, Childs property will be empty and removed in future versions.

I made a little trick to allow PowerShell to display nested URL element array information on main screen.

As you can see, the output is straightforward. First CA object represents a Enterprise CA element and the rest elements (within title) represent CA certificate chain. There might be duplicates, because CAs may share the same intermediate/root certificates in their chains. Status property contains either “Ok” or “Error” summary status. If any CA URL resulted in error, CA’s summary is set to “Error”. URLs property contains an array of URL elements:

PS C:\> $report[1].urls


Name              : AIA Location #1
Status            : Ok
ExtendedErrorInfo :
Url               : http://www.contoso.com/pki/dc2ica(2).crt
ExpirationDate    : 2015.03.05. 13:10:31
UrlType           : Certificate
UrlObject         :

Name              : CDP Location #1
Status            : Ok
ExtendedErrorInfo :
Url               : http://www.contoso.com/pki/contoso-DC2-CA(2).crl
ExpirationDate    : 2014.12.29. 19:54:23
UrlType           : Crl
UrlObject         :

Name              : DeltaCRL Location #1
Status            : FailedToDownload
ExtendedErrorInfo : Error 0x80190194 (-2145844844)
Url               : http://www.contoso.com/pki/contoso-DC2-CA(2)+.crl
ExpirationDate    :
UrlType           : Crl
UrlObject         :

Name              : OCSP Location #1
Status            : Ok
ExtendedErrorInfo :
Url               : http://dc2.contoso.com/ocsp
ExpirationDate    : 2014.12.27. 19:54:32
UrlType           : Ocsp
UrlObject         :



PS C:\>

For example, if we save script result to variable, we can examine report in details like shown above. I simulated the case when CRL file is not available for some URL. Each UrlElement contains Status property which can be one of the following values:

  • For AIA/issuer URL: Ok, FailedToDownload, NotYetValid, Expiring, Expired, Revoked, InvalidCert
  • For CRL URL: Ok, FailedToDownload, NotYetValid, Expiring, Expired, InvalidIssuer
  • For OCSP: Ok, Error

In addition, if the URL is active, a corresponding object can be accessed by calling GetObject() method:

PS C:\> $report[1].urls[0].getobject()

Thumbprint                                Subject
----------                                -------
E45ACBB4417260A622C9C45F4322C377A98BEC9D  CN=contoso-DC2-CA, DC=contoso, DC=com


PS C:\> $report[1].urls[1].getobject()


Version             : 2
Type                : Base CRL
IssuerDN            : System.Security.Cryptography.X509Certificates.X500DistinguishedName
Issuer              : CN=contoso-DC2-CA, DC=contoso, DC=com
ThisUpdate          : 2014.12.22. 18:34:23
NextUpdate          : 2014.12.29. 19:54:23
SignatureAlgorithm  : 1.2.840.113549.1.1.5 (sha1RSA)
Extensions          : {2.5.29.35 (Authority Key Identifier), 1.3.6.1.4.1.311.21.1 (CA Version), 2.5.29.20 (CRL Number),
                       1.3.6.1.4.1.311.21.4 (Next CRL Publish)...}
RevokedCertificates : {Serial number: 810100000200181d1a20 revoked at: 2010.12.24. 20:01:00, Serial number: 7e010000020
                      06940d31f revoked at: 2010.12.24. 20:01:00}
RawData             : {48, 130, 2, 119...}
Handle              : 494784224



PS C:\> $report[1].urls[3].getobject()


Version                  : 1
ResponseType             : id_pkix_ocsp_basic
ResponseStatus           : Successful
ProducedAt               : 2014.12.26. 19:23:30
NonceReceived            : False
NonceValue               :
ResponderKeyId           : 268E34F7908F644699DBEC5A8934CDA923ECA158
ResponderNameId          :
Request                  : PKI.OCSP.OCSPRequest
SignerCertificates       : {[Subject]
                             CN=dc2.contoso.com

                           [Issuer]
                             CN=contoso-DC2-CA, DC=contoso, DC=com

                           [Serial Number]
                             659BB31735250F0800020000069B

                           [Not Before]
                             2014.12.17. 17:14:55

                           [Not After]
                             2014.12.31. 17:14:55

                           [Thumbprint]
                             F05F935E65D8369346E61EC88C6C697812615F62
                           }
Responses                : {PKI.OCSP.CertID}
ResponseExtensions       :
HttpHeaders              : {Content-Length, Cache-Control, Content-Type, Date...}
SignerCertificateIsValid : True
SignatureIsValid         : True
ChainErrorInformation    :
ResponseErrorInformation : 0
SignatureAlgorithm       : 1.2.840.113549.1.1.5 (sha1RSA)
RawData                  : {48, 130, 5, 213...}



PS C:\>

This allows you to perform additional checks if necessary.

Download it and use to automate regular Enterprise PKI health status checking:

this script requires installed PowerShell PKI module!

Please note that this is a proof of concept, therefore it may not work in all scenarios and unhandled errors may appear. It would be great if you provide me some usage feedback to help me to make the tool better.

As always enjoy the automation of tools within the Windows-based, .NET aware, WPF accessible, multi-processes on the same IP / Port usage, admin's automation tool, powershell.exe!


Share this article:

Comments:

Jordan ALLIOT

This is a great first version! It will definitely replace the old camonitor.vbs script. Thanks a lot!

Vadims Podans

This is one of the intentions. Main intention is to provide more diagnostic functionality into my PSPKI module. Not sure if this appears in the next release of PSPKI, but at some point -- definitely.

Jordan ALLIOT

Hello again, Some ideas for improvement (for which I have no idea whether it is technically feasible or not): * Add support for monitoring previous but still valid CA keys/certificates (in case of CA re-key/renewal). When we re-key a CA, it is important to monitor that AIA/CRL/OCSP are still available for the previous key, in order to keep valid already issued certificates. Using the Exchange certificate only allows to monitor the very latest CA key/certificate unfortunately. * Add support (maybe through an optional parameter of the script) to monitor standalone CAs as well and even non-Microsoft CAs. For each of those, no Exchange certificate is available but we could base the check on a certificate in the local store or through a file path. I happen to have both use cases. For the second, I'm having a complex hierarchy where the root and some subCAs are based on a non-Microsoft solution while other subCAs run ADCS. I need to check the availability of each of their AIA/CDP/OCSP, regardless of the underlying solution. Besides, I also have another ADCS hierarchy in a different AD forest that I'd like to monitor from the same server/script. Being able to pass as parameter additional certificates for which to check the chain could help on this matter. Again, thanks a lot for your work.

Chipeater

Hi Vadims, Like the previous commenter - I think your work is ace and much appreciated. I've run into a little problem with the Enterprise PKI script though. Just as a background, in this test instance, I have a single Enterprise CA beneath an offline MS Root. There are four CDPs / AIAs - only the first and last CDPs are reachable from the location where I run the script (i.e. it is expected that CDP 2 and 3 are not reachable). I guess the errors are mainly related to messages rather than PSPKI functions as such? Can you possibly explain? Thanks, Chipeater .\EnterprisePKI.ps1 ==================== Chipeater Class 3 Primary CA ==================== Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Name : Chipeater Class 3 Primary CA Status : Error ChainStatus : NoError URLs : {AIA Location #1: http://epkicrl/epki/Chipeater Class 3 Primary CA.crt, expire: 25/09/2024 14:21:00, Status: Ok, AIA Location #2: http://IAMCS/pki/mps/Chipeater Class 3 Primary CA.crt, expire: , Status: FailedToDownload, AIA Location #3: http://epki.chipeater.uk/epki/Chipeater Class 3 Primary CA.crt, expire: , Status: FailedToDownload, AIA Location #4: http://chipeater.uk/epki/Chipeater Class 3 Primary CA.crt, expire: 25/09/2024 14:21:00, Status: Ok...} Childs : Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Name : Chipeater Root CA Status : Error ChainStatus : NoError URLs : {AIA Location #1: http://epkicrl/epki/Chipeater Root CA.crt, expire: 23/07/2034 15:09:24, Status: Ok, AIA Location #2: http://IAMCS/pki/mps/Chipeater Root CA.crt, expire: , Status: FailedToDownload, AIA Location #3: http://epki.chipeater.uk/epki/Chipeater Root CA.crt, expire: , Status: FailedToDownload, AIA Location #4: http://chipeater.uk/epki/Chipeater Root CA.crt, expire: 23/07/2034 15:09:24, Status: Ok...} Childs :

Vadims Podans

> Add support for monitoring previous but still valid CA keys/certificates I can do this. I'll put a note for myself to implement this. > Add support (maybe through an optional parameter of the script) to monitor standalone CAs Standalone CAs do not support key archival, as the result I can't get CA Exchange certificate. Needs another way. > I need to check the availability of each of their AIA/CDP/OCSP, regardless of the underlying solution. hard to say. I can do this as well. Don't get me wrong, it is technically possible (I don't see much problems for me to code this). I need to reevaluate this request.

Vadims Podans

To Chipeater, you are right, it is related to error code resolver. The error 12007 stands for ERROR_INTERNET_NAME_NOT_RESOLVED -- "The server name or address could not be resolved" However, the real problem is unknown for me, as my error code handles network-related errors. Can you confirm that wininet.dll library is installed in system32 folder on your system?

Chipeater

Hi Vadims, Like the previous commenter - I think your work is ace and much appreciated. I've run into a little problem with the Enterprise PKI script though. Just as a background, in this test instance, I have a single Enterprise CA beneath an offline MS Root. There are four CDPs / AIAs - only the first and last CDPs are reachable from the location where I run the script (i.e. it is expected that CDP 2 and 3 are not reachable). I guess the errors are mainly related to messages rather than PSPKI functions as such? Can you possibly explain? Thanks, Chipeater .\EnterprisePKI.ps1 ==================== Chipeater Class 3 Primary CA ==================== Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Name : Chipeater Class 3 Primary CA Status : Error ChainStatus : NoError URLs : {AIA Location #1: http://epkicrl/epki/Chipeater Class 3 Primary CA.crt, expire: 25/09/2024 14:21:00, Status: Ok, AIA Location #2: http://IAMCS/pki/mps/Chipeater Class 3 Primary CA.crt, expire: , Status: FailedToDownload, AIA Location #3: http://epki.chipeater.uk/epki/Chipeater Class 3 Primary CA.crt, expire: , Status: FailedToDownload, AIA Location #4: http://chipeater.uk/epki/Chipeater Class 3 Primary CA.crt, expire: 25/09/2024 14:21:00, Status: Ok...} Childs : Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Exception calling "GetMessage" with "1" argument(s): "No error messages are assoicated with error code: 12007 Operation failed." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Get-ErrorMessage.ps1:11 char:2 + [PKI.Utils.Error]::GetMessage($ErrorCode) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception Name : Chipeater Root CA Status : Error ChainStatus : NoError URLs : {AIA Location #1: http://epkicrl/epki/Chipeater Root CA.crt, expire: 23/07/2034 15:09:24, Status: Ok, AIA Location #2: http://IAMCS/pki/mps/Chipeater Root CA.crt, expire: , Status: FailedToDownload, AIA Location #3: http://epki.chipeater.uk/epki/Chipeater Root CA.crt, expire: , Status: FailedToDownload, AIA Location #4: http://chipeater.uk/epki/Chipeater Root CA.crt, expire: 23/07/2034 15:09:24, Status: Ok...} Childs :

Chipeater

Hi Vadims, I can confirm that the wininet.dll library is indeed present in the System32 folder. Cheers, Dave

Jordan ALLIOT

Another way of improvement I can think of: do not hardcode expiration notification threshold but instead use (configurable) percentage values of the certificate/CRL lifetime.

Vadims Podans

This is already in my list. I was too lazy to move them to parameters. > use (configurable) percentage values I took original pkiview.msc, where absolute values are used. Is it necessary to use percentage?

Jordan ALLIOT

For me absolute values is not good. I have a 3-tier hierarchy where the root and policy CAs are offline and have CRLs valid for 1 month whereas signing CAs are online with CRLs valid for 2 days and published every day. Publication of signing CAs CRLs is automatic and an alert should be triggered somewhere 18 hours before expiration whereas for offline CAs, this is a manual process (requiring HSM operators presence) that needs to be scheduled ahead of time so 18 hours would be way too short for instance. Speaking about HSMs, I have an error when the script tries to call GetCAExchangeCertificate() on an enterprise CA with HSM operator cards protection. This CA does not do key archival and we never need Exchange certs then. When the scripts calls the method, the CA tries to generate a new Exchange certificate, which triggers the HSM CSP prompt for operator card which never times out. I had to kill the powershell process because even CTRL+C did not exit the script. There is probably a bug in the HSM CSP but it would be great to have some sort of timeout on such operations.

Vadims Podans

Ok I will make further research and will try to find an optimal solution. > There is probably a bug in the HSM CSP but it would be great to have some sort of timeout on such operations. I can't do much here. All calls are synchronous and depend on underlying DCOM connections. However I'm planning to support alternate methods (by passing already issued certificates in the case of Standalone CAs) where you can avoid CA Exchange certificate retrieval. p.s. currently I'm passing exams at university, therefore I will be able to write the code in february.

Jordan ALLIOT

Yes that should be good! Although it would need to be available for enterprise CAs as well (as opt-in) in order for my use case to be usable. But don't worry. If too complicated to implement and too specific a use case, I'll deal with adapting the script for my particular use case myself. Good luck for your exams in the meantime!

Vadims Podans

> Although it would need to be available for enterprise CAs as well alternate input methods will be CA-independent. When you pass certificate as an input, the code will not contact CAs, instead it will use only information available in the certificate. > If too complicated to implement and too specific a use case, I'll deal with adapting the script for my particular use case myself. it is not complicated. I consider your suggestion enough common (heterogenous networks and multi-vendor CAs) and will include this functionality. And percentage calculus as well. I think to include separate parameters for CA certs, Base and Delta CRLs (three additional parameters) thershold settings. As I said, currently I have to focus on other tasks. However I won't do anything in regards of GetCAExchangeCertificate method. It is something case-specific. > Good luck for your exams thanks!

Vadims Podans

> Add support for monitoring previous but still valid CA keys/certificates I checked the documentation and it seems that I can't do this. There is no way to access CA Exchange for previous CA certs. CA Exchange cert is available for the most recent CA certificate.

Andy Ray

Hello! I was searching around the web for a way to easily document a PKI infrastructure. I've pulled the code (EnterprisePKI.ps1) - some of the code is a bit more advanced (the DLL leveraging) than me - but I think I can read along and get the concept. However, what I seem to be missing is some dependencies that this script may have on other modules, cmdlets, or tools.... namely it fails at "Get-CA" on line 311. I saw at the top of the code that it had a dependency on 3.0, which I initially assumed to be PowerShell 3.0, but I strongly suspect now it is the PS PKI Module on CodePlex? I looked it over and it seems to have a rich feature set, but doesn't appear to have the Get-CA cmdlet (or an alias for it). Is there something you have done for your environment that I missed? Thanks, Andy

Andy Ray

Turns out I didn't read ALL of the documentation on the PS PKI Module on CodePlex. May I throw 2 cents in and suggest a plug for the module at the top of your script and swap: >#requires -Version 3.0 for >#requires -Version 3.0 of http://pspki.codeplex.com/ Thanks!

Vadims Podans

#requires -version x.y -- it is an instruction, not a comment. PowerShell reads this line and throws exception if current PS version is lower than specified in this line. Therefore, your suggested line won't work. I made a note about dependencies in the blog post.

Jason Sheets

I don't see a link to your automation powershell script, only to the pki module.  Please provide a link.  Thank You

Jay

Where is the link? 

Thank's 


Post your comment:

Please, solve this little equation and enter result below. Captcha