X.509 certificates are used to authenticate clients and servers. The most common use case is for web servers using HTTPS.
When obtaining a certificate from a certificate authority (CA), the usual flow is:
If you want to obtain a certificate from a typical commercial CA, here’s how. First, you’ll need to generate a private key, we’ll generate an RSA key (these are the most common types of keys on the web right now):
>>> from cryptography.hazmat.backends import default_backend
>>> from cryptography.hazmat.primitives import serialization
>>> from cryptography.hazmat.primitives.asymmetric import rsa
>>> # Generate our key
>>> key = rsa.generate_private_key(
... public_exponent=65537,
... key_size=2048,
... backend=default_backend()
... )
>>> # Write our key to disk for safe keeping
>>> with open("path/to/store/key.pem", "wb") as f:
... f.write(key.private_bytes(
... encoding=serialization.Encoding.PEM,
... format=serialization.PrivateFormat.TraditionalOpenSSL,
... encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"),
... ))
If you’ve already generated a key you can load it with load_pem_private_key().
Next we need to generate a certificate signing request. A typical CSR contains a few details:
>>> from cryptography import x509
>>> from cryptography.x509.oid import NameOID
>>> from cryptography.hazmat.primitives import hashes
>>> # Generate a CSR
>>> csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
... # Provide various details about who we are.
... x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"),
... x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
... x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"),
... x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"),
... ])).add_extension(
... x509.SubjectAlternativeName([
... # Describe what sites we want this certificate for.
... x509.DNSName(u"mysite.com"),
... x509.DNSName(u"www.mysite.com"),
... x509.DNSName(u"subdomain.mysite.com"),
... ]),
... critical=False,
... # Sign the CSR with our private key.
... ).sign(key, hashes.SHA256(), default_backend())
>>> # Write our CSR out to disk.
>>> with open("path/to/csr.pem", "wb") as f:
... f.write(csr.public_bytes(serialization.Encoding.PEM))
Now we can give our CSR to a CA, who will give a certificate to us in return.
While most of the time you want a certificate that has been signed by someone else (i.e. a certificate authority), so that trust is established, sometimes you want to create a self-signed certificate. Self-signed certificates are not issued by a certificate authority, but instead they are signed by the private key corresponding to the public key they embed.
This means that other people don’t trust these certificates, but it also means they can be issued very easily. In general the only use case for a self-signed certificate is local testing, where you don’t need anyone else to trust your certificate.
Like generating a CSR, we start with creating a new private key:
>>> # Generate our key
>>> key = rsa.generate_private_key(
... public_exponent=65537,
... key_size=2048,
... backend=default_backend()
... )
>>> # Write our key to disk for safe keeping
>>> with open("path/to/store/key.pem", "wb") as f:
... f.write(key.private_bytes(
... encoding=serialization.Encoding.PEM,
... format=serialization.PrivateFormat.TraditionalOpenSSL,
... encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"),
... ))
Then we generate the certificate itself:
>>> # Various details about who we are. For a self-signed certificate the
>>> # subject and issuer are always the same.
>>> subject = issuer = x509.Name([
... x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"),
... x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
... x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"),
... x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"),
... ])
>>> cert = x509.CertificateBuilder().subject_name(
... subject
... ).issuer_name(
... issuer
... ).public_key(
... key.public_key()
... ).serial_number(
... x509.random_serial_number()
... ).not_valid_before(
... datetime.datetime.utcnow()
... ).not_valid_after(
... # Our certificate will be valid for 10 days
... datetime.datetime.utcnow() + datetime.timedelta(days=10)
... ).add_extension(
... x509.SubjectAlternativeName([x509.DNSName(u"localhost")]),
... critical=False,
... # Sign our certificate with our private key
... ).sign(key, hashes.SHA256(), default_backend())
>>> # Write our certificate out to disk.
>>> with open("path/to/certificate.pem", "wb") as f:
... f.write(cert.public_bytes(serialization.Encoding.PEM))
And now we have a private key and certificate that can be used for local testing.
Certificates and certificate signing requests can be issued with multiple key types. You can determine what the key type is by using isinstance checks:
>>> public_key = cert.public_key()
>>> if isinstance(public_key, rsa.RSAPublicKey):
... # Do something RSA specific
... elif isinstance(public_key, ec.EllipticCurvePublicKey):
... # Do something EC specific
... else:
... # Remember to handle this case