"""Peer identity — stable peer_id and RSA keypair, persisted to disk.""" from __future__ import annotations import hashlib import json import os import stat from pathlib import Path _DEFAULT_IDENTITY_PATH = Path.home() / ".config" / "meshnet" / "identity.json" def _generate_keypair() -> tuple[bytes, bytes]: """Return (private_key_pem, public_key_pem) for a new RSA-2048 keypair.""" from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization key = rsa.generate_private_key(public_exponent=65537, key_size=2048) priv_pem = key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) pub_pem = key.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) return priv_pem, pub_pem def _peer_id_from_pubkey(pub_pem: bytes) -> str: return hashlib.sha256(pub_pem).hexdigest()[:16] def load_or_create_identity(path: Path | None = None) -> dict: """Return identity dict with peer_id, private_key_pem, public_key_pem. Creates and persists a new identity if none exists at path. """ p = path or _DEFAULT_IDENTITY_PATH if p.exists(): try: data = json.loads(p.read_text()) if "peer_id" in data and "public_key_pem" in data: return data except (json.JSONDecodeError, OSError): pass priv_pem, pub_pem = _generate_keypair() identity = { "peer_id": _peer_id_from_pubkey(pub_pem), "private_key_pem": priv_pem.decode(), "public_key_pem": pub_pem.decode(), } p.parent.mkdir(parents=True, exist_ok=True) p.write_text(json.dumps(identity, indent=2)) try: os.chmod(p, stat.S_IRUSR | stat.S_IWUSR) except OSError: pass return identity