How can I verify/read an IIS7 SSL renewal CSR with OpenSSL

I have the privilege of handling ~5 SSL CSRs per week, checking their validity before passing them off to our CA for action. I use OpenSSL on an Ubuntu machine to check that they are valid, testing things like the correct OU name, a sensible CN, key size >=2048 bits and so on, as our requests are sometimes incorrect.

The other day I received a renewal request from an IIS7 machine. I can't figure out how to read this at all, using OpenSSL. It is valid, as my CA has accepted it ...

'file(1)' says it is an "RFC1421 Security Certificate Signing Request text", which is what it says for ~50% of the CSRs I have here (the rest are "PEM certificate request").

$ head iis7rcsr
-----BEGIN NEW CERTIFICATE REQUEST-----
MIIQsQYJKoZIhvcNAQcCoIIQojCCEJ4CAQExCzAJBgUrDgMCGgUAMIIJegYJKoZI
hvcNAQcBoIIJawSCCWcwggljMIIIzAIBADCB2zELMAkGA1UEBhMCTloxDTALBgNV
BBEMBDkwNTQxDjAMBgNVBAgMBU90YWdvMRAwDgYDVQQHDAdEdW5lZGluMRwwGgYD
...
...

openssl req, which reads CSRs (PKCS#10) fails to comprehend it ...

$ openssl req -in iis7rcsr -text
unable to load X509 request
5156:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1316:
5156:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:380:Type=X509_REQ_INFO
5156:error:0D08303A:asn1 encoding routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1 error:tasn_dec.c:748:Field=req_info, Type=X509_REQ
5156:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:pem_oth.c:83:

This article from Andreas Klein on the MSDN blogs suggests that IIS7 renewal CSRs are a PKCS#7 container, with a CSR and a signature based on the current certificate ... but I still can't read it.

$ openssl pkcs7 -in iis7rcsr -text
unable to load PKCS7 object
6581:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:650:Expecting: PKCS7

I can use 'openssl base64' to decode the file, and in the resulting binary file I can see strings that look like the CSR, and some CA references that must have come from a signature based on the old certificate. So the idea of container(CSR,signature) sounds plausible.

But I still cannot find a way of reading the CSR that's in there! I have tried a lot of things, I won't list the details here, but here are the high points of variations I've tried : pkcs12 pkcs7 PEM DER req x509 verify ...

I cannot post the CSR itself here unfortunately. Can anyone help me figure out a way of reading/verifying this file?


Solution 1:

The structure of this IIS7 renewal request is actually quite elegant. It seems to start from the premise that because this is a request to renew a current certificate, it needs to prove that the request is coming from the correct host -- i.e. the host that is actually using the current certificate & ∴ owns the associated private key. In the Internet world, you prove that you are allowed to request renewals for a certificate by authenticating to your CA as the original user, rather than creating a signed CSR.

To prove the right to issue a renewal request, IIS7 creates a normal CSR (PKCS#10 object), and then signs it, and provides the cert of the key that signed it.

  • IIS7 renewal CSR
    • PKCS#7 Data
      • PKCS#10 Data (the ordinary CSR)
    • Normal server certificate
    • Issuing CA data
    • RSA signature (I assume)

Use openssl asn1parse -in iis7rcsr -i to see the structure of the file, and compare this to normal CSRs. You should see an OCTET STRING near the beginning, in an object labelled ":pkcs7-data", which is what you need to extract to get the CSR.

$ openssl asn1parse -in iis7rcsr -i
0:d=0  hl=4 l=4273 cons: SEQUENCE          
4:d=1  hl=2 l=   9 prim:  OBJECT            :pkcs7-signedData
15:d=1  hl=4 l=4258 cons:  cont [ 0 ]        
19:d=2  hl=4 l=4254 cons:   SEQUENCE          
23:d=3  hl=2 l=   1 prim:    INTEGER           :01
26:d=3  hl=2 l=  11 cons:    SET               
28:d=4  hl=2 l=   9 cons:     SEQUENCE          
30:d=5  hl=2 l=   5 prim:      OBJECT            :sha1
37:d=5  hl=2 l=   0 prim:      NULL              
39:d=3  hl=4 l=2426 cons:    SEQUENCE          
43:d=4  hl=2 l=   9 prim:     OBJECT            :pkcs7-data
54:d=4  hl=4 l=2411 cons:     cont [ 0 ]        
58:d=5  hl=4 l=2407 prim:      OCTET STRING      [HEX DUMP]:3082096330820...

In order to get the actual PKCS#10 CSR out of here, we need that offset number, "58" in this example. Then we can use that offset to extract the binary version of that object :-

$ openssl asn1parse -in iis7rcsr -strparse 58 -out thecsr -noout

Next we can read that output file 'thecsr' with openssl req, remembering to specify the input format DER.

$ openssl req -in thecsr -inform DER -text -noout
Certificate Request:
Data:
    Version: 0 (0x0)
    Subject: (normal CSR Subject: line, censored)
    Subject Public Key Info:
        Public Key Algorithm: rsaEncryption
...

I can wrap all this up into one command-line with no temporary files (but sadly 2 reads of the original cert), as long as I can use Linux's /proc/self/fd/ to fool openssl (it will do native tricks with file descriptors for password handling, but not normal output).

$ openssl asn1parse -in iis7rcsr -strparse $(openssl asn1parse -in iis7rcsr | grep -A2 ':pkcs7-data'|tail -1|cut -d: -f1) -out /dev/stdout -noout | openssl req -inform DER -noout -text

Certificate Request:
Data:
    Version: 0 (0x0)
    Subject: (Subject: line censored again)
    Subject Public Key Info:
        Public Key Algorithm: rsaEncryption
        RSA Public Key: (1024 bit)
            Modulus (1024 bit):
...

This long command line is directly equivalent to the simple openssl req -in non-iis7rcsr -noout -text that I normally use :-)

Solution 2:

Thanks Jim for this excellent information that was greatly helpful, I had the exact same problem trying to renew a w2008/IIS7 server certificate.

I would add just one thing. You may be able to extract the CSR in P10 format directly with the following command: certutil -split iis7rcsr (iis7rcsr being the .csr you get via IIS manager). The csr will then be extracted in a file named blob0_1.p10 It's in binary format (DER), you may have to encode it in base64 with the following command: certutil -encode blob0_1.p10 finalcsr.csr

There is a last problem, though. I then discovered, dumping the .csr content with openssl that the renewal process automatically forced the use of 1024 bits key (even though the original private key created on the server for the server certificate was 2048 bits length). It seems then you can't force the use of 2048 bits keys using the renewal process of IIS7.

The only good option seems to be to create a new key/certificate and not using the renewal process.