EncryptionClient
EncryptionClient بخش کلاینت EncryptionServer است. Payloadهای upstream را با AEAD رمز میکند و در recordهایی شبیه TLS application data میفرستد. در جهت downstream نیز recordهای رمزشده را باز میکند و payload خام را به نود قبلی برمیگرداند.
این نود TlsClient نیست: TLS handshake انجام نمیدهد، certificate را تأیید نمیکند و SNI/ALPN ندارد. قالب record شبیه TLS فقط برای فریمبندی payload رمزشده به کار میرود.
جایگاه رایج
اتصال مستقیم دو نود در یک process:
TesterClient -> EncryptionClient -> EncryptionServer -> TesterServer
تونل TCP دو سروری:
client side: TcpListener -> EncryptionClient -> TcpConnector
server side: TcpListener -> EncryptionServer -> TcpConnector
تنظیمات دو طرف باید با هم سازگار باشند و تونلهای میانی باید بایتهای record رمزشده را بدون تغییر عبور دهند.
این نود چه میکند؟
- از
password،saltوkdf-iterationsیک کلید ۳۲ بایتی AEAD میسازد. - payloadهای upstream را رمز میکند.
- plaintext upstream بزرگتر از
max-frame-sizeرا به چند record تقسیم میکند. - هر chunk رمزشده را در یک record شبیه TLS application data قرار میدهد.
- بایتهای رمزشده downstream را تا کامل شدن recordها بافر میکند.
- پیش از رمزگشایی، header و طول body هر record را اعتبارسنجی میکند.
- recordهای downstream را باز میکند و plaintext بازیابیشده را به نود قبلی میفرستد.
- در صورت نامعتبر بودن record، سرریز read buffer یا شکست احراز اصالت AEAD، هر دو جهت را میبندد.
EncryptionClient یک تونل میانی stream است و socket یا packet line نمیسازد.
نمونه تنظیم
{
"name": "enc-client",
"type": "EncryptionClient",
"settings": {
"algorithm": "chacha20-poly1305",
"password": "replace-with-a-strong-secret",
"salt": "chain-a",
"kdf-iterations": 20000,
"max-frame-size": 16356
},
"next": "transport"
}
تنظیمات متناظر سمت سرور:
{
"name": "enc-server",
"type": "EncryptionServer",
"settings": {
"algorithm": "chacha20-poly1305",
"password": "replace-with-a-strong-secret",
"salt": "chain-a",
"kdf-iterations": 20000,
"max-frame-size": 16356
},
"next": "service"
}
فیلدهای لازم
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام نود در پیکربندی که باید یکتا باشد. |
type | string | باید دقیقاً "EncryptionClient" باشد. |
next | string | اجباری. نود بعدی که recordهای رمزشده را میگیرد. |
settings | object | اجباری است و باید حداقل password داشته باشد. |
فیلدهای لازم داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
password | non-empty string | secret مشترک برای ساخت کلید AEAD که باید در سمت سرور نیز یکسان باشد. |
این نود هم به نود قبلی و هم به next نیاز دارد؛ ابتدا یا انتهای chain نیست.
تنظیمات اختیاری
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
algorithm | string یا numeric enum | chacha20-poly1305 | الگوریتم AEAD. اگر algorithm وجود نداشته باشد، method نیز بهعنوان نام مستعار پذیرفته میشود. |
salt | string | waterwall-encryption | ورودی salt برای ساخت کلید که باید در سمت سرور یکسان باشد. |
kdf-iterations | integer | 12000 | تعداد دورهای KDF. بازه معتبر 1 تا 1000000 است. |
max-frame-size | integer | 16356 | حداکثر plaintext هر record. بازه معتبر 1 تا 16356 است. |
مقدارهای قابل قبول برای algorithm:
| الگوریتم | مقدارها |
|---|---|
| ChaCha20-Poly1305 | chacha20-poly1305, chacha20poly1305, chacha20, chacha |
| AES-256-GCM | aes-gcm, aes256gcm, aes-256-gcm, aes256-gcm |
اگر AES-GCM را انتخاب کنید ولی backend فعال رمزنگاری از آن پشتیبانی نکند، راهاندازی شکست میخورد.
ساخت کلید
هر دو peer با استفاده از موارد زیر همان کلید ۳۲ بایتی را میسازند:
passwordsaltkdf-iterations
پیادهسازی از حلقهای اختصاصی در WaterWall و مبتنی بر BLAKE2s برای ساخت کلید استفاده میکند، نه از PBKDF2 یا Argon2. کلید ساختهشده در state تونل نگه داشته میشود و هنگام آزاد شدن instance تونل با صفر بازنویسی میشود.
برای کار کردن مسیر رمزشده، هر دو سمت باید password، salt، kdf-iterations یکسان و algorithm سازگار داشته باشند.
فرمت record
هر بخش رمزشده با این ساختار ارسال میشود:
5-byte TLS-like header | 12-byte nonce | AEAD ciphertext | 16-byte tag
| بخش | مقدار |
|---|---|
| record type | 0x17 application data |
| version | 0x0303 |
| body length | طول nonce + ciphertext + tag بهصورت big-endian ۱۶ بیتی |
Header پنجبایتی بهعنوان associated data در AEAD احراز اصالت میشود. نام الگوریتم داخل record نوشته نمیشود و باید در پیکربندی دو طرف یکسان باشد.
محدودیتها و Padding
| مقدار | اندازه |
|---|---|
| TLS-like header | 5 bytes |
| nonce | 12 bytes |
| AEAD tag | 16 bytes |
| حداکثر body هر record | 16384 bytes |
| حداکثر plaintext هر record | 16356 bytes |
required_padding_left | 17 bytes |
مقدار required_padding_left برابر 17 است؛ فضای کافی برای header پنجبایتی و nonce دوازدهبایتی که پیش از رمزنگاری prepend میشوند.
اگر payloadِ upstream از max-frame-size بزرگتر باشد، کلاینت آن را میان چند record تقسیم میکند. بافر payload صفرطول برای استفاده مجدد برگردانده میشود و داده عبور نمیکند.
رفتار جهتها
| جهت | رفتار |
|---|---|
| upstream payload | رمز و فریمبندی میشود و به next میرود. |
| downstream payload | بافر، parse و رمزگشایی میشود و plaintext به prev میرود. |
ممکن است یک فریم کامل downstream در چند callbackِ payload تکهتکه برسد یا چند فریم با یک callback دریافت شوند. Stream خواندن، بایتهای بافرشده را تا زمان parse شدن recordهای کامل نگه میدارد.
اگر حجم داده رمزشده در بافر از حدود دو record کامل بیشتر شود، line بسته خواهد شد.
رفتار Lifecycle
با دریافت Init در upstream، کلاینت stream خواندن مخصوص line را مقداردهی اولیه میکند و Init را در همان جهت به next میفرستد.
با دریافت Finish از هر جهت، state محلی line را از بین میبرد و Finish را در همان جهت منتشر میکند: در upstream به next و در downstream به prev.
Pause و resume در همان جهت دریافتشده عبور داده میشوند:
| callback | مقصد ارسال |
|---|---|
| upstream pause/resume | next tunnel upstream |
| downstream pause/resume | previous tunnel downstream |
upstream Est و downstream Init برای این نود غیرفعال هستند.
نکتهها
- باید با
EncryptionServerاستفاده شود. - این نود از بایتهای payload محافظت میکند، اما استفاده از قالب record شبیه TLS در WaterWall را پنهان نمیکند.
passwordباید طولانی و تصادفی باشد و در پیکربندیهای عمومی قرار نگیرد.- بهتر است
max-frame-sizeدر دو طرف یکسان باشد. گیرنده recordهای بزرگتر از سقف خودش را رد میکند. - اگر به رفتار واقعی TLS نیاز دارید، از
TlsClientوTlsServerاستفاده کنید.