PowerShell PKI module v3.0 (part 3)

Continuing the story about new release I will talk about two interesting features.

PKCS#7 data support

Previously there was only basic PKCS#10 certificate request support. However, there are many cases when certificate request is composed in a PKCS#7 signed message. As a start point, I tried to utilize both, EnvelopedCms and SignedCms and other related classes in the PKCS namespace. However, I quickly figured out that these classes are completely useless and do not provide any way to access PKCS#7 contents. Shame on .NET!

Eventually, I opened a PKCS#7 ASN module and started manual decoding procedure. Hopefully, I already have enough good generic ASN.1 structure parser (in ASN namespace), which was combined with some p/invoke to decode common structures. As the result, I ended with several classes in my own PKCS namespace. Classes which names ends with 2 – are my own implementations of corresponding .NET classes.

Main class to decode PKCS#7 signed message is PKCS7SignedMessage class. It has properties which corresponds to PKCS#7 ASN module properties. Actual signed message is stored in the Content property which is of Object return type. Generic Object type is necessary, because PKCS#7 message content can be whatever you can imagine. If this is a request, then Content property contains embedded PKCS#10 certificate request. If this archived key BLOB, then there will be an appropriate object. To determine what object is stored in the Content property, I introduced ContentType property. As you can see from the content type -> content binding table, only CMC request is implemented so far.

Content property will contain an array of embedded objects. For example, an array of PKCS#10 objects, although, there cannot be more than one request in the PKCS#7 message. This behavior came from PKCS#7 ASN module which allows multiple objects of ContentType in the Content property.

Other content types causes a raw byte array in the Content property.

Here is how it looks:

PS C:> $pkcs7 = New-Object System.Security.Cryptography.PKCS.PKCS7SignedMessage D:\Users\vPodans\Desktop\pkcs7.tx
t
PS C:> $pkcs7.ContentType

Value                                                       FriendlyName
-----                                                       ------------
1.3.6.1.5.5.7.12.2                                          CMC Data


PS C:>

Therefore we can access Content property and should see an array of X509CertificateRequest objects:

PS C:> $pkcs7.Content[0]


Version            : 1
RequestType        : PKCS10
Subject            : CN=this is a test, CN=Another Common Name
SubjectDN          : System.Security.Cryptography.X509Certificates.X500DistinguishedName
PublicKey          : System.Security.Cryptography.X509Certificates.PublicKey
Attributes         : {0, 0, 0}
Extensions         : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.
                     Oid, System.Security.Cryptography.Oid...}
ExternalData       :
SignatureIsValid   : True
SignatureAlgorithm : System.Security.Cryptography.Oid
RawData            : {48, 130, 4, 70...}



PS C:> $pkcs7.Content[0].Extensions


TemplateOid  : System.Security.Cryptography.Oid
MajorVersion : 100
MinorVersion : 17
Critical     : False
Oid          : System.Security.Cryptography.Oid
RawData      : {48, 47, 6, 39...}

EnhancedKeyUsages : {Server Authentication}
Critical          : False
Oid               : System.Security.Cryptography.Oid
RawData           : {48, 10, 6, 8...}

KeyUsages : KeyEncipherment, DigitalSignature
Critical  : True
Oid       : System.Security.Cryptography.Oid
RawData   : {3, 2, 5, 160}

ApplicationPolicies : {Server Authentication}
Critical            : False
Oid                 : System.Security.Cryptography.Oid
RawData             : {48, 12, 48, 10...}

AlternativeNames : {DNS Name=MoreDNS.test.iu.ed, DNS Name=onemore.test.iu.ed}
Critical         : False
Oid              : System.Security.Cryptography.Oid
RawData          : {48, 42, 130, 19...}

SubjectKeyIdentifier : 852EB22D04B45E50FD08F8D8D36B1B106E0FCDC1
Critical             : False
Oid                  : System.Security.Cryptography.Oid
RawData              : {4, 20, 133, 46...}



PS C:>

And here we see a common PKCS#10 request object. In addition I displayed request extensions. What if there is some other content?

PS C:> $pkcs7 = New-Object System.Security.Cryptography.PKCS.PKCS7SignedMessage D:\Users\vPodans\Desktop\key.blob

PS C:> $pkcs7.ContentType

Value                                                       FriendlyName
-----                                                       ------------
1.2.840.113549.1.7.1                                        PKCS 7 Data


PS C:> $pkcs7


Version          : 1
DigestAlgorithms : {sha256}
ContentType      : System.Security.Cryptography.Oid
Content          : {4, 130, 6, 80...}
Certificates     : {[Subject]
                     CN=Contoso CA, DC=contoso, DC=com

                   [Issuer]
                     CN=Contoso CA, DC=contoso, DC=com

                   [Serial Number]
                     0305C721370E458748FB95D3F45B06A6

                   [Not Before]
                     15.02.2009. 16:31:15

                   [Not After]
                     19.05.2029. 10:11:24

                   [Thumbprint]
                     5DF395AFFB9E6FB62F6EC58DF6790150954949AF
                   , [Subject]
                     CN=contoso-DC2-CA, DC=contoso, DC=com

                   [Issuer]
                     CN=Contoso CA, DC=contoso, DC=com

                   [Serial Number]
                     159BC07A00010000002A

                   [Not Before]
                     06.03.2010. 13:10:31

                   [Not After]
                     05.03.2015. 13:10:31

                   [Thumbprint]
                     E45ACBB4417260A622C9C45F4322C377A98BEC9D
                   , [Subject]
                     CN=Administrator, CN=Users, DC=contoso, DC=com

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

                   [Serial Number]
                     152D129D0002000001C7

                   [Not Before]
                     21.03.2011. 18:29:04

                   [Not After]
                     20.03.2013. 18:29:04

                   [Thumbprint]
                     965EA393D8336A386209A06340038EE4C6728F14
                   , [Subject]
                     CN=Administrator, CN=Users, DC=contoso, DC=com

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

                   [Serial Number]
                     153008DB0002000001C8

                   [Not Before]
                     21.03.2011. 18:32:19

                   [Not After]
                     20.03.2012. 18:32:19

                   [Thumbprint]
                     0FB30ECEA5D7F48531CC1CF534CC067F30F008A0
                   }
RevocationLists  :
Attributes       :
SignerInfos      : {System.Security.Cryptography.Pkcs.SignerInfo2}
RawData          : {48, 130, 27, 19...}



PS C:>

I grabbed archived key blob. Content type is generic PKCS 7 Data. Actually it stores encrypted archived key and Certificates property contains key recovery agents certificates and certificate chains.

Enhanced X.509 certificate request support

Previous section talked about general use of PKCS#7 message. In order to work with certificate request, I extended X509CertificateRequest class by supporting both, PKCS#10 and CMC/PKCS#7 certificate requests:

PS C:\> $pkcs10 = New-Object System.Security.Cryptography.X509CertificateRequests.X509CertificateRequest D:\Users\
vPodans\Desktop\pkcs10.txt
PS C:\> $pkcs10


Version            : 1
RequestType        : PKCS10
Subject            : CN=XX-UNIT-EXAMPLE.workstation.offline
SubjectDN          : System.Security.Cryptography.X509Certificates.X500DistinguishedName
PublicKey          : System.Security.Cryptography.X509Certificates.PublicKey
Attributes         : {0, 0, 0, 0}
Extensions         : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.
                     Oid}
ExternalData       :
SignatureIsValid   : True
SignatureAlgorithm : System.Security.Cryptography.Oid
RawData            : {48, 130, 4, 22...}



PS C:\> $pkcs7 = New-Object System.Security.Cryptography.X509CertificateRequests.X509CertificateRequest D:\Users\v
Podans\Desktop\pkcs7.txt
PS C:\> $pkcs7


Version            : 1
RequestType        : PKCS7
Subject            : CN=this is a test, CN=Another Common Name
SubjectDN          : System.Security.Cryptography.X509Certificates.X500DistinguishedName
PublicKey          : System.Security.Cryptography.X509Certificates.PublicKey
Attributes         : {0, 0, 0}
Extensions         : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.
                     Oid, System.Security.Cryptography.Oid...}
ExternalData       : System.Security.Cryptography.Pkcs.PKCS7SignedMessage
SignatureIsValid   : True
SignatureAlgorithm : System.Security.Cryptography.Oid
RawData            : {48, 130, 6, 132...}



PS C:\>

Generic X509CertificateRequest supports any of them, so you don’t need to care which certificate request format is stored in the file. Just pass the file to the class constructor and it will do the rest. There are two properties that makes difference:

  • RequestType – identifies whether the request is PKCS#10 or PKCS#7.
  • ExternalData – gets the PKCS#7 envelope. It is null for PKCS#10 requests. In other words, ExternalData property is raw PKCS#7 object, and current X509CertificateRequest object is extracted object from Content property.

Another small, but useful feature is certificate request dump in a certutil-style. Call ToString() method on X509CertificateRequest object:

PS C:\> $pkcs10.ToString()
PKCS10 Certificate Request:
Version: 1
Subject:
    CN=XX-UNIT-EXAMPLE.workstation.offline

Public Key Algorithm:
    Algorithm ObjectId: RSA (1.2.840.113549.1.1.1)
    Algorithm Parameters:
    05 00
Public Key Length: 2048 bits
Public Key: UnusedBits=0
    0000        30 82 01 0a 02 82 01 01  00 e9 68 ab 2b 03 8c 65
    0010        51 66 68 43 9f 85 56 73  da 8a 53 95 e2 76 a8 e4
    0020        96 de e1 4c 8d 88 26 84  d6 ac 80 fc 2e 32 99 a3
    0030        10 63 a2 98 6e 10 80 fa  19 ac fd f8 13 08 d1 df
    0040        96 5d 88 fa a8 99 31 b0  03 0e 06 60 78 ac 19 ac
    0050        b1 66 41 23 d4 83 24 52  0b e2 62 00 5a be 70 bd
    0060        25 2c 1f 19 14 90 d2 1c  92 2c 3d 04 4e d0 9c b9
    0070        87 64 c4 64 c0 23 54 83  11 eb b3 71 e5 91 32 51
    0080        66 c8 cc 94 7c 34 2e a0  60 db 84 e0 36 c1 26 8d
    0090        51 6f 88 f6 53 26 a0 e5  83 e5 5b 2c ca d3 a3 fb
    00a0        60 7e 31 db f7 68 6b 3a  7d 28 3f db 5e 98 b4 40
    00b0        24 d1 2a bc f2 2f d6 73  36 4b c9 18 65 94 6d e1
    00c0        7d f2 a0 06 23 a0 c7 5a  a7 a6 ec d4 f8 17 fc 19
    00d0        a2 e7 58 16 03 6f 4c 51  17 3f 42 76 ae f8 cb 89
    00e0        b1 fb af 12 3d f8 31 35  89 ef 28 de 5a b4 5e bc
    00f0        92 7d 20 53 f8 87 1a 39  5b 5d 66 70 c4 8b 9e f9
    0100        58 e8 65 2e 54 a4 a8 08  a3 02 03 01 00 01
Request attributes (Count=4):
  Attribute[0], Length=12 (0c):
    OS Version (OID=1.3.6.1.4.1.311.13.2.3): 6.2.9200.2

  Attribute[1], Length=67 (43):
    Client Info (OID=1.3.6.1.4.1.311.21.20):
        Client ID: 9
        Computer name: IU-MSSG-FRED81.testads.iu.edu
        User name: TESTADS\tskfserv
        Process name: certreq.exe

  Attribute[2], Length=90 (5a):
    Enrollment Name Value Pair (OID=1.3.6.1.4.1.311.13.2.1):
        SAN=dns=XX-UNIT-EXAMPLE.workstation.offline

  Attribute[3], Length=100 (64):
    CSP Info (OID=1.3.6.1.4.1.311.13.2.2):
        KeySpec: 1
        Provider: Microsoft RSA SChannel Cryptographic Provider
        Signature unused bits: 0

Request extensions (Count=3):
  OID=Key Usage (2.5.29.15), Critical=True, Length=4 (04):
    Digital Signature, Key Encipherment (a0)

  OID=Enhanced Key Usage (2.5.29.37), Critical=False, Length=12 (0c):
    Client Authentication (1.3.6.1.5.5.7.3.2)

  OID=Subject Key Identifier (2.5.29.14), Critical=False, Length=22 (16):
    e4 fe ed 3e 00 82 aa 1c d9 48 88 c4 6a 1c c7 78 ab 79 44 b6

Signature Algorithm:
    Algorithm ObjectId: 1.2.840.113549.1.1.5 (sha1RSA)
Signature: Unused bits=0
    0000        77 13 c8 e3 13 0a ff be  f2 18 b7 cd 5e c2 ec 61
    0010        cc 26 ad 5b ca f4 24 f9  02 f1 60 c6 5c d1 55 f7
    0020        07 c7 ea 52 ed 7a e7 18  fc 18 54 f6 78 cb 49 f1
    0030        11 31 49 c2 56 87 81 a0  70 05 2e 91 fc 58 cb 68
    0040        39 4b a5 04 04 91 29 c8  25 e7 af 5e d4 de 0d 25
    0050        98 70 8d 56 3a f9 b4 cb  d9 32 c7 e7 25 68 dd cc
    0060        06 58 07 3c 5b 33 0e 5a  ce 83 e6 89 b5 9f 84 ef
    0070        ca 62 13 18 bd ff e9 1f  dd a7 ea 46 b4 88 48 8f
    0080        ae 18 23 91 12 43 75 6e  66 46 8a 60 fd 0a 15 36
    0090        21 bf 7e b1 7c 96 e8 06  55 c0 21 bc 68 94 db 68
    00a0        9e 73 de 6f b1 91 9e e7  ac ca 5d 9e dc 87 cb 54
    00b0        e3 d5 34 80 f2 57 69 28  57 9b da 1a be a9 a1 3a
    00c0        44 89 33 5b ec ca 4f e6  13 7b a7 78 de 72 d3 87
    00d0        ea 26 79 ad 66 4a ba 06  ca 37 39 bd 91 de e6 f4
    00e0        f4 82 1f 53 23 a5 02 5d  2c a4 23 2a ff 40 e4 43
    00f0        2c ef a2 ad b5 3b 2e f7  71 ed 67 b4 7c 2b 0c 07
Signature matches Public Key: True

PS C:\>

The output is *similar* to certutil, not the same. I slightly adjusted rendered data to make it less confusing, but followed formatting rules to make it more familiar.

CNG request support

And the last section in today’s article. It is uncommon when certificate request contains CNG key and CNG signature. And once again, I struggled with awful CNG key support in .NET. I was forced to write additional code, hacks and tricks to make it working. Moreover, I didn’t found any working solution in internet. You can find such code samples in MessageSignature.cs file in PKI.Core.dll sources.

However, it now works:

PS C:\> $pkcs10 = New-Object System.Security.Cryptography.X509CertificateRequests.X509CertificateRequest "D:\Users
\vPodans\Desktop\CA-2.req"
PS C:\> $pkcs10.ToString()
PKCS10 Certificate Request:
Version: 1
Subject:
    CN=Sysadmins LV Class 1 TEST ECDSA CA-2, OU=Information Systems, OU=Division of IT (DoIT), O=Sysadmins LV,
 L=Jurmala, S=Rigas raj., C=LV

Public Key Algorithm:
    Algorithm ObjectId: ECC (1.2.840.10045.2.1)
    Algorithm Parameters:
    06 05 2b 81 04 00 22
        ECDSA_P384 (1.3.132.0.34)
Public Key Length: 384 bits
Public Key: UnusedBits=0
    0000        04 99 84 6f 9f 60 7c d7  20 ea 1d 62 66 71 9f 21
    0010        d5 42 f5 1d a7 d1 99 3a  74 b3 63 f3 43 b9 02 27
    0020        ee 8d 0f 1a 1d ab 82 62  5d 55 d2 4d 88 3a 6f 6a
    0030        81 69 30 5b 4f da e6 5c  5d 4c 08 33 20 4d 66 32
    0040        0a d6 bb 2d 35 23 da f8  83 2f e3 e6 30 17 9a 8c
    0050        87 8b ad 8e 27 88 55 ae  05 cd 1b ea 93 e3 35 b7
    0060        36
Request attributes (Count=1):
  Attribute[0], Length=13 (0d):
    OS Version (OID=1.3.6.1.4.1.311.13.2.3): 6.3.9600.2.

Request extensions (Count=6):
  OID=Key Usage (2.5.29.15), Critical=True, Length=4 (04):
    Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)

  OID=CA Version (1.3.6.1.4.1.311.21.1), Critical=False, Length=3 (03):
    V0.0

  OID=Subject Key Identifier (2.5.29.14), Critical=False, Length=22 (16):
    eb e6 9b 2e c6 33 56 73 81 8e d5 7d f2 2a ec 7c 8e 12 25 11

  OID=Certificate Policies (2.5.29.32), Critical=False, Length=61 (3d):
    [1]Certificate Policy:
         Policy Identifier=1.3.6.1.4.1.34658.1.2
         [1,1]Policy Qualifier Info:
              Policy Qualifier Id=CPS
              Qualifier:
                   http://www.sysadmins.lv/cps/

  OID=Certificate Template Name (1.3.6.1.4.1.311.20.2), Critical=False, Length=12 (0c):
    SubCA

  OID=Basic Constraints (2.5.29.19), Critical=True, Length=8 (08):
    Subject Type=CA
    Path Length Constraint=0

Signature Algorithm:
    Algorithm ObjectId: 1.2.840.10045.4.3.3 (sha384ECDSA)
Signature: Unused bits=0
    0000        09 55 97 4e 9a fa 65 72  8f 7e 3a 80 8c 1b ae 72
    0010        88 3d d1 c1 36 df 84 4b  0e 59 97 80 b0 46 f1 fe
    0020        14 29 b5 30 ea 07 f7 81  1a 95 85 56 0f a4 97 a7
    0030        00 31 02 87 bd 89 01 19  15 3f a9 06 29 e7 a8 e3
    0040        f4 f1 ab f4 94 bb 85 7b  75 7a fa 8f 52 d8 66 55
    0050        3d 77 c7 bb 48 a8 fe e9  58 74 24 6b ab 6d 88 19
    0060        bf d4 7a 30 02 65 30
Signature matches Public Key: True

PS C:\>

The code now easily validates CNG signatures.

Last word

Actually PKCS#7 and CNG extensions took the most time in this PSPKI module release development. However, I’m really glad that PowerShell got enough powerful PKI support. And it is not the limit! It is sad that Microsoft invests too little in PowerShell and PKI integration. On the other hand, I wouldn’t wrote PSPKI module which was started as an ad-hoc solution for my internal needs and evolved into a sort of decent project. In next post I will cover some new features in X.509CRL and, maybe, something else.

Comments:

Alexander
Alexander 21.09.2016 16:31 (GMT+2)

Hello Vadims.

Do you aware how to read NameValue pair (1.3.6.1.4.1.311.13.2.1)?

I see CX509NameValuePair interface is there, i can create new instance via Initialize(), but i can't understand gow to read this attribute value(s) from CSR. There is no method InitializeDecode like ClientInfo, for example, has.

Also i see that object based on CX509CertificateRequestPkcs10 has property "Signature", but when code is executed and PKCS10 decoded, this property = null.

At the same time certutil -dump showing me all properties without any issues.

Am i doing something wrong, or some classes are not compltely implemented there? I im lost here...

Vadims Podāns
Vadims Podāns 21.09.2016 18:37 (GMT+2)

Please, ask the question at the project web site: https://pspki.codeplex.com/discussions

and, please, provide more information about your issue.

Captcha