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:
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:
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!
This is a great first version! It will definitely replace the old camonitor.vbs script. Thanks a lot!
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.
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.
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 :
> 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.
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?
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 :
Hi Vadims, I can confirm that the wininet.dll library is indeed present in the System32 folder. Cheers, Dave
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.
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?
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.
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.
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!
> 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!
> 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.
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
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!
#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.
I don't see a link to your automation powershell script, only to the pki module. Please provide a link. Thank You
Where is the link?
Thank's
The most recent and actual version is on Github: https://github.com/Crypt32/PSPKI/blob/master/PSPKI/Server/Get-EnterprisePKIHealthStatus.ps1
Post your comment:
Comments: