Código Python para genera la clave pública, la clave privada y el CSR para su firma por parte de una autoridad de certificación

Este código en Python se escribió en 2015 para generar con facilidad un CSR que se pudiese enviar a una autoridad de certificación para que nos proporcionase un certificado X.509 firmado que pudiésemos instalar en el servidor web.

El procedimiento normal es utiizar OpenSSL y así lo estuve haciendo durante décadas, pero, invirtiendo unos minutos para pasar el proceso a Python, el resultado es más sencillo, más configurable y más automatizado.

Hoy en día utilizo Let's Encrypt y este proceso ya no es necesario, pero lo documento aquí por si le resulta de interés a alguien.

z-generar_csr-20221222.py (Código fuente)

#!/usr/bin/env python3

# (c) 2015 jcea@jcea.es - http://www.jcea.es/
# Código liberado como Dominio Público.
# Haz con él lo que quieras.

import sys, time, os, argparse

parser = argparse.ArgumentParser(description='''Genera claves pública, privada
y CSR para su firma por parte de una autoridad de certificación.''')
parser.add_argument('dominio',
                    help='Dominio para el que generamos las claves y el CSR')
args = parser.parse_args()
dominio = args.dominio  # Esto es UNICODE


# Ver https://cryptography.readthedocs.org/en/latest/x509/tutorial/
# Ver https://cryptography.io/en/latest/x509/reference/#cryptography.x509.CertificateSigningRequestBuilder

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.x509.oid import NameOID

private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )

builder = x509.CertificateSigningRequestBuilder()

builder = builder.subject_name(x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, dominio),
    ]))
builder = builder.add_extension(
        x509.BasicConstraints(ca=False, path_length=None), critical=True,
    )
csr = builder.sign(
        private_key, hashes.SHA256(), default_backend()
    )

dominio = "%s-%d" %(dominio, time.time())

with open(dominio+'.key', 'wb') as f :
    os.fchmod(f.fileno(), 0o400)
    f.write(private_key.private_bytes(
           encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption(),
        ))

with open(dominio+'.csr', 'wb') as f :
    f.write(csr.public_bytes(serialization.Encoding.PEM))

El código no tiene ninguna complejidad algorítmica y creo que se entiende con suma facilidad.

Como veis en las líneas 4-5, el código se libera por completo. Puedes hacer con él lo que quieras.

En las líneas 9-14 se lee el parámetro dominio desde la línea de comandos, gestionando errores si fuera el caso.

En las líneas 20-25 podemos ver que usamos la biblioteca criptográfica cryptography de Python, una biblioteca externa pero bastante estándar para estos menesteres.

En las líneas 27-31 se genera una clave RSA privada, secreta, de 2048 bits. El número de bits no es configurable por línea de comando, pero se puede modificar con facilidad en el código.

En las líneas 33-40 se va montando el CSR, incluyendo el dominio y restricciones básicas, y se firma con la clave privada.

La clave privada se va a grabar en disco (con permisos restrictivos para que el fichero no se pueda leer por terceros) en las líneas 47-53. El CSR se graba, de forma más simple, en las líneas 55-56.

Ya solo quedaría enviar el CSR a la autoridad de certificación, que nos devolverá una clave pública firmada por ella. Se mete en el servidor web y ya está.