|
#!/usr/bin/env python3
# (c) 2018-2023 jcea@jcea.es - https://www.jcea.es/
# Código liberado como Dominio Público.
# Haz con él lo que quieras.
import os
import sys
import argparse
import datetime
import ipaddress
# 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('--CA', required=True, nargs=1,
help='CA que se usará para firmar')
parser.add_argument('--days', default=365, metavar='DÍAS', type=int,
help='Expiración en días (365 por defecto)')
parser.add_argument('NAME', help='Nombre del certificado y el CN')
parser.add_argument('SAN', nargs='+', help='Nombres de dominio e IPs')
args = parser.parse_args()
name = args.NAME # Esto es UNICODE
san = args.SAN # Esto es UNICODE
CA = args.CA[0]
print(f'Generamos un certificado que expira en {args.days} días',
file=sys.stderr)
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
with open(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 "{CA}" '
'no parece ser una entidad de certificación')
with open(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, name))
subject = x509.Name(subject)
SubjectAlternativeName = []
for element in san:
try:
ip = ipaddress.ip_address(element)
element = x509.IPAddress(ip)
except ValueError:
element = x509.DNSName(element)
SubjectAlternativeName.append(element)
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)
builder = builder.add_extension(x509.ExtendedKeyUsage([
x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH
]),
critical=True)
cert = builder.sign(CA_key, hashes.SHA256(), backend=default_backend())
with open(name + '.cert', 'wb') as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
with open(name + '.key', 'wb',
opener=lambda x, y: os.open(x, y, mode=0o600)) as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption())
)
|