From 6929d42736627dfe820a7e2009acb0153b6aed41 Mon Sep 17 00:00:00 2001 From: ospab Date: Wed, 8 Jul 2026 03:06:53 +0300 Subject: [PATCH] Polish docs/README to match v0.4.x: fix license mismatch, CLI, crypto docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README.md/README.ru.md: License section still said "Business Source License 1.1 ... converts to MIT in 2030" while the badge right above it, Cargo.toml, and LICENSE itself all say AGPL-3.0 — a direct contradiction. Now both say AGPL-3.0 and link to LICENSE. - README.md/README.ru.md: CLI Reference / Quick Start described the old flag-based interface (--init, --check, --generate-key, --links, bare positional URL) that no longer exists after the subcommand refactor. Rewrote both to the current `ostp ` surface (run/connect/setup/ init/check/gk/links/import/update/migrate/prober/proxy-env/uninstall), including gk's alias and update's --branch/--version. RU previously had no command reference at all; added one to match EN. - docs/{en,ru}/obfuscation.md: removed the XTLS-Reality section (feature removed in §A) and replaced it with an accurate description of junk packets + TCP fragmentation, the actual current supplementary stealth mechanism, including the per-key junk marker (no global DPI signature). Also corrected the key-derivation and masking-algorithm descriptions, which described a much older scheme (SHA-256(access_key)[0..8] + static/ nonce-based XOR) than what derive_all_secrets()/derive_payload_mask() actually implement now (HKDF with version-gated, domain-separated outputs; HMAC-SHA256 mask keyed on the packet's own ciphertext). The RU version was additionally rewritten out of an oddly formal "industrial telemetry" register into plain technical Russian. --- README.md | 54 ++++++++++++++++++++------------- README.ru.md | 38 ++++++++++++++++++++--- docs/en/obfuscation.md | 51 +++++++++++++++++-------------- docs/ru/obfuscation.md | 69 ++++++++++++++++++++++-------------------- 4 files changed, 132 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 50a63fc..58ddf81 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,10 @@ graph TD ```bash # On your VPS (server): -./ostp --init server +./ostp init server # On your machine (client): -./ostp --init client +./ostp init client ``` ### 2. Edit config @@ -129,16 +129,16 @@ graph TD ### 3. Run ```bash -./ostp # Uses config.json in current directory -./ostp --config /path/to.json # Custom config path -./ostp --check # Validate config without running -./ostp --generate-key # Generate a new access key -./ostp --links # Print client share links +./ostp # Uses config.json in current directory +./ostp --config /path/to.json # Custom config path +./ostp check # Validate config without running +./ostp gk # Generate a new access key +./ostp links # Print client share links ``` ### 4. Connect via share link (one-liner) ```bash -./ostp "ostp://ACCESS_KEY@server.com:50000?..." +./ostp connect "ostp://ACCESS_KEY@server.com:50000?..." ``` > [!WARNING] @@ -171,21 +171,34 @@ Full API reference: [Management API](https://github.com/ospab/ostp/wiki/Manageme ## CLI Reference ``` -ostp [OPTIONS] [URL] +ostp [--config ] [COMMAND] -Options: +Commands: + run Run the daemon using the config file (default when no command is given) + connect Connect once using a share link: ostp://KEY@HOST:PORT + setup Interactive setup wizard + init Generate a template config (server/client/relay) + check Validate the configuration file and exit + gk Generate a secure access key (alias: generate-key) + --format Key format: hex, base64 (default: hex) + -n, --count Number of keys to generate (default: 1) + links Print client share links from the server config + import Import a share link into the config file + update Update OSTP to the latest release + -b, --branch Release channel: stable, pre-release, nightly (default: stable) + -v, --version Update to an exact version instead of the channel's latest + migrate Force-migrate the configuration file to the current format + prober Run the DNS-transport resolver prober + proxy-env Print shell export commands for the local SOCKS proxy + proxy-env-clear Print shell export commands to unset it + uninstall Stop the service and remove the binary and config + +Global options: --config Config file path (default: config.json) - --init Generate template config (server/client) - --check Validate configuration and exit - -g, --generate-key Generate a secure access key - -c, --count Number of keys to generate (default: 1) - --format Key format: hex, base64 (default: hex) - --links Print client share links from server config - -Arguments: - [URL] Connect via share link: ostp://KEY@HOST:PORT ``` +Every subcommand also accepts `-h`/`--help` for its own option list. + --- ## Protocol Summary @@ -230,8 +243,7 @@ cargo test -p ostp-core -p ostp-server ## License -Business Source License 1.1. Free for personal and non-commercial use. -Converts to MIT License on May 14, 2030. +GNU Affero General Public License v3.0 (AGPL-3.0). See [LICENSE](LICENSE) for the full text. --- diff --git a/README.ru.md b/README.ru.md index 1fba266..e55c552 100644 --- a/README.ru.md +++ b/README.ru.md @@ -84,8 +84,8 @@ irm https://raw.githubusercontent.com/ospab/ostp/master/scripts/install.ps1 | ie Создать конфиг по умолчанию: ```bash -./ostp --init server # VPS -./ostp --init client # Локальная машина +./ostp init server # VPS +./ostp init client # Локальная машина ``` ### Сервер (`config.json`) @@ -156,6 +156,37 @@ irm https://raw.githubusercontent.com/ospab/ostp/master/scripts/install.ps1 | ie ./ostp ``` +### Справка по командам + +``` +ostp [--config ] [КОМАНДА] + +Команды: + run Запустить демон по конфигу (по умолчанию, если команда не указана) + connect Подключиться по share-ссылке: ostp://KEY@HOST:PORT + setup Интерактивный мастер настройки + init Сгенерировать шаблон конфига (server/client/relay) + check Проверить конфиг и выйти + gk Сгенерировать access-key (алиас: generate-key) + --format Формат ключа: hex, base64 (по умолчанию hex) + -n, --count Количество ключей (по умолчанию 1) + links Вывести client-share-ссылки из серверного конфига + import Импортировать share-ссылку в конфиг + update Обновить OSTP до актуального релиза + -b, --branch Канал релиза: stable, pre-release, nightly (по умолчанию stable) + -v, --version Обновиться на точную версию вместо последней в канале + migrate Принудительно мигрировать конфиг к текущему формату + prober Запустить DNS-transport prober + proxy-env Вывести shell-команды для локального SOCKS-прокси + proxy-env-clear Вывести shell-команды для их отмены + uninstall Остановить сервис и удалить бинарник с конфигом + +Глобальные опции: + --config Путь к конфигу (по умолчанию config.json) +``` + +У каждой подкоманды есть своя справка через `-h`/`--help`. + ### TUN-режим (Windows) Использует встроенный сетевой стек `smoltcp` и виртуальный адаптер `wintun` (необходима `wintun.dll`). Требует запуска с правами Администратора. @@ -204,5 +235,4 @@ cross build --release --target x86_64-unknown-linux-gnu ## Лицензия -Business Source License 1.1. Бесплатно для личного и некоммерческого использования. -Переходит в MIT License 14 мая 2030 года. +GNU Affero General Public License v3.0 (AGPL-3.0). Полный текст — в файле [LICENSE](LICENSE). diff --git a/docs/en/obfuscation.md b/docs/en/obfuscation.md index 8548566..402ec98 100644 --- a/docs/en/obfuscation.md +++ b/docs/en/obfuscation.md @@ -5,40 +5,38 @@ Traditional tunneling protocols (such as TLS, OpenVPN, and WireGuard) exhibit di --- -## Obfuscation Key Derivation +## Secret Derivation -To dynamically mask protocol data, an 8-byte obfuscation key is statically derived from the shared `access_key` configured on both the client and the server: +Every protocol secret — the obfuscation key, the Noise PSK, the handshake padding range, and the per-key junk marker (see below) — is derived from the shared `access_key` via a single HKDF-SHA256 pass, domain-separated by a trailing info byte per output: -$$\text{Key} = \text{SHA-256}(\text{access\_key})[0..8]$$ +``` +PRK = HKDF-Extract(salt = SHA-256(access_key)[0..16], IKM = access_key || PROTOCOL_VERSION) +obfuscation_key = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x01, 8 bytes) +psk = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x02, 32 bytes) +handshake_pad = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x03, 2 bytes) +junk_marker = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x04, 4 bytes) +``` -This key is established pre-session and is never transmitted across the wire in any capacity. +The wire protocol version is mixed into the IKM, not sent as a plaintext byte: peers on a different protocol version derive an entirely different `obfuscation_key`, so they simply cannot deobfuscate each other's packets and are rejected as unauthorized — a hard version gate with no recognizable marker ever appearing on the wire. No secret is ever transmitted; both sides derive the same values independently from the shared access key. --- ## Dynamic In-Place Masking Algorithm -OSTP datagrams are processed "in-place" immediately prior to transmission and right after arrival. Two distinct mathematical modes are utilized based on the current handshake phase: +OSTP datagrams are masked "in-place" immediately prior to transmission and right after arrival. The mask itself is **derived from the packet's own ciphertext**, not from a fixed keystream or a counter, so it changes with every packet automatically: + +``` +mask = HMAC-SHA256(key = obfuscation_key, message = ciphertext[0..min(32, len)]) +``` ### 1. Handshake Phase Mode (`is_handshake = true`) -During connection initiation (Noise Handshake), the wire packet consists of a 4-byte `session_id` prefixed to the Noise payload. To mask the fixed session ID: - -* **Masking**: The first 4 bytes are XORed with the first 4 bytes of the derived obfuscation key: - $$\text{raw}[i] = \text{raw}[i] \oplus \text{Key}[i \pmod 8], \quad i \in [0..3]$$ -* **De-masking**: A repeated XOR with the identical key bytes recovers the original `session_id`. +The wire packet is `[4-byte session_id][2-byte noise_len][Noise payload]`. The mask is computed over the Noise payload (`raw[6..]`), and its first 6 bytes are XORed onto `session_id || noise_len`. ### 2. Data Transmission Mode (`is_handshake = false`) -Post-handshake, the wire layout contains: -`[4-byte session_id]` + `[8-byte nonce]` + `[AEAD Ciphertext]` +The wire packet is `[4-byte session_id][8-byte nonce][AEAD ciphertext]`. The mask is computed over the AEAD ciphertext, and its first 12 bytes are XORed onto `session_id || nonce`. -To completely randomize metadata, a two-tiered dynamic XOR masking process is applied: - -1. **Nonce Masking**: The 8-byte `nonce` (sequence counter) is XORed with the full 8-byte static key: - $$\text{nonce\_bytes}[i] = \text{nonce\_bytes}[i] \oplus \text{Key}[i], \quad i \in [0..7]$$ -2. **Session ID Masking**: The 4-byte `session_id` is masked using high dynamic entropy — the lower 32 bits of the **original (unmasked)** `nonce` value: - $$\text{session\_id\_bytes}[i] = \text{session\_id\_bytes}[i] \oplus \text{real\_nonce\_low32\_bytes}[i], \quad i \in [0..3]$$ - -#### Impact of the Scheme: -Because the `nonce` increments strictly with each outgoing datagram, the session ID's masking keystream continuously changes. This breaks all packet header correlations and eliminates repeating byte patterns, rendering statistical fingerprinting futile. +#### Impact of the Scheme +Because the mask is keyed on both the shared secret and the packet's own ciphertext, no two packets — even consecutive ones from the same session — share a keystream, without needing an explicit counter-based scheme. This breaks all packet header correlations and eliminates repeating byte patterns, rendering statistical fingerprinting futile. --- @@ -50,6 +48,13 @@ The `AdaptivePadder` calculates dynamic dummy byte quantities to append to the p - **Dynamic Distributions**: The padding algorithms emulate length profiles commonly seen in whitelisted HTTPS or real-time video streams. - **Encrypted Overheads**: The appended padding resides within the AEAD cipher scope. Consequently, passive observers cannot distinguish padding bytes from useful application payload, hiding the true message boundary lengths. -## XTLS-Reality Impersonation +--- -OSTP provides a custom, dependency-free implementation of the XTLS-Reality protocol. It fully simulates a TLS 1.3 handshake (with realistic ClientHello profiles) to bypass advanced DPI filters. Post-handshake, it utilizes ChaCha20Poly1305 to seamlessly encrypt and tunnel the inner HTTP/WSS connections. \ No newline at end of file +## Junk Packets & TCP Fragmentation + +OSTP does not try to impersonate a known protocol (TLS, HTTP, or otherwise) — a fingerprint-matching filter can always be updated to catch an impersonation attempt. Instead it follows a **zapret-like** approach: no recognizable header at all, plus active manipulation of packet boundaries, so there is nothing distinctive to fingerprint in the first place. + +- **Junk packets**: before the handshake, the client sends a configurable number (`junk_pc`) of random-size (`junk_ps`) filler datagrams. Each carries a 4-byte marker **derived from the access key** (the `junk_marker` above) rather than a fixed constant — a fixed marker would itself be a universal signature any observer could filter on across every OSTP deployment. The server derives the same per-key marker while trying candidate keys and drops matching junk silently, before it ever reaches the "unauthorized probe" logging path. +- **TCP fragmentation** (UoT/TCP transport only): the first packet (the handshake) is split into small chunks (`frag_chunk` bytes) with short delays (`frag_sleep` ms) between writes, so DPI that inspects only the first TCP segment never sees a complete handshake to fingerprint. + +Both are configurable per-profile; neither is sent over plain UDP transport, where a standalone junk datagram would look exactly like a random one-off probe to the server. \ No newline at end of file diff --git a/docs/ru/obfuscation.md b/docs/ru/obfuscation.md index 49de434..fc33339 100644 --- a/docs/ru/obfuscation.md +++ b/docs/ru/obfuscation.md @@ -1,55 +1,60 @@ -# Маскирование энтропии сигналов OSTP +# Обфускация трафика OSTP -## Философия структуры канала +## Философия -Традиционные сетевые протоколы промышленного сбора данных могут обладать фиксированными заголовками, что при анализе статистического распределения байт ведет к предвзятости выборок и искажению телеметрического профиля. Задача механизмов энтропийного маскирования OSTP — достижение **равномерного вероятностного распределения значений байт**, начиная с самого первого пакета. Это делает сигналы шины данных абсолютно однородными и устойчивыми к корреляционному анализу и структурному мониторингу сетевых контроллеров. +Классические туннельные протоколы (TLS, OpenVPN, WireGuard) имеют узнаваемые сигнатуры в хэндшейке или статичные заголовки пакетов. Механизм обфускации OSTP спроектирован так, чтобы **начиная с первого байта** трафик был максимально похож на случайный шум — и для DPI-систем был неотличим от него. --- -## Производная сигнатурная матрица (Keystream Initialization Vector) +## Деривация секретов -Для стабилизации битового распределения используется 8-байтовый вектор, вычисляемый на базе глобального идентификатора регистрации узла (`access_key`): +Все секреты протокола — ключ обфускации, PSK Noise-хэндшейка, диапазон паддинга хэндшейка и маркер junk-пакетов (см. ниже) — выводятся из общего `access_key` одним проходом HKDF-SHA256, с разделением по доменам через последний байт `info`: -$$\text{Key} = \text{SHA-256}(\text{access\_key})[0..8]$$ +``` +PRK = HKDF-Extract(salt = SHA-256(access_key)[0..16], IKM = access_key || PROTOCOL_VERSION) +obfuscation_key = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x01, 8 байт) +psk = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x02, 32 байта) +handshake_pad = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x03, 2 байта) +junk_marker = HKDF-Expand(PRK, info = SHA-256(access_key)[16..] || 0x04, 4 байта) +``` -Данная последовательность фиксируется на передающем и принимающем узлах и не передается через внешние сетевые шлюзы. +Версия протокола подмешивается в IKM, а не передаётся открытым байтом на проводе: пиры с разной версией протокола выведут разный `obfuscation_key` и просто не смогут деобфусцировать пакеты друг друга — жёсткий version gate без единого узнаваемого маркера на проводе. Ни один секрет никогда не передаётся — обе стороны независимо выводят одинаковые значения из общего access_key. --- -## Алгоритм динамического маскирования пакетов (In-place Masking) +## Алгоритм динамического маскирования -Пакетные структуры OSTP проходят низкоуровневую предобработку непосредственно перед выдачей в канальный уровень (Layer 3) и при получении. В зависимости от фазы жизненного цикла сессии связи выделяют две модели: +Датаграммы OSTP маскируются "на месте" прямо перед отправкой и сразу после получения. Сама маска **выводится из шифротекста самого пакета**, а не из статичного потока ключа или счётчика — поэтому она меняется от пакета к пакету автоматически: -### 1. Этап начального согласования среды (`is_handshake = true`) -В период инициализации канала передачи пакет структурирован как 4-байтовое поле логического адреса порта `session_id` и криптографический блок согласования среды. Для подавления статических компонент ID порта применяется процедура обратимого битового сложения: +``` +mask = HMAC-SHA256(key = obfuscation_key, message = ciphertext[0..min(32, len)]) +``` -* **Обработка**: Первые 4 байта вектора пакета проходят побитовую операцию XOR с первыми 4 байтами сигнатурной матрицы: - $$\text{raw}[i] = \text{raw}[i] \oplus \text{Key}[i \pmod 8], \quad i \in [0..3]$$ -* **Восстановление**: Обратное наложение сигнатурной матрицы возвращает корректное значение логического идентификатора. +### 1. Фаза хэндшейка (`is_handshake = true`) +Пакет на проводе — `[4 байта session_id][2 байта noise_len][Noise-полезная нагрузка]`. Маска считается по Noise-полезной нагрузке (`raw[6..]`), и её первые 6 байт накладываются XOR'ом на `session_id || noise_len`. -### 2. Этап высокоскоростного переноса данных (`is_handshake = false`) -После перевода сессии в состояние активности кадр передачи принимает следующий вид: -`[4 байта session_id]` + `[8 байт nonce]` + `[Полезная нагрузка блока]` +### 2. Фаза передачи данных (`is_handshake = false`) +Пакет на проводе — `[4 байта session_id][8 байт nonce][AEAD-шифротекст]`. Маска считается по шифротексту, и её первые 12 байт накладываются XOR'ом на `session_id || nonce`. -Для максимизации дифференциальной энтропии применяется двухступенчатое динамическое взвешивание: - -1. **Коррекция счетчика цикла (Nonce Correction)**: 8-байтовое значение инкрементного счетчика пакета подвергается побитовому сложению с вектором матрицы: - $$\text{nonce\_bytes}[i] = \text{nonce\_bytes}[i] \oplus \text{Key}[i], \quad i \in [0..7]$$ -2. **Маскирование ID сессии**: 4-байтовое поле логического адреса маскируется с помощью переменной высокочастотной энтропии — младших 32 бит **исходного** показателя системного счетчика пакетов: - $$\text{session\_id\_bytes}[i] = \text{session\_id\_bytes}[i] \oplus \text{real\_nonce\_low32\_bytes}[i], \quad i \in [0..3]$$ - -#### Статистическая устойчивость: -Благодаря инкрементации счетчика на каждом цикле отправки, маскирующий поток (keystream) для поля `session_id` постоянно видоизменяется. Это полностью нивелирует фиксированные битовые паттерны во всем спектре UDP-датаграмм и исключает появление повторяющихся префиксов. +#### Эффект схемы +Поскольку маска зависит одновременно от общего секрета и от содержимого шифротекста конкретного пакета, никакие два пакета — даже два подряд идущих в одной сессии — не используют одинаковый ключевой поток, и для этого не нужна явная схема на основе счётчика. Это полностью убирает корреляции между заголовками пакетов и повторяющиеся байтовые паттерны, делая статистический фингерпринтинг бесполезным. --- -## Выравнивание блоков по границам регистров (Adaptive Alignment) +## Статистический паддинг -Дополнительно к маскировке заголовков, протокол OSTP исключает возможность анализа поведения системы на основе длин пакетов данных. Модуль адаптивного заполнения (`AdaptivePadder`) рассчитывает оптимальный размер буфера выравнивания (`padding`), интегрируемый в структуру пакета до момента активации шифрующего каскада: +Помимо маскирования заголовков, OSTP защищается от анализа длин пакетов (Traffic Length Analysis). `AdaptivePadder` вычисляет случайный размер мусорных байт, добавляемых к полезной нагрузке ещё до шифрования: -- **Стратегия заполнения буферов**: Механизм анализирует текущую длину выборки телеметрии и производит масштабирование до типичных кратных длин промышленных сетей передачи данных и буферов потоковых агрегаторов. -- **Изоляция выравнивания**: Данные заполнения помещаются внутрь защищенной области кадра. Внешние анализаторы топологии сети не способны определить внутренние границы между телеметрической нагрузкой и служебными полями выравнивания, видя только монолитный блок данных. +- **Динамическое распределение**: длины паддинга подобраны так, чтобы напоминать профили длин обычного HTTPS-трафика или видеопотоков. +- **Внутри шифротекста**: добавленный паддинг находится внутри области AEAD-шифрования — пассивный наблюдатель не может отличить паддинг от полезной нагрузки и не видит настоящую границу сообщения. -## XTLS-Reality (Имитация TLS 1.3) +--- -OSTP предоставляет собственную реализацию протокола XTLS-Reality без сторонних зависимостей. Протокол полностью имитирует рукопожатие TLS 1.3 (с реалистичным профилем ClientHello) для обхода продвинутых DPI фильтров. После успешного рукопожатия применяется ChaCha20Poly1305 для бесшовного шифрования и туннелирования внутренних HTTP/WSS соединений. \ No newline at end of file +## Junk-пакеты и TCP-фрагментация + +OSTP не пытается притворяться известным протоколом (TLS, HTTP и т.п.) — фильтр по сигнатуре всегда можно обновить под конкретную имитацию. Вместо этого используется подход **в духе zapret**: никакого узнаваемого заголовка вообще, плюс активная манипуляция границами пакетов — фингерпринтить попросту нечего. + +- **Junk-пакеты**: перед хэндшейком клиент отправляет настраиваемое количество (`junk_pc`) мусорных датаграмм случайного размера (`junk_ps`). Каждая несёт 4-байтовый маркер, **выведенный из access_key** (тот самый `junk_marker` выше), а не фиксированную константу — константный маркер сам по себе стал бы универсальной сигнатурой для любого наблюдателя сразу по всем серверам OSTP. Сервер, перебирая кандидатов-ключей, выводит тот же маркер и тихо отбрасывает junk, не доходя до логирования «unauthorized probe». +- **TCP-фрагментация** (только для транспорта UoT/TCP): первый пакет (хэндшейк) режется на мелкие куски (`frag_chunk` байт) с небольшими задержками (`frag_sleep` мс) между записями — DPI, анализирующий только первый TCP-сегмент, никогда не видит цельный хэндшейк для фингерпринтинга. + +Обе фичи настраиваются per-профиль; ни одна не применяется поверх обычного UDP-транспорта, где отдельная junk-датаграмма выглядела бы для сервера точь-в-точь как случайный одиночный проб. \ No newline at end of file