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"
}
فیلدهای لازم
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام نود در پیکربندی که باید یکتا باشد. |
type | string | باید دقیقاً "EncryptionServer" باشد. |
next | string | اجباری. نود بعدی که payload رمزگشاییشده را میگیرد. |
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ِ 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/resume | next tunnel upstream |
| downstream pause/resume | previous tunnel downstream |
upstream Est و downstream Init برای این نود غیرفعال هستند.
نکتهها
- باید با
EncryptionClientاستفاده شود. - این نود از بایتهای payload محافظت میکند، اما استفاده از قالب record شبیه TLS در WaterWall را پنهان نمیکند.
passwordباید طولانی و تصادفی باشد و در پیکربندیهای عمومی قرار نگیرد.- بهتر است
max-frame-sizeدر دو طرف یکسان باشد. گیرنده recordهای بزرگتر از سقف خودش را رد میکند. - اگر به رفتار واقعی TLS نیاز دارید، از
TlsClientوTlsServerاستفاده کنید.