microstack/tools/init/init/tls.py

85 lines
2.9 KiB
Python

#!/usr/bin/env python3
from init.shell import check
from datetime import datetime
from dateutil.relativedelta import relativedelta
import ipaddress
import socket
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from init import shell
def generate_self_signed(cert_path, key_path, ip=None, fingerprint_config=None):
"""Generate a self-signed certificate with associated keys.
The certificate will have a fake CNAME and subjAltName since
the expectation is that this certificate will only be used by
clients that know its fingerprint and will not use a validation
via a CA certificate and hostname. This approach is similar to
Certificate Pinning, however, here a certificate is not embedded
into the application but is generated on initialization at one
node and its fingerprint is copied in a token to another node
via a secure channel.
https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning
"""
# Do not generate a new certificate and key if there is already an existing
# pair. TODO: improve this check and allow renewal.
if cert_path.exists() and key_path.exists():
return
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
cn = socket.gethostname()
common_name = x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, cn)
])
if ip:
san = x509.SubjectAlternativeName(
[x509.DNSName(cn), x509.IPAddress(ipaddress.ip_address(ip))]
)
else:
san = x509.SubjectAlternativeName([x509.DNSName(cn)])
basic_contraints = x509.BasicConstraints(ca=True, path_length=0)
now = datetime.utcnow()
cert = (
x509.CertificateBuilder()
.subject_name(common_name)
.issuer_name(common_name)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + relativedelta(years=10))
.add_extension(basic_contraints, False)
.add_extension(san, False)
.sign(key, hashes.SHA256(), default_backend())
)
cert_fprint = cert.fingerprint(hashes.SHA256()).hex()
if fingerprint_config:
shell.config_set(**{fingerprint_config: cert_fprint})
serialized_cert = cert.public_bytes(encoding=serialization.Encoding.PEM)
serialized_key = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
cert_path.write_bytes(serialized_cert)
key_path.write_bytes(serialized_key)
check('chmod', '644', str(cert_path))
check('chmod', '600', str(key_path))