/* * crypto/crypto.h — TLS 1.3 (mbedTLS) and the media AEAD (libsodium). * * Design: docs/security.md. Control channel = TLS 1.3. Media = keys exported from the TLS * session (RFC 5705 / 8446) + per-frame ChaCha20-Poly1305 with a counter nonce and a * sliding-window replay filter. Encryption is MANDATORY — never add a plaintext path. */ #ifndef VOICECAT_CRYPTO_CRYPTO_H #define VOICECAT_CRYPTO_CRYPTO_H #include #include #include #ifdef VOICECAT_HAS_NET #include #include #include #include // libsodium #include // mbedTLS #include #include #include #include #include #include namespace voicecat::crypto { // ── Server identity ──────────────────────────────────────────────────────────── // Long-lived Ed25519 key identifying this server instance across cert rotations. // Fingerprint is the 32-byte SHA-256 of the public key. struct ServerIdentity { std::array pk{}; std::array sk{}; std::array fingerprint{}; static ServerIdentity generate(); static ServerIdentity load(const std::filesystem::path& path); void save(const std::filesystem::path& path) const; std::string fingerprint_hex() const; }; // ── Server TLS certificate ───────────────────────────────────────────────────── // Self-signed ECDSA-P256 cert for TLS. On first run, generated and persisted. struct ServerCert { std::string pem_cert; std::string pem_key; static ServerCert generate(const std::string& server_name); static ServerCert load(const std::filesystem::path& cert_path, const std::filesystem::path& key_path); void save(const std::filesystem::path& cert_path, const std::filesystem::path& key_path) const; }; // ── TLS 1.3 context ─────────────────────────────────────────────────────────── // Wraps mbedTLS for one TLS connection (server or client side). // All public methods except close() must be called from a single thread at a time. class TlsContext { public: enum class Role { Server, Client }; // server_cert: required for server role; nullptr for client // pinned_fp: 32-byte Ed25519 fingerprint to accept (client TOFU); nullptr = any TlsContext(Role role, const ServerCert* server_cert, const std::array* pinned_fp = nullptr); ~TlsContext(); TlsContext(const TlsContext&) = delete; TlsContext& operator=(const TlsContext&) = delete; // Perform the TLS handshake over an already-connected BSD socket fd. // Blocking — run from a WorkerPool thread. // Returns true on success; error contains a diagnostic string on failure. bool handshake(int socket_fd, std::string& error); // Read/write post-handshake (single-threaded). Returns bytes transferred, or <0 on error. int read(uint8_t* buf, size_t len); int write(const uint8_t* buf, size_t len); // RFC 5705 / RFC 8446 §7.5 exporter — derive media keys after handshake. bool export_keying_material(const char* label, const uint8_t* ctx, size_t ctx_len, uint8_t* out, size_t out_len); // Whether the handshake completed. bool ready() const { return ready_; } // Underlying socket fd (valid after handshake). For select() in the caller. int native_fd() const { return net_ctx_.fd; } // Set per-read timeout (ms, 0 = blocking). Affects post-handshake reads. void set_read_timeout(uint32_t ms); // True when the given return value from read() indicates a read timeout. static bool is_timeout_error(int rc); private: Role role_; const std::array* pinned_fp_; bool ready_{false}; mbedtls_entropy_context entropy_{}; mbedtls_ctr_drbg_context ctr_drbg_{}; mbedtls_ssl_context ssl_{}; mbedtls_ssl_config conf_{}; mbedtls_x509_crt srvcert_{}; mbedtls_pk_context pkey_{}; mbedtls_net_context net_ctx_{}; }; // ── Media AEAD (M2) ─────────────────────────────────────────────────────────── // Per-frame voice encryption. Abstracted so the backend is swappable. class MediaCrypto { public: virtual ~MediaCrypto() = default; virtual long seal(const uint8_t* plain, size_t len, const uint8_t* aad, size_t aad_len, uint8_t* out, size_t out_cap) = 0; virtual long open(const uint8_t* sealed, size_t len, const uint8_t* aad, size_t aad_len, uint8_t* out, size_t out_cap) = 0; }; } // namespace voicecat::crypto #else // !VOICECAT_HAS_NET — skeleton stubs namespace voicecat::crypto { class MediaCrypto { public: virtual ~MediaCrypto() = default; virtual long seal(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) = 0; virtual long open(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) = 0; }; } // namespace voicecat::crypto #endif // VOICECAT_HAS_NET #endif // VOICECAT_CRYPTO_CRYPTO_H