aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-09 04:11:00 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-09 04:11:00 +0200
commit6abb68ae95f6c4da4a66453398006183a73db9d9 (patch)
tree534b1f1ec7c049e5064d26fc3875e143e69bd900 /packages/meshbay-node/src/meshbay_node/transport/tls_cert.py
parent46b6353ebfb57c7fea481a9aac919b7977e3d186 (diff)
downloadmeshbay-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.py95
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