""" Self-signed TLS certificate generation for the node. The cert is used for transport confidentiality only. Node identity is verified via Ed25519 PK (from hub), not TLS cert chain. Clients connect with ssl.CERT_NONE + verify Ed25519 at the MNP handshake layer. Certificate is generated once and cached at ~/.config/meshbay/node_tls.pem/.key. """ import logging import os from pathlib import Path import ssl import datetime import ipaddress from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID log = logging.getLogger(__name__) DEFAULT_CERT = Path.home() / ".config" / "meshbay" / "node_tls.crt" DEFAULT_KEY = Path.home() / ".config" / "meshbay" / "node_tls.key" def generate_self_signed_cert( cert_path: Path = DEFAULT_CERT, key_path: Path = DEFAULT_KEY, ) -> tuple[Path, Path]: """Generate a self-signed RSA-2048 TLS cert valid for 10 years.""" cert_path.parent.mkdir(parents=True, exist_ok=True) rsa_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "meshbay-node"), ]) cert = ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(rsa_key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.now(datetime.timezone.utc)) .not_valid_after(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=3650)) .add_extension( x509.SubjectAlternativeName([ x509.DNSName("localhost"), x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")), ]), critical=False, ) .sign(rsa_key, hashes.SHA256()) ) cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM)) key_path.write_bytes(rsa_key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption(), )) cert_path.chmod(0o644) key_path.chmod(0o600) log.info("TLS cert generated: %s", cert_path) return cert_path, key_path def server_ssl_context( cert_path: Path = DEFAULT_CERT, key_path: Path = DEFAULT_KEY, ) -> ssl.SSLContext: """SSL context for the node's TCP server.""" if not cert_path.exists() or not key_path.exists(): generate_self_signed_cert(cert_path, key_path) ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ctx.load_cert_chain(certfile=cert_path, keyfile=key_path) ctx.minimum_version = ssl.TLSVersion.TLSv1_3 return ctx def client_ssl_context() -> ssl.SSLContext: """ SSL context for clients connecting to a node. CERT_NONE because we verify node identity via Ed25519 PK at the MNP layer. """ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE ctx.minimum_version = ssl.TLSVersion.TLSv1_3 return ctx