z-generar_certificado_streaming-20180917.py (Código fuente)

#!/usr/bin/env python3

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

import os
import argparse
import datetime

# 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

parser = argparse.ArgumentParser(
            description='Genera un certificado con SubjectAlternativeName.')
parser.add_argument('--days', default=365, metavar='DÍAS', type=int,
                    help='Expiración en días (365 por defecto)')
parser.add_argument('--CA', default='webdav2.bt.jcea.es',
                    help='CA que se usará para firmar')
parser.add_argument('dominio', nargs='+', help='Dominio')
args = parser.parse_args()
dominios = args.dominio  # Esto es UNICODE

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

with open(args.CA + '.cert', 'rb') as f:
    CA_certificate = f.read()
CA_certificate = x509.load_pem_x509_certificate(CA_certificate,
                                                backend=default_backend())

try:
    is_CA = CA_certificate.extensions.get_extension_for_class(
                x509.BasicConstraints).value.ca
except x509.ExtensionNotFound:
    is_CA = False
if not is_CA:
    raise RuntimeError(f'El certificado "{args.CA}" '
                       'no parece ser una entidad de certificación')

with open(args.CA + '.key', 'rb') as f:
    CA_key = f.read()
CA_key = serialization.load_pem_private_key(CA_key, password=None,
                                            backend=default_backend())

subject = [i for i in CA_certificate.subject if i.oid != NameOID.COMMON_NAME]
subject.append(x509.NameAttribute(NameOID.COMMON_NAME, dominios[0]))
subject = x509.Name(subject)
SubjectAlternativeName = [x509.DNSName(dominio) for dominio in dominios]
SubjectAlternativeName = x509.SubjectAlternativeName(SubjectAlternativeName)

builder = x509.CertificateBuilder()
builder = builder.subject_name(subject)
builder = builder.issuer_name(CA_certificate.subject)
builder = builder.public_key(key.public_key())
builder = builder.serial_number(x509.random_serial_number())
builder = builder.not_valid_before(datetime.datetime.utcnow())
builder = (builder.not_valid_after(datetime.datetime.utcnow() +
           datetime.timedelta(days=args.days)))
builder = builder.add_extension(SubjectAlternativeName, critical=False)
builder = builder.add_extension(x509.BasicConstraints(ca=False,
                                                      path_length=None),
                                critical=True)

cert = builder.sign(CA_key, hashes.SHA256(), backend=default_backend())

with open(dominios[0] + '.cert', 'wb') as f:
    f.write(cert.public_bytes(serialization.Encoding.PEM))
with open(dominios[0] + '.key', 'wb',
          opener=lambda x, y: os.open(x, y, mode=0o400)) as f:
    f.write(key.private_bytes(
              encoding=serialization.Encoding.PEM,
              format=serialization.PrivateFormat.TraditionalOpenSSL,
              encryption_algorithm=serialization.NoEncryption())
            )