تحضير المفاتيح (على كلا الجانبين):
[ -f ~ /.ssh/id_ed25519 ] && [ -f ~ /.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519
scp ~ /.ssh/id_ed25519.pub remote:from_remote_side/
مشفرة io.ReadWriteCloser
، سهلة:
// Generate (if not exists) and read ED25519 keys
identity , err := secureio . NewIdentity ( `/home/user/.ssh` )
// Read remote identity
remoteIdentity , err := secureio . NewRemoteIdentityFromPublicKey ( `/home/user/from_remote_side/id_ed25519.pub` )
// Create a connection
conn , err := net . Dial ( "udp" , "10.0.0.2:1234" )
// Create an encrypted connection (and exchange keys using ECDH and verify remote side by ED25519 signature).
session := identity . NewSession ( remoteIdentity , conn , nil , nil )
session . Start ( context . Background ())
// Use it!
// Write to it
_ , err = session . Write ( someData )
// Or/and read from it
_ , err = session . Read ( someData )
إعداد جهاز الاستقبال:
session . SetHandlerFuncs ( secureio . MessageTypeChannel ( 0 ), func ( payload [] byte ) {
fmt . Println ( "I received a payload:" , payload )
}, func ( err error ) {
panic ( err )
})
إرسال رسالة بشكل متزامن:
_ , err := session . WriteMessage ( secureio . MessageTypeChannel ( 0 ), payload )
أو
إرسال رسالة بشكل غير متزامن:
// Schedule the sending of the payload
sendInfo := session . WriteMessageAsync ( secureio . MessageTypeChannel ( 0 ), payload )
[.. your another stuff here if you want ..]
// Wait until the real sending
<- sendInfo . Done ()
// Here you get the error if any:
err := sendInfo. Err
// It's not necessary, but helps to reduce the pressure on GC (so to optimize CPU and RAM utilization)
sendInfo . Release ()
يمكن إنشاء messageType لقناة مخصصة عبر الوظيفة MessageTypeChannel(channelID uint32)
. channelID
هو رقم مخصص لتحديد التدفق (لربط المرسل بجهاز الاستقبال المناسب على الجانب البعيد). في الأمثلة أعلاه، تم استخدام 0
كقيمة channelID
، ولكن يمكن أن تكون أي قيمة في النطاق: 0 <= x <= 2**31
.
يوجد أيضًا نوع خاص من الرسائل هو MessageTypeReadWrite
يستخدم Read()
/ Write()
. ولكن يمكنك إعادة توجيه هذا التدفق إلى معالج مخصص.
SessionOptions.MaxPayloadSize
.SessionOptions.SendDelay
على &[]time.Duration{0}[0]
. تم تنفيذ المعيار من خلال الاتصال عبر مقبس UNIX.
BenchmarkSessionWriteRead1-8 10000 118153 ns/op 0.01 MB/s 468 B/op 8 allocs/op
BenchmarkSessionWriteRead16-8 10000 118019 ns/op 0.14 MB/s 455 B/op 8 allocs/op
BenchmarkSessionWriteRead1024-8 9710 119238 ns/op 8.59 MB/s 441 B/op 8 allocs/op
BenchmarkSessionWriteRead32000-8 6980 173441 ns/op 184.50 MB/s 488 B/op 9 allocs/op
BenchmarkSessionWriteRead64000-8 3994 310038 ns/op 206.43 MB/s 629 B/op 9 allocs/op
BenchmarkSessionWriteMessageAsyncRead1-8 2285032 539 ns/op 1.86 MB/s 0 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead16-8 2109264 572 ns/op 27.99 MB/s 2 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead1024-8 480385 2404 ns/op 425.87 MB/s 15 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead32000-8 30163 39131 ns/op 817.76 MB/s 162 B/op 5 allocs/op
BenchmarkSessionWriteMessageAsyncRead64000-8 15435 77898 ns/op 821.59 MB/s 317 B/op 10 allocs/op
تم تصميم هذه الحزمة لتكون غير متزامنة، لذا فإن Write
عبارة عن غلاف غبي حول كود WriteMessageAsync
. للحصول على مزيد من الإنتاجية، يقوم بدمج جميع رسائلك التي تم جمعها في 50 ميكروثانية في رسالة واحدة، ويرسلها، ثم يقسمها مرة أخرى. يسمح بتقليل كمية مكالمات النظام والنفقات العامة الأخرى. لذا، لتحقيق ما يعادل 1.86 ميجابايت/ثانية على الرسائل ذات البايت الواحد، فإنك تحتاج إلى إرسال الكثير منها بشكل غير متزامن (من بعضها البعض)، لذلك سيتم دمجها أثناء الإرسال/الاستقبال عبر اتصال الواجهة الخلفية.
كما أن سرعة 800 ميجابايت/ثانية هذه تتعلق أكثر بحالة المضيف المحلي. وحالة الشبكة الأكثر واقعية (إذا كان لدينا MTU ~= 1400) هي:
BenchmarkSessionWriteMessageAsyncRead1300_max1400-8 117862 10277 ns/op 126.49 MB/s 267 B/op 10 allocs/op
تتم مصادقة الجانب البعيد بتوقيع ED25519 (لرسالة تبادل المفاتيح).
يتم إجراء تبادل المفاتيح عبر ECDH مع X25519. إذا تم تعيين PSK، فسيتم ربط PSK بقيمة ملح ثابتة، وتجزئته باستخدام blake3.Sum256
و sha3.Sum256
واستخدامه لـ XOR المفتاح (المتبادل).
يتم استخدام القيمة الناتجة كمفتاح تشفير لـ XChaCha20. يُسمى هذا المفتاح cipherKey
داخل الكود.
يتم تحديث المفتاح (المستلم عبر ECDH) كل دقيقة. وبالتالي يتم تحديث cipherKey
كل دقيقة أيضًا.
يتم تبادل SessionID
من خلال رسائل تبادل المفاتيح الأولى. SessionID
عبارة عن مزيج من UnixNano (عند تهيئة الجلسة) وعدد صحيح عشوائي 64 بت.
كما تبدأ كل حزمة بمعرف PacketID
نص عادي فريد (لجلسة واحدة) (في الواقع إذا تم تعيين PSK، فسيتم تشفير PacketID
بمفتاح مشتق كتجزئة لـ PSK مملح).
يتم استخدام مجموعة PacketID
و SessionID
كـ IV/NONCE لـ XChaCha20 ويتم استخدام cipherKey
كمفتاح.
تجدر الإشارة إلى أن PacketID
يهدف إلى أن يكون فريدًا داخل الجلسة فقط. لذلك يبدأ بالصفر ثم يزيد بمقدار 1 لكل رسالة تالية. ولذلك، إذا تم كسر ساعة النظام، فسيتم ضمان تفرد NONCE بقيمة عشوائية 64 بت لـ SessionID
.
تتم مصادقة الرسالة باستخدام Poly1305. كمفتاح لـ Poly1305، تم استخدام تجزئة blake3.Sum256 لما يلي:
PacketID
و cipherKey
XOR-ed بقيمة ثابتة. من المفترض أن يتم زيادة PacketID
فقط. يتم تذكر PacketID المستلم في نافذة محدودة من القيم. إذا تم استلام حزمة بنفس PacketID
(كما كانت بالفعل) أو مع PackerID
أقل (من أقل قيمة ممكنة في النافذة)، فسيتم تجاهل الحزمة فقط.
تمر الجلسة الناجحة بالإعدادات الافتراضية عبر حالات/مراحل/مراحل:
هذه هي المرحلة التي يتم فيها تحليل جميع الخيارات وتهيئة كافة الإجراءات المطلوبة.
بعد ذلك سيتم تحويل *Session
إلى حالة "تبادل المفاتيح".
في هذه المرحلة، يقوم الطرفان (المحلي والبعيد) بتبادل المفاتيح العامة لـ ECDH للحصول على مفتاح مشترك متماثل (انظر "التصميم الأمني").
أيضًا، إذا تم تعيين هوية عن بعد، فإننا نتحقق مما إذا كانت متطابقة، ونتجاهل أي رسائل تبادل مفاتيح مع أي مفاتيح عامة أخرى لـ ED25519 (لا تخلط بين المفتاح العام ECDH): تكون أزواج المفاتيح ED25519 ثابتة لكل طرف (وعادةً ما تكون مسبقة- المحددة)، في حين يتم إنشاء أزواج مفاتيح ECDH لكل تبادل مفاتيح.
كما يتم التحقق من كل مفتاح لتبادل المفاتيح بواسطة المفتاح العام (الذي يتم تمريره مع الرسالة).
باستخدام الإعدادات الافتراضية، يرسل كل طرف أيضًا رسائل شكر للتحقق من استلام الرسائل واستلامها. و(بالإعدادات الافتراضية) ينتظر كل طرف رسالة إقرار من الجانب البعيد (انظر أيضًا SessionOptions.AnswersMode
).
إذا نجح كل شيء هنا، فإن *Session
تنتقل إلى مرحلة "التفاوض". لكن عملية تبادل المفاتيح لا تزال تتم بشكل دوري في الخلفية.
نحاول في هذه المرحلة تحديد حجم الحزم التي يمكن لـ io.Writer
الأساسي التعامل معها. لذلك نحاول إرسال حزم بثلاثة أحجام مختلفة ونرى أي منها سيكون قادرًا على القيام برحلة ذهابًا وإيابًا. ثم كرر الإجراء في فترة زمنية أقصر. وهكذا حتى 4 مرات.
يمكن تمكين هذا السلوك أو تعطيله من خلال SessionOptions.NegotiatorOptions.Enable
.
عند انتهاء هذا الإجراء (أو في حالة تعطيله)، يتم تحويل *Session
إلى الحالة "تم التأسيس".
هذه هي الحالة التي تعمل فيها *Session
بشكل طبيعي، لذا يمكنك إرسال واستقبال الرسائل من خلالها. والرسائل التي تتم محاولة إرسالها قبل الوصول إلى هذه المرحلة سيتم إرسالها بمجرد وصول *Session
إلى هذه المرحلة.
Closing
هو حالة انتقالية قبل أن يصبح Closed
. إذا تم الوصول إلى حالة Closed
فهذا يعني أن الجلسة قد ماتت ولن يحدث لها أي شيء على الإطلاق.
Async
للكتابة المتزامنة.notewakeup
بدلاً من Cond.Wait