Cleanup crypto comments
This commit is contained in:
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
namespace voicecat::crypto {
|
namespace voicecat::crypto {
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
static void throw_if(int rc, const char* msg) {
|
static void throw_if(int rc, const char* msg) {
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
@@ -35,7 +34,7 @@ static std::string compute_hex_fingerprint(const uint8_t* data, size_t len) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ServerIdentity ────────────────────────────────────────────────────────────
|
// ServerIdentity
|
||||||
|
|
||||||
ServerIdentity ServerIdentity::generate() {
|
ServerIdentity ServerIdentity::generate() {
|
||||||
ServerIdentity id;
|
ServerIdentity id;
|
||||||
@@ -75,7 +74,7 @@ std::string ServerIdentity::fingerprint_hex() const {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ServerCert ────────────────────────────────────────────────────────────────
|
// ServerCert
|
||||||
|
|
||||||
ServerCert ServerCert::generate(const std::string& server_name) {
|
ServerCert ServerCert::generate(const std::string& server_name) {
|
||||||
mbedtls_entropy_context entropy;
|
mbedtls_entropy_context entropy;
|
||||||
@@ -171,7 +170,7 @@ void ServerCert::save(const std::filesystem::path& cert_path,
|
|||||||
write_file(key_path, pem_key);
|
write_file(key_path, pem_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── TlsContext ────────────────────────────────────────────────────────────────
|
// TlsContext
|
||||||
|
|
||||||
TlsContext::TlsContext(Role role, const ServerCert* server_cert,
|
TlsContext::TlsContext(Role role, const ServerCert* server_cert,
|
||||||
const std::array<uint8_t, 32>* pinned_fp)
|
const std::array<uint8_t, 32>* pinned_fp)
|
||||||
@@ -217,7 +216,7 @@ TlsContext::TlsContext(Role role, const ServerCert* server_cert,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == Role::Client) {
|
if (role == Role::Client) {
|
||||||
// Skip CA chain verification — we use TOFU via the server identity fingerprint.
|
// Skip CA chain verification, we use TOFU via the server identity fingerprint.
|
||||||
mbedtls_ssl_conf_authmode(&conf_, MBEDTLS_SSL_VERIFY_NONE);
|
mbedtls_ssl_conf_authmode(&conf_, MBEDTLS_SSL_VERIFY_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +283,7 @@ bool TlsContext::peer_cert_fingerprint(std::array<uint8_t, 32>& out) const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── SodiumMediaCrypto ─────────────────────────────────────────────────────────
|
// SodiumMediaCrypto
|
||||||
|
|
||||||
SodiumMediaCrypto::SodiumMediaCrypto(
|
SodiumMediaCrypto::SodiumMediaCrypto(
|
||||||
const uint8_t key[crypto_aead_chacha20poly1305_ietf_KEYBYTES]) {
|
const uint8_t key[crypto_aead_chacha20poly1305_ietf_KEYBYTES]) {
|
||||||
@@ -347,8 +346,7 @@ long SodiumMediaCrypto::open(const uint8_t* sealed, size_t len, const uint8_t* a
|
|||||||
if (out_cap < len - crypto_aead_chacha20poly1305_ietf_ABYTES) return -1;
|
if (out_cap < len - crypto_aead_chacha20poly1305_ietf_ABYTES) return -1;
|
||||||
|
|
||||||
// Read the full 64-bit nonce counter directly from aad[8..15] (seq, big-endian
|
// Read the full 64-bit nonce counter directly from aad[8..15] (seq, big-endian
|
||||||
// u64). Protocol v2 carries the full counter on the wire, so the nonce is exact —
|
// u64).
|
||||||
// no reconstruction/rollover guessing needed.
|
|
||||||
if (aad_len < 16) return -1;
|
if (aad_len < 16) return -1;
|
||||||
uint64_t counter = (static_cast<uint64_t>(aad[8]) << 56) |
|
uint64_t counter = (static_cast<uint64_t>(aad[8]) << 56) |
|
||||||
(static_cast<uint64_t>(aad[9]) << 48) |
|
(static_cast<uint64_t>(aad[9]) << 48) |
|
||||||
@@ -364,7 +362,7 @@ long SodiumMediaCrypto::open(const uint8_t* sealed, size_t len, const uint8_t* a
|
|||||||
// window before the AEAD tag is verified — otherwise a single corrupted/forged
|
// window before the AEAD tag is verified — otherwise a single corrupted/forged
|
||||||
// packet would shove recv_highest_ far ahead and reject every later legitimate
|
// packet would shove recv_highest_ far ahead and reject every later legitimate
|
||||||
// packet as "too old", permanently wedging the stream. Order per RFC 3711 §3.3:
|
// packet as "too old", permanently wedging the stream. Order per RFC 3711 §3.3:
|
||||||
// replay-check → authenticate → update.
|
// replay-check authenticate update.
|
||||||
if (recv_initialized_ && counter <= recv_highest_) {
|
if (recv_initialized_ && counter <= recv_highest_) {
|
||||||
uint64_t offset = recv_highest_ - counter;
|
uint64_t offset = recv_highest_ - counter;
|
||||||
if (offset >= 64) return -1; // too old
|
if (offset >= 64) return -1; // too old
|
||||||
@@ -381,7 +379,7 @@ long SodiumMediaCrypto::open(const uint8_t* sealed, size_t len, const uint8_t* a
|
|||||||
nonce, key_.data()) != 0)
|
nonce, key_.data()) != 0)
|
||||||
return -1; // auth failure — leave the replay window untouched
|
return -1; // auth failure — leave the replay window untouched
|
||||||
|
|
||||||
// ── Authenticated: now it's safe to advance the window ────────────────────
|
// Authenticated: now it's safe to advance the window ────────────────────
|
||||||
if (!recv_initialized_) {
|
if (!recv_initialized_) {
|
||||||
recv_highest_ = counter;
|
recv_highest_ = counter;
|
||||||
recv_window_ = 1; // bit0 = highest itself
|
recv_window_ = 1; // bit0 = highest itself
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* crypto/crypto.h — TLS 1.3 (mbedTLS) and the media AEAD (libsodium).
|
* 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
|
*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
|
* 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.
|
* sliding-window replay filter. Encryption is MANDATORY — never add a plaintext path.
|
||||||
*/
|
*/
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
namespace voicecat::crypto {
|
namespace voicecat::crypto {
|
||||||
|
|
||||||
// ── Server identity ────────────────────────────────────────────────────────────
|
// Server identity
|
||||||
// Long-lived Ed25519 key identifying this server instance across cert rotations.
|
// Long-lived Ed25519 key identifying this server instance across cert rotations.
|
||||||
// Fingerprint is the 32-byte SHA-256 of the public key.
|
// Fingerprint is the 32-byte SHA-256 of the public key.
|
||||||
struct ServerIdentity {
|
struct ServerIdentity {
|
||||||
@@ -44,7 +44,7 @@ struct ServerIdentity {
|
|||||||
std::string fingerprint_hex() const;
|
std::string fingerprint_hex() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Server TLS certificate ─────────────────────────────────────────────────────
|
// Server TLS certificate
|
||||||
// Self-signed ECDSA-P256 cert for TLS. On first run, generated and persisted.
|
// Self-signed ECDSA-P256 cert for TLS. On first run, generated and persisted.
|
||||||
struct ServerCert {
|
struct ServerCert {
|
||||||
std::string pem_cert;
|
std::string pem_cert;
|
||||||
@@ -57,7 +57,7 @@ struct ServerCert {
|
|||||||
const std::filesystem::path& key_path) const;
|
const std::filesystem::path& key_path) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── TLS 1.3 context ───────────────────────────────────────────────────────────
|
// TLS 1.3 context
|
||||||
// Wraps mbedTLS for one TLS connection (server or client side).
|
// Wraps mbedTLS for one TLS connection (server or client side).
|
||||||
// All public methods must be called from a single thread at a time.
|
// All public methods must be called from a single thread at a time.
|
||||||
class TlsContext {
|
class TlsContext {
|
||||||
@@ -119,7 +119,7 @@ class TlsContext {
|
|||||||
mbedtls_net_context net_ctx_{};
|
mbedtls_net_context net_ctx_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Media AEAD ─────────────────────────────────────────────────────────────────
|
// Media AEAD
|
||||||
// Per-frame voice encryption. Abstracted so the backend is swappable.
|
// Per-frame voice encryption. Abstracted so the backend is swappable.
|
||||||
class MediaCrypto {
|
class MediaCrypto {
|
||||||
public:
|
public:
|
||||||
@@ -135,7 +135,7 @@ class MediaCrypto {
|
|||||||
uint8_t* out, size_t out_cap) = 0;
|
uint8_t* out, size_t out_cap) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── ChaCha20-Poly1305 backend ──────────────────────────────────────────────────
|
// ChaCha20-Poly1305 backend
|
||||||
// Keys are derived from the TLS session via RFC 5705 / mbedTLS exporter.
|
// Keys are derived from the TLS session via RFC 5705 / mbedTLS exporter.
|
||||||
// Nonce scheme: 4 zero bytes ‖ monotonic-counter(u64 big-endian, 8 bytes).
|
// Nonce scheme: 4 zero bytes ‖ monotonic-counter(u64 big-endian, 8 bytes).
|
||||||
// Anti-replay: 64-bit sliding window keyed on the received counter.
|
// Anti-replay: 64-bit sliding window keyed on the received counter.
|
||||||
@@ -145,10 +145,10 @@ class SodiumMediaCrypto final : public MediaCrypto {
|
|||||||
static std::unique_ptr<SodiumMediaCrypto> derive(TlsContext& tls, uint8_t ctx_byte);
|
static std::unique_ptr<SodiumMediaCrypto> derive(TlsContext& tls, uint8_t ctx_byte);
|
||||||
|
|
||||||
// Convenience: derive the key used to encrypt outgoing frames.
|
// Convenience: derive the key used to encrypt outgoing frames.
|
||||||
// is_client=true → ctx=0x00 (client sends); is_client=false → ctx=0x01 (server sends).
|
// is_client=true ctx=0x00 (client sends); is_client=false ctx=0x01 (server sends).
|
||||||
static std::unique_ptr<SodiumMediaCrypto> derive_send(TlsContext& tls, bool is_client);
|
static std::unique_ptr<SodiumMediaCrypto> derive_send(TlsContext& tls, bool is_client);
|
||||||
// Convenience: derive the key used to decrypt incoming frames.
|
// Convenience: derive the key used to decrypt incoming frames.
|
||||||
// is_client=true → ctx=0x01 (client recvs); is_client=false → ctx=0x00 (server recvs).
|
// is_client=true ctx=0x01 (client recvs); is_client=false ctx=0x00 (server recvs).
|
||||||
static std::unique_ptr<SodiumMediaCrypto> derive_recv(TlsContext& tls, bool is_client);
|
static std::unique_ptr<SodiumMediaCrypto> derive_recv(TlsContext& tls, bool is_client);
|
||||||
|
|
||||||
// Unit-test constructor: supply a raw 32-byte key directly.
|
// Unit-test constructor: supply a raw 32-byte key directly.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* crypto/tofu_store.h — Trust-On-First-Use pin storage.
|
* crypto/tofu_store.h: Trust-On-First-Use pin storage.
|
||||||
*
|
*
|
||||||
* File format: one "host:port <hex-fingerprint>\n" line per entry.
|
* File format: one "host:port <hex-fingerprint>\n" line per entry.
|
||||||
* Used by clients to remember server fingerprints across reconnects.
|
* Used by clients to remember server fingerprints across reconnects.
|
||||||
|
|||||||
Reference in New Issue
Block a user