Working with certificate revocation lists (CRL) in PowerShell (part 1)

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.

Motivation

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.

Exploring CRLs in PowerShell

Ok, I think, it is enough for motivation and we are ready to start.

View basic information

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:

Certificate Reovcation List

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

Digging into CRL details

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.

View CRL extensions

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.

View revoked certificates

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...}

This is all for the first part. In the next post (or posts) I will show more advanced and sophisticated stuff and CRL generation functionality.

Comments:

Captcha