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

EncryptionServer

EncryptionServer بخش سرور EncryptionClient است. Recordهای رمز‌شده‌ای را که از کلاینت و در جهت upstream می‌رسند باز می‌کند و payload خام را به next می‌دهد. در جهت downstream نیز payload برگشتی را رمز و فریم‌بندی می‌کند و به نود قبلی می‌فرستد.

هشدار

این نود TlsServer نیست: TLS handshake انجام نمی‌دهد، certificate ارائه نمی‌کند و پارامترهای TLS را نیز negotiate نمی‌کند. قالب record شبیه TLS فقط برای فریم‌بندی payload رمز‌شده AEAD به کار می‌رود.

جایگاه رایج

اتصال مستقیم دو نود در یک process:

TesterClient -> EncryptionClient -> EncryptionServer -> TesterServer

تونل TCP دو سروری:

client side: TcpListener -> EncryptionClient -> TcpConnector
server side: TcpListener -> EncryptionServer -> TcpConnector

تنظیمات دو طرف باید با هم سازگار باشند و تونل‌های میانی باید بایت‌های record رمز‌شده را بدون تغییر عبور دهند.

این نود چه می‌کند؟

  • از password، salt و kdf-iterations یک کلید ۳۲ بایتی AEAD می‌سازد.
  • بایت‌های رمز‌شده upstream را تا کامل شدن recordها بافر می‌کند.
  • header و طول body هر record در upstream را اعتبارسنجی می‌کند.
  • recordهای upstream را باز می‌کند و plaintext بازیابی‌شده را به next می‌فرستد.
  • payloadهای downstream را رمز می‌کند.
  • plaintext downstream بزرگ‌تر از max-frame-size را به چند record تقسیم می‌کند.
  • recordهای رمز‌شده downstream را به نود قبلی می‌فرستد.
  • در صورت نامعتبر بودن record، سرریز read buffer یا شکست احراز اصالت AEAD، هر دو جهت را می‌بندد.

EncryptionServer یک تونل میانی stream است و socket یا packet line نمی‌سازد.

نمونه تنظیم

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

فیلدهای لازم

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

فیلدنوعتوضیح
namestringنام نود در پیکربندی که باید یکتا باشد.
typestringباید دقیقاً "EncryptionServer" باشد.
nextstringاجباری. نود بعدی که payload رمزگشایی‌شده را می‌گیرد.
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ِ downstream از max-frame-size بزرگ‌تر باشد، سرور آن را میان چند record تقسیم می‌کند. بافر payload صفرطول برای استفاده مجدد برگردانده می‌شود و داده عبور نمی‌کند.

رفتار جهت‌ها

جهترفتار
upstream payloadبافر، parse و رمزگشایی می‌شود و plaintext به next می‌رود.
downstream payloadرمز و فریم‌بندی می‌شود و به prev می‌رود.

ممکن است یک فریم کامل upstream در چند 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 برای این نود غیرفعال هستند.

نکته‌ها

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