I would like to use python to create a CA certificate, and client certificates that I sign with it. I will be using these with OpenVPN. After several days of research, and trial and error, this is what I've come up with:
#!/usr/bin/env python
import os
import sys
import random
from OpenSSL import crypto
###########
# CA Cert #
###########
ca_key = crypto.PKey()
ca_key.generate_key(crypto.TYPE_RSA, 2048)
ca_cert = crypto.X509()
ca_cert.set_version(2)
ca_cert.set_serial_number(random.randint(50000000,100000000))
ca_subj = ca_cert.get_subject()
ca_subj.commonName = "My CA"
ca_cert.add_extensions([
crypto.X509Extension("subjectKeyIdentifier", False, "hash", subject=ca_cert),
])
ca_cert.add_extensions([
crypto.X509Extension("authorityKeyIdentifier", False, "keyid:always", issuer=ca_cert),
])
ca_cert.add_extensions([
crypto.X509Extension("basicConstraints", False, "CA:TRUE"),
crypto.X509Extension("keyUsage", False, "keyCertSign, cRLSign"),
])
ca_cert.set_issuer(ca_subj)
ca_cert.set_pubkey(ca_key)
ca_cert.sign(ca_key, 'sha256')
ca_cert.gmtime_adj_notBefore(0)
ca_cert.gmtime_adj_notAfter(10*365*24*60*60)
# Save certificate
with open("ca.crt", "wt") as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert))
# Save private key
with open("ca.key", "wt") as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, ca_key))
###############
# Client Cert #
###############
client_key = crypto.PKey()
client_key.generate_key(crypto.TYPE_RSA, 2048)
client_cert = crypto.X509()
client_cert.set_version(2)
client_cert.set_serial_number(random.randint(50000000,100000000))
client_subj = client_cert.get_subject()
client_subj.commonName = "Client"
client_cert.add_extensions([
crypto.X509Extension("basicConstraints", False, "CA:FALSE"),
crypto.X509Extension("subjectKeyIdentifier", False, "hash", subject=client_cert),
])
client_cert.add_extensions([
crypto.X509Extension("authorityKeyIdentifier", False, "keyid:always", issuer=ca_cert),
crypto.X509Extension("extendedKeyUsage", False, "clientAuth"),
crypto.X509Extension("keyUsage", False, "digitalSignature"),
])
client_cert.set_issuer(ca_subj)
client_cert.set_pubkey(client_key)
client_cert.sign(ca_key, 'sha256')
client_cert.gmtime_adj_notBefore(0)
client_cert.gmtime_adj_notAfter(10*365*24*60*60)
# Save certificate
with open("client.crt", "wt") as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, client_cert))
# Save private key
with open("client.key", "wt") as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, client_key))
This generates the certificates and private keys, but unfortunately, I must be doing something wrong because they do not verify:
$ openssl verify -verbose -CAfile ca.crt client.crt
client.crt: CN = Client
error 7 at 0 depth lookup:certificate signature failure
139823049836448:error:04091068:rsa routines:INT_RSA_VERIFY:bad signature:rsa_sign.c:293:
139823049836448:error:0D0C5006:asn1 encoding routines:ASN1_item_verify:EVP lib:a_verify.c:241:
What am I doing wrong?
To install certifi Python on Microsoft Windows: Type cmd in the search bar and hit Enter to open the command line. Type python3 -m pip install certifi in the command line and hit Enter again. This installs certifi for your default Python installation.
You are setting notBefore and notAfter after you already signed the certificate and thus change the already signed certificate - which makes the signature not match the certificate anymore:
client_cert.sign(ca_key, 'sha256')
client_cert.gmtime_adj_notBefore(0)
client_cert.gmtime_adj_notAfter(10*365*24*60*60)
# Save certificate
...
If you move the signing part to be the last step, i.e. directly before writing the file, then the verification will be successful:
client_cert.gmtime_adj_notBefore(0)
client_cert.gmtime_adj_notAfter(10*365*24*60*60)
client_cert.sign(ca_key, 'sha256')
# Save certificate
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With