Generate valid signed X509 client certificates with pyopenssl

I spent a while today trying to figure out why a certificate deemed valid by the openssl verify command was invalid on my Windows machine.

Errors such as:

  • "This certificate has an invalid digital signature."
  • "The integrity of this certificate cannot be guaranteed. The certificate may have been corrupted or may have been altered"

I used the pyopenssl library to generate my CA cert as well as the client certificate. You might already have an intermediate certificate and won't need to generate the CA cert. I'll add a link to working code at the end of this post. Feel free to scroll down if that's what you're interested in.

There are a couple of reasons why you might be seeing these errors:
1. Public Key Length is <1024 bits. See this stackoverflow post and this blog post for more details
2. Your Certificate Revocation List(CRL) Endpoints have been misconfigured or aren't reachable
3. You used the pyopenssl library and added the "subjectKeyIdentifier" X509Extension before setting the public key to use.

I'll let you guess which one I hit :)

Here's what happened. My signed client cert generation script looked something like this:

client_cert.add_extensions([  
        crypto.X509Extension(b"authorityKeyIdentifier", False, b"keyid", issuer=root_ca_cert),
    ])

client_cert.add_extensions([  
        crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=client_cert),
    ])

client_cert.set_issuer(root_ca_subj)  
client_cert.set_pubkey(client_key)  

This took me a while to identify, but the client certificate generated had the extensions looking this:

X509v3 Authority Key Identifier:  
    keyid:DA:39:A3:EE:5E:6B:4B:0D:32:55:BF:EF:95:60:18:90:AF:D8:07:09
X509v3 Subject Key Identifier:  
    DA:39:A3:EE:5E:6B:4B:0D:32:55:BF:EF:95:60:18:90:AF:D8:07:09

Notice how the Subject Key and Authority Key are identical? The only time that should happen is in the case of a self signed cert.

Digging in further this key was actually the Subject Key of my CA certificate (The one I was trying to use to sign this client cert).

There's more to it, but at a high level if a CA (with Subject Key ID = CA_ski) is signing a client cert, the Client cert should have:

Subject Key ID = [something unique]

Authority Key ID = CA_ski

Alright. So how is this subject key id generated? Let's go look at the (theoretical) source of truth :). From RFC 5280:

For end entity certificates, subject key identifiers SHOULD be derived from the public key  

And that's when I found my bug. If you look at my code, I'm setting the public key after adding the subjectKeyIdentifier extension. As a result the library seems to default to using the CA's key to generate the subjectKeyIdentifier.

The fix is to set the public key on the client cert before adding the subjectKeyIdentifier. So the code should look more like this:

client_cert.set_issuer(root_ca_subj)  
client_cert.set_pubkey(client_key)

client_cert.add_extensions([  
        crypto.X509Extension(b"authorityKeyIdentifier", False, b"keyid", issuer=root_ca_cert),
    ])

client_cert.add_extensions([  
        crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=client_cert),
    ])

The client cert generated by this will have the right authorityKeyIdentifier set and correctly generate a unique subjectKeyIdentifier based on the client public key!

Feel free to reach out to me @rohchak if you have any questions!

Here's the code:

CA Cert gen:


Signed Client Cert Gen: