Hello everyone!
Today I would like to summarize techniques on working with X.509 certificate revocation lists (CRL) in PowerShell. There are a lot of examples in my weblog, but most of this information is provided as context-specific addition to work in a given article’s context. Before talking about the subject, I’d like to put few words about the reason of this blog post and why it is written in that way.
As PowerShell evolves, it starts to cover more and new areas. And this process continues since PowerShell birth (in 2006). Systems administrators become more critical to script functionality. Previously, if something was not doable at all, we skipped that thing. With PowerShell we are able to do much more things. Maybe, not natively, maybe not in an elegant manner (say, through complex parsing), maybe very ugly, but we can do that right now. If community is interested in some area, they will develop a framework to make things easier and available to everyone.
One big area I’m interesting in is public key infrastructure, CryptoAPI, certificates and everything related to them. This happened several years ago (I recall it was in 2009) when I already was a passionate PowerShell enthusiast. Unfortunately, I realized that PowerShell has very-very basic support of certificate-related stuff. Mostly, this is due to poor support from underlying .NET platform. As the result, I attempted to start my big project to integrate cryptography into PowerShell through PS module and standalone scripts. Apparently, I was one of the first PS enthusiasts who started cryptography integration into PowerShell. I made huge work during these 7 years, went through tons of mistakes, bad choices, misunderstandings, trials and probes. It took 7 years and still continues. As of now, I have developed one of the best PowerShell module to work with PKI I’m aware of: PowerShell PKI. It is not just a module, it is a whole framework, which offers additional functionality through .NET-style objects and methods you can call from PS console. All this makes me confident in PowerShell PKI area.
However, I constantly see that other administrators and coders tries to reinvent the wheel. Unfortunately, they go through the same problems I faced in the past and this worries me. PKI is not very easy technology and when you combine it with poor code, there are little chances to succeed. I personally against this. If something is already done by community or very experienced individuals, try their code, because it went (most likely) through more thorough testing by community (consumers) and will appear more correct from the technology perspective and less buggy from the code perspective.
Today Boe Prox tweeted a link to a post that talks about how to read some x.509 CRL details. Although, the code uses very interesting tricks on parsing, it is not ready for use in production, because will fail in more complex scenarios, For example, if CA name length is more than 127 bytes (127 characters in ANSI or 63 characters in Unicode), the script will fail. With name in Unicode you will see clumsy spaces in the name. Another problem is the assumption of existence of NextUpdate
field. As per ASN module, NextUpdate
field is optional and may be absent. This can occur when CA is decommissioned and in order to support existing certificate validation, last CRL is published with empty NextUpdate
field, thus making CRL valid indefinitely. Next thing is that author splits time structures by UTCTime
tag identifier. Although, this would work for most scenarios, in some it won’t. If the timestamp object anywhere in the x.509 object is after 31th December 2049, it is encoded by using GeneralizedTime
tag identifier. With all respect to blog post author, I would say the following: I supported a wide range PKI environments and can certainly say that such non-standard cases appear often and such “short” scripts will simply fail. This is why I encourage to not reinvent the wheel and try ready solutions if they exist.
So, this (and upcoming) blog post will collect a comprehensive information about x.509 CRL support in PowerShell with PowerShell PKI module.
Ok, I think, it is enough for motivation and we are ready to start.
Along with x.509 certificates, an X.509 certificate revocation list (CRL) is an essential object in public key cryptography. X.509 certificates prove someone’s identity, while X.509 CRLs are used to determine if the certificate is not revoked by its issued authority. While there is a great support of x.509 certificates in .NET and PowerShell, there is zero support for x.509 CRLs. This is why I developed an X509CRL2 .NET class which is wrapped into Get-CertificateRevocationList function. X509CRL2 class can be constructed from a byte array or by specifying the path to a file:
PS C:\> $crl = Get-CRL C:\CRLs\ssca-sha2-g5.crl PS C:\> $crl Version : 2 Type : Base CRL IssuerName : System.Security.Cryptography.X509Certificates.X500DistinguishedName Issuer : CN=DigiCert SHA2 Secure Server CA, O=DigiCert Inc, C=US ThisUpdate : 2016.10.18. 20:14:33 NextUpdate : 2016.10.25. 20:00:00 SignatureAlgorithm : 1.2.840.113549.1.1.11 (sha256RSA) Extensions : {2.5.29.35 (Authority Key Identifier), 2.5.29.20 (CRL Number), 2.5.29.28 (Issuing Distribution Po int)} RevokedCertificates : {Serial number: 0c587cfa9bf443daeab70526d4bc009f revoked at: 2015.11.06. 21:57:32, Serial number: 0b3ba5097ac6f59b551a1338357a0981 revoked at: 2015.11.09. 11:22:51, Serial number: 0e213e45ff44bd 975d6d22cbb8a40f2d revoked at: 2015.11.09. 19:21:01, Serial number: 08d64d9f888feee694b32d06bba9f b83 revoked at: 2015.11.09. 19:22:03...} RawData : {48, 131, 4, 111...} Handle : 0 PS C:\>
For reference purposes I’ll use CRL issued by DigiCert CA in $crl
and My custom MS CA in $crl2
. Here we see general information about CRL, including its issuer, validity, signature algorithm, extensions and the list of revoked certificates. Exactly, what we see in GUI:
Issuer name is available as a decoded string (Issuer property) and as an X500DistinguishedName object (IssuerName property). ThisUpdate and NextUpdate are standard DateTime objects. Though, NextUpdate is nullable (can be null under certain circumstances). For quick CRL overview (without parsing it further) we can use verbose textual dump (a bit justified version of certutil –dump):
X509 Certificate Revocation List: Version: 2 Issuer: CN=DigiCert SHA2 Secure Server CA O=DigiCert Inc C=US This Update: 2016.10.18. 20:14:33 Next Update: 2016.10.25. 20:00:00 CRL Entries: 8291 Serial Number: 0c587cfa9bf443daeab70526d4bc009f Revocation Date: 2015.11.06. 21:57:32 Serial Number: 0b3ba5097ac6f59b551a1338357a0981 Revocation Date: 2015.11.09. 11:22:51 <...> CRL Extensions: 3 OID=Authority Key Identifier (2.5.29.35), Critical=False, Length=24 (18): KeyID=0f 80 61 1c 82 31 61 d5 2f 28 e7 8d 46 38 b4 2c e1 c6 d9 e2 OID=CRL Number (2.5.29.20), Critical=False, Length=4 (04): CRL Number=349 OID=Issuing Distribution Point (2.5.29.28), Critical=True, Length=49 (31): Distribution Point Name: Full Name: URL=http://crl3.digicert.com/ssca-sha2-g5.crl Only Contains User Certs=No Only Contains CA Certs=No Indirect CRL=No Signature Algorithm: Algorithm ObjectId: 1.2.840.113549.1.1.11 (sha256RSA) Signature: Unused bits=0 0000 12 eb ee 6b aa 1c 5c eb 86 97 09 8c d2 36 d2 b1 0010 68 9a 36 f6 4a d1 51 31 02 8e 85 56 09 b0 a6 05 0020 0c df f2 cc 2d 97 6b 41 4d f6 a1 20 39 ac 17 be 0030 ba 84 c5 dd 33 10 3c 4b 05 d6 a2 50 41 c9 47 fd 0040 c9 c9 cb f2 a3 1c 1d 70 27 9e 39 92 b2 1c 26 3e 0050 e9 68 d9 e0 38 b0 a1 85 0f 89 75 f5 5c 86 f4 99 0060 df 0a 38 77 0f b7 01 05 6c 9d 9a c7 eb 8b 35 0a 0070 44 1e 12 30 83 e0 14 e7 34 ba c2 55 7e ae c5 79 0080 4c 55 76 3b 4f ec e4 3b 6f 8b 43 b7 c5 80 50 35 0090 79 cf 81 d0 68 eb 5f d9 4f 27 56 b2 c2 0b 07 4f 00a0 00 57 b7 1a 5d 89 12 01 03 31 4e 35 b0 fb 39 ad 00b0 9a ea 66 ed 4b 0b 15 46 fd 18 50 07 3f 27 6d 2f 00c0 9d ef f2 ba ae db 7b 69 bf bd a0 1a 0f 64 9b 8c 00d0 fd 7b 80 18 37 e8 9b 17 6c cd 22 55 d8 ab bd 5c 00e0 a8 1a 53 01 53 03 ae 8d 70 92 52 fe d3 2a d6 30 00f0 94 04 59 b8 a0 68 79 bc 43 3c af 1c 3a 2e 6d 13
Let’s take a look into available properties and methods:
PS C:\> $crl | gm TypeName: System.Security.Cryptography.X509Certificates.X509CRL2 Name MemberType Definition ---- ---------- ---------- Build Method void Build(System.Security.Cryptography.X509Certificates.X509Certificate2 signerInfo,... CertificateInCrl Method bool CertificateInCrl(System.Security.Cryptography.X509Certificates.X509Certificate2 ... Dispose Method void Dispose(), void IDisposable.Dispose() Encode Method string Encode(SysadminsLV.Asn1Parser.EncodingType encoding), string Encode(System.Sec... Equals Method bool Equals(System.Object obj) Export Method void Export(string path, System.Security.Cryptography.X509Certificates.X509EncodingTy... GetCRLNumber Method bigint GetCRLNumber() GetHashCode Method int GetHashCode() GetNextPublish Method System.Nullable[datetime] GetNextPublish() GetSafeContext Method System.Security.Cryptography.X509Certificates.SafeCRLHandleContext GetSafeContext() GetType Method type GetType() HasDelta Method bool HasDelta() Import Method void Import(string path), void Import(byte[] rawData) ImportCRLEntries Method void ImportCRLEntries(System.Security.Cryptography.X509Certificates.X509CRLEntryColle... ImportExtensions Method void ImportExtensions(System.Security.Cryptography.X509Certificates.X509ExtensionColl... ReleaseContext Method void ReleaseContext() Reset Method void Reset() SetHashingAlgorithm Method void SetHashingAlgorithm(System.Security.Cryptography.Oid2 algorithmIdentifier) SetNextUpdate Method void SetNextUpdate(datetime nextUpdate) SetThisUpdate Method void SetThisUpdate(datetime thisUpdate) ToString Method string ToString(bool verbose), string ToString() VerifySignature Method bool VerifySignature(System.Security.Cryptography.X509Certificates.X509Certificate2 i... Extensions Property System.Security.Cryptography.X509Certificates.X509ExtensionCollection Extensions {get;} Handle Property System.IntPtr Handle {get;} Issuer Property string Issuer {get;} IssuerName Property System.Security.Cryptography.X509Certificates.X500DistinguishedName IssuerName {get;} NextUpdate Property System.Nullable[datetime] NextUpdate {get;} RawData Property byte[] RawData {get;} RevokedCertificates Property System.Security.Cryptography.X509Certificates.X509CRLEntryCollection RevokedCertifica... SignatureAlgorithm Property System.Security.Cryptography.Oid SignatureAlgorithm {get;} ThisUpdate Property datetime ThisUpdate {get;} Type Property string Type {get;} Version Property int Version {get;} PS C:\>
As we can see, an X509CRL2 class is full of features. We will explore them in this and upcoming blog posts.
If necessary, we can read CRL extensions:
PS C:\> $crl.Extensions IncludedComponents : KeyIdentifier KeyIdentifier : 0f80611c823161d52f28e78d4638b42ce1c6d9e2 IssuerNames : SerialNumber : Critical : False Oid : 2.5.29.35 (Authority Key Identifier) RawData : {48, 22, 128, 20...} CRLNumber : 349 Critical : False Oid : 2.5.29.20 (CRL Number) RawData : {2, 2, 1, 93} Critical : True Oid : 2.5.29.28 (Issuing Distribution Point) RawData : {48, 47, 160, 45...} PS C:\>
We see three extensions: Authority Key Identifier which provides information to correctly bind CRL issuer certificate among candidates, CRL Number and Issuing Distribution Point extensions. What I like in this output is that most common extensions are wrapped into their respective classes, so we don’t need to parse extensions, this is already done by my code. You can quickly retrieve AKI value, or CRL sequential number very quickly with zero effort. We can get better experience with MS CA generated CRLs:
PS C:\> $crl2 = Get-CRL '\\dc2\CertEnroll\contoso-DC2-CA(2).crl' PS C:\> $crl2.Extensions IncludedComponents : KeyIdentifier KeyIdentifier : 9dfdfcaac5bb26e2c49ad5d04b5d6a610a8aba43 IssuerNames : SerialNumber : Critical : False Oid : 2.5.29.35 (Authority Key Identifier) RawData : {48, 22, 128, 20...} Critical : False Oid : 1.3.6.1.4.1.311.21.1 (CA Version) RawData : {2, 3, 2, 0...} CRLNumber : 414 Critical : False Oid : 2.5.29.20 (CRL Number) RawData : {2, 2, 1, 158} NextCRLPublish : 2010.04.28. 19:24:46 Critical : False Oid : 1.3.6.1.4.1.311.21.4 (Next CRL Publish) RawData : {23, 13, 49, 48...} FreshestCrlDistributionPoints : {URL=http://www.contoso.com/pki/contoso-DC2-CA(2)+.crl} Critical : False Oid : 2.5.29.46 (Freshest CRL) RawData : {48, 57, 48, 55...} PS C:\>
As said, I support many extensions with decoded information. For example, we can easily get Delta CRL (if available) locations by discovering Freshest CRL extension:
PS C:\> $crl2.Extensions["2.5.29.46"] FreshestCrlDistributionPoints Critical Oid RawData ----------------------------- -------- --- ------- {URL=http://www.contoso.co... False 2.5.29.46 (Freshest CRL) {48, 57, 48, 55...} PS C:\> $crl2.Extensions["2.5.29.46"].GetURLs() http://www.contoso.com/pki/contoso-DC2-CA(2)+.crl PS C:\>
Extension object is of type of X509FreshestCRLExtension class. FreshestCrlDistributionPoints property is a collection of distribution point object with complex structures, but for robustness there is a GetURLs method that retrieves a collection of plain URLs.
In order to determine whether the CRL has differential Delta CRL we can do this shortcut:
PS C:\> $crl.HasDelta() False PS C:\> $crl2.HasDelta() True
I made HasDelta method to quickly determine whether Delta CRL is available for this Base CRL. By calling this method on Delta CRL object, it will return False, because Delta cannot have another Delta CRL. In a given example, DigiCert’s CRL doesn’t have delta, while my domain CA does.
We can look at the list of revoked certificates:
PS C:\> $crl.RevokedCertificates SerialNumber : 0c587cfa9bf443daeab70526d4bc009f RevocationDate : 2015.11.06. 21:57:32 ReasonCode : 0 ReasonMessage : Unspecified RawData : {48, 33, 2, 16...} SerialNumber : 0b3ba5097ac6f59b551a1338357a0981 RevocationDate : 2015.11.09. 11:22:51 ReasonCode : 0 ReasonMessage : Unspecified RawData : {48, 33, 2, 16...} <...> PS C:\>
the list is quite large:
PS C:\> $crl.RevokedCertificates.Count 8291
so I’m showing here only two entries. We can check if particular certificate is presented in CRL by calling CertificateInCrl method:
PS C:\> $crl2.CertificateInCrl("C:\Certs\revoked.cer") True
Here is a gotcha: although, the method accepts an instance of X509Certificate2 object, in PowerShell we can pass a string into the method, because X509Certificate2 has appropriate constructor (from string) to build the object from a file. So, if we pass path string to CertificateInCrl, PowerShell will silently attempt to construct the certificate object for us and then will pass it to calling method.
We see that the certificate is listed in the CRL, so we can get revocation details:
PS C:\> $crl2.RevokedCertificates[$revcert.SerialNumber] SerialNumber : 659bb31735250f08000300000757 RevocationDate : 2016.10.12. 5:48:00 ReasonCode : 0 ReasonMessage : Unspecified RawData : {48, 31, 2, 14...}
Post your comment:
Comments: