diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-09 04:11:00 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-09 04:11:00 +0200 |
| commit | 6abb68ae95f6c4da4a66453398006183a73db9d9 (patch) | |
| tree | 534b1f1ec7c049e5064d26fc3875e143e69bd900 /packages/meshbay-node/src/meshbay_node/transport/tls_cert.py | |
| parent | 46b6353ebfb57c7fea481a9aac919b7977e3d186 (diff) | |
| download | meshbay-6abb68ae95f6c4da4a66453398006183a73db9d9.tar.gz | |
feat(node): add TCP+TLS chunk server and client (MNP v1)
Self-signed TLS cert (RSA-2048, TLS 1.3 min). Server: JWT offline
verify, index sync, file_request → encrypt+sign chunk pipeline.
Client: handshake, fetch_index, fetch_chunk with Ed25519 verify +
blake3 hash check + GEK decrypt. Integration test: 2MB file served
in 2 chunks, reassembled == original. 3/3 tests. Full suite: 29/29.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport/tls_cert.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/tls_cert.py | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py b/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py new file mode 100644 index 0000000..1354ac9 --- /dev/null +++ b/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py @@ -0,0 +1,95 @@ +""" +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 |