Friday, November 18, 2016

Inspecting SCEP enrollment traffic

SCEP is a protocol which facilitates client enrollment with a Certificate Authorities (CA), delivery and renewal of certificates and delegation of identity verification from a CA to a trusted Registration Authoritie (RA)

A project I'm working on requires me to generate a Certificate Signing Request (CSR) on behalf client which doesn't exist yet, and deliver of those requests to the CA via an RA that I'm building. I'll then set aside the certificate and keys for installation onto the client system when it becomes available.

It seemed like ripping apart a request from a real client, as delivered by a real RA would be a good place to start, so that's what I did. I set up a CA (R1), an RA (R2) and a client (R3), performed the enrollment and captured the traffic between the R2 and R1.

There's a nice diagram detailing how a client delivers its  to a CA on this Cisco page, so have a quick peek at the breakdown listed under Client Enrollment there.

A CSR delivered by an RA (rather than the client) is similarly encapsulated, except that both of the PKCS7 functions are performed by the RA (with the RA's private key), rather than the client.

Cisco's diagram looks fairly straightforward, but it took me a while to work out the handful of openssl incantations that follow.

The first step was to collect the data delivered over HTTP using a sniffer. The RA's HTTP GET request looks like this:

 /cgi-bin/pkiclient.exe?operation=PKIOperation&message=MIIJUwYJKoZI...etc...  

The value of the message field is a few KB of URL encoded text, so the first step was to snag that text, stick it in a file I called RA->CA.raw

The text is URL encoded, so a quick substitution restored the newlines, '+', '/' and '=' characters to their usual form, leaving behind a nice base64 encoded file with 64 characters on each line. I saved it as RA->CA.b64

Decode that base64 data to its binary form:

 chris$ base64 --decode 'RA->CA.b64' > 'RA->CA.bin'  

The result is a PKCS7 signedData blob in DER format. You can browse the ASN.1 info for the request I'm working on here. This blob will have the certificate of the signer included.

In a client -> CA request, the client uses his own certificate to sign re-enrollment requests, and uses a self-signed certificate to bootstrap the initial enrollment. In this case (RA -> CA), the data is signed by the RA, so it's the RA's certificate that's included. You can see it at offset 1116 in the ASN.1 parser  linked in the previous paragraph.

Let's extract the RA's certificate:

 chris$ openssl pkcs7 -inform der -print_certs -in 'RA->CA.bin' | openssl x509 -out RA_cert.pem  


And let's have a look at it:


 chris$ openssl x509 -noout -in RA_cert.pem -issuer -subject  
 issuer= /CN=CA  
 subject= /OU=ioscs RA/unstructuredName=R2/serialNumber=4279256517  

The message was signed by the RA and we have that certificate. The RA's certificate was signed by the CA and we don't have that one. We're going to need the CA's certificate in order to extract the payload of this PKCS7 signedData bundle, because without it the trust chain is incomplete so the message won't validate. The CA in this case is a Cisco IOS router. Export the certificate in PEM format:

 R1(config)# crypto pki export ROOT-CA pem terminal  

Save the resulting text in CA_cert.pem and validate/extract the payload from the PKCS7 signedData blob sent by the RA to the CA:

 chris$ openssl cms -inform der -verify -CAfile CA_cert.pem -in 'RA->CA.bin' -out 'RA->CA.payload1'  
 Verification successful  

Great. Now we've got the payload from the PKCS7 signedData blob. What's in there? Another PKCS7, of course. This time it's envelopedData (encrypted). ASN.1 decoder.

This data is encrypted with the CA's public key, so only the CA (holder of the corresponding private key) can read it. We need the CA's private key. Export it from the CA:

 R1(config)# crypto key export rsa CA pem terminal 3des mypassphrase  

Save the resulting text (just the bits between the begin/end private key markers) in CA_key.3des. Strip the passphrase for sanity's sake. This is a test environment after all!

 chris$ openssl rsa -in CA_key.3des -out CA_key.pem -passin pass:mypassphrase  

Now we can use the CA's private key to decode the message sent to the CA:

 chris$ openssl cms -decrypt -in 'RA->CA.payload1' -inkey CA_key.pem -inform der -out 'RA->CA.payload2'  

So... What's in this RA->CA.payload2 file? Back to the ASN.1 decoder... Holy cow, it's a Certificate Signing Request!

 chris$ openssl req -in 'RA->CA.payload2' -inform der -noout -subject  
 subject=/unstructuredName=R3/serialNumber=4279256517  

Next steps for me are to run all of these steps backward so that I can deliver such a package to a CA for signing, but without having a real client or RA system. I'll start by generating keys, then a CSR, then encrypting the CSR, signing it with an RA's certificate, etc...

4 comments:

  1. I found post very interesting and helpful for my work - I am not actually working with CISCO equipment but implementing a stand-alone SCEP client with OpenSSL/libcrypto and Curl/libcurl API-s.

    I could not find a continuation on your blog (preparation of the request to the CA using SCEP protocol) and I do not know whether you are still interested in this stuff, so I will not go into details but I can tell that apparently this cannot be done only with openssl command-line tool.

    With openssl tool I only got as far as creating CMS envelope data with encrypted CSR. To create CMS signed data - with signed attributes specific to the SCEP protocol (transactionId, senderNonce, etc.) - which is the actual SCEP request message to the CA, the only way I have found was to write a program using OpenSSL libcrypto API, only to add these signed attributes to the SCEP request message.

    ReplyDelete
    Replies
    1. Hi MW,

      Indeed, I am still interested in this stuff. I've now got a (mostly) python implementation that does everything I need. Specifically it:

      * Generates a keypair
      * Generates an RA-style CSR using the keypair
      * Submits the RA-style CSR to the CA (I grant this manually)
      * Retrieves the RA certificate
      * Creates end user key pairs
      * Creates end user CSRs
      * Signs end user CSRs with the RA cert
      * Submits the end user CSRs to the CA (these are granted automatically/immediately)
      * Writes out the resulting end user key pair and certificate

      Delete
    2. Hi Chris,
      I am interested in looking at your Python implementation of SCEP. Can you please share it. Thank you.

      Delete
  2. It is included in my habit that I often visit blogs in my free time, so after landing on your blog. I have thoroughly impressed with it and decided to take out some precious time to visit it again and again. Thanks. Traffic secrets

    ReplyDelete