پرش به مطلب اصلی

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"
}

فیلدهای لازم

فیلدهای سطح اصلی:

فیلدنوعتوضیح
namestringنام نود در پیکربندی که باید یکتا باشد.
typestringباید دقیقاً "EncryptionClient" باشد.
nextstringاجباری. نود بعدی که recordهای رمز‌شده را می‌گیرد.
settingsobjectاجباری است و باید حداقل password داشته باشد.

فیلدهای لازم داخل settings:

فیلدنوعتوضیح
passwordnon-empty stringsecret مشترک برای ساخت کلید AEAD که باید در سمت سرور نیز یکسان باشد.

این نود هم به نود قبلی و هم به next نیاز دارد؛ ابتدا یا انتهای chain نیست.

تنظیمات اختیاری

گزینهنوعپیش‌فرضتوضیح
algorithmstring یا numeric enumchacha20-poly1305الگوریتم AEAD. اگر algorithm وجود نداشته باشد، method نیز به‌عنوان نام مستعار پذیرفته می‌شود.
saltstringwaterwall-encryptionورودی salt برای ساخت کلید که باید در سمت سرور یکسان باشد.
kdf-iterationsinteger12000تعداد دورهای KDF. بازه معتبر 1 تا 1000000 است.
max-frame-sizeinteger16356حداکثر plaintext هر record. بازه معتبر 1 تا 16356 است.

مقدارهای قابل قبول برای algorithm:

الگوریتممقدارها
ChaCha20-Poly1305chacha20-poly1305, chacha20poly1305, chacha20, chacha
AES-256-GCMaes-gcm, aes256gcm, aes-256-gcm, aes256-gcm

اگر AES-GCM را انتخاب کنید ولی backend فعال رمزنگاری از آن پشتیبانی نکند، راه‌اندازی شکست می‌خورد.

ساخت کلید

هر دو peer با استفاده از موارد زیر همان کلید ۳۲ بایتی را می‌سازند:

  • password
  • salt
  • kdf-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 type0x17 application data
version0x0303
body lengthطول nonce + ciphertext + tag به‌صورت big-endian ۱۶ بیتی

Header پنج‌بایتی به‌عنوان associated data در AEAD احراز اصالت می‌شود. نام الگوریتم داخل record نوشته نمی‌شود و باید در پیکربندی دو طرف یکسان باشد.

محدودیت‌ها و Padding

مقداراندازه
TLS-like header5 bytes
nonce12 bytes
AEAD tag16 bytes
حداکثر body هر record16384 bytes
حداکثر plaintext هر record16356 bytes
required_padding_left17 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/resumenext tunnel upstream
downstream pause/resumeprevious tunnel downstream

upstream Est و downstream Init برای این نود غیرفعال هستند.

نکته‌ها

  • باید با EncryptionServer استفاده شود.
  • این نود از بایت‌های payload محافظت می‌کند، اما استفاده از قالب record شبیه TLS در WaterWall را پنهان نمی‌کند.
  • password باید طولانی و تصادفی باشد و در پیکربندی‌های عمومی قرار نگیرد.
  • بهتر است max-frame-size در دو طرف یکسان باشد. گیرنده recordهای بزرگ‌تر از سقف خودش را رد می‌کند.
  • اگر به رفتار واقعی TLS نیاز دارید، از TlsClient و TlsServer استفاده کنید.