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: