Siapkan kunci (di kedua sisi):
[ -f ~ /.ssh/id_ed25519 ] && [ -f ~ /.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519
scp ~ /.ssh/id_ed25519.pub remote:from_remote_side/
Terenkripsi io.ReadWriteCloser
, mudah:
// 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 )
Siapkan penerima:
session . SetHandlerFuncs ( secureio . MessageTypeChannel ( 0 ), func ( payload [] byte ) {
fmt . Println ( "I received a payload:" , payload )
}, func ( err error ) {
panic ( err )
})
Kirim pesan secara serentak:
_ , err := session . WriteMessage ( secureio . MessageTypeChannel ( 0 ), payload )
ATAU
Kirim pesan secara asinkron:
// 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 untuk saluran khusus dapat dibuat melalui fungsi MessageTypeChannel(channelID uint32)
. channelID
adalah nomor khusus untuk mengidentifikasi aliran mana (untuk menghubungkan pengirim dengan penerima yang sesuai di sisi jarak jauh). Pada contoh di atas, nilai 0
digunakan sebagai nilai channelID
, namun dapat berupa nilai apa pun dalam rentang tersebut: 0 <= x <= 2**31
.
Juga ada MessageType khusus MessageTypeReadWrite
yang digunakan untuk Read()
/ Write()
default. Namun Anda dapat mengalihkan aliran ini ke penangan khusus.
SessionOptions.MaxPayloadSize
.SessionOptions.SendDelay
ke &[]time.Duration{0}[0]
. Benchmark dilakukan dengan komunikasi melalui soket 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
Paket ini dirancang untuk menjadi asynchronous, jadi pada dasarnya Write
adalah pembungkus kode WriteMessageAsync
yang bodoh. Untuk mendapatkan lebih banyak throughput, ia menggabungkan semua pesan Anda yang dikumpulkan dalam 50 mikrodetik menjadi satu, mengirim, dan kemudian membaginya kembali. Hal ini memungkinkan untuk mengurangi jumlah syscall dan overhead lainnya. Jadi untuk mencapai sekitar 1,86MiB/s pada pesan 1-byte, Anda perlu mengirim banyak pesan secara asinkron (satu sama lain), sehingga pesan-pesan tersebut akan digabungkan saat mengirim/menerima melalui koneksi backend.
Juga 800MiB/s ini lebih banyak tentang kasus localhost. Dan kasus jaringan yang lebih realistis (jika kita memiliki MTU ~= 1400) adalah:
BenchmarkSessionWriteMessageAsyncRead1300_max1400-8 117862 10277 ns/op 126.49 MB/s 267 B/op 10 allocs/op
Sisi jarak jauh diautentikasi dengan tanda tangan ED25519 (dari pesan pertukaran kunci).
Pertukaran kunci dilakukan melalui ECDH dengan X25519. Jika PSK disetel, maka PSK tersebut digabungkan dengan nilai garam konstan, di-hash dengan blake3.Sum256
dan sha3.Sum256
dan digunakan untuk XOR kunci (yang ditukar).
Nilai yang dihasilkan digunakan sebagai kunci enkripsi untuk XChaCha20. Kunci ini disebut cipherKey
di dalam kode.
Kuncinya (diterima melalui ECDH) diperbarui setiap menit. Jadi pada gilirannya cipherKey
juga diperbarui setiap menit.
SessionID
dipertukarkan melalui pesan pertukaran kunci pertama. SessionID
adalah kombinasi UnixNano (saat Sesi diinisialisasi) dan integer 64-bit acak.
Juga setiap paket dimulai dengan PacketID
teks biasa yang unik (untuk sesi) (sebenarnya jika PSK disetel maka PacketID
dienkripsi dengan kunci yang diturunkan sebagai hash dari PSK asin).
Kombinasi PacketID
dan SessionID
digunakan sebagai IV/NONCE untuk XChaCha20 dan cipherKey
digunakan sebagai kuncinya.
Perlu disebutkan bahwa PacketID
dimaksudkan untuk menjadi unik hanya dalam satu Sesi. Jadi ini hanya dimulai dari nol dan kemudian bertambah 1 untuk setiap pesan berikutnya. Oleh karena itu jika jam sistem rusak maka keunikan NONCE dijamin oleh nilai acak SessionID
64-bit.
Otentikasi pesan dilakukan menggunakan Poly1305. Sebagai kunci untuk Poly1305 menggunakan hash blake3.Sum256 dari:
PacketID
dan cipherKey
XOR-ed dengan nilai konstan. PacketID
seharusnya meningkat saja. PacketID yang diterima diingat dalam jendela nilai yang terbatas. Jika paket diterima dengan PacketID
yang sama (seperti yang sudah ada) atau dengan PackerID
yang lebih kecil (dari nilai minimal yang mungkin ada di jendela) maka paket tersebut akan diabaikan begitu saja.
Sesi yang sukses dengan pengaturan default melewati status/fase/tahapan:
Ini adalah tahap di mana semua opsi diurai dan semua goroutine yang diperlukan diinisialisasi.
Setelah itu *Session
akan dialihkan ke status "Pertukaran Kunci".
Pada tahap ini kedua belah pihak (lokal dan jarak jauh) bertukar kunci publik ECDH untuk mendapatkan kunci bersama yang simetris (lihat "Desain Keamanan").
Juga jika identitas jarak jauh disetel maka kami memverifikasi apakah cocok, dan mengabaikan pesan pertukaran kunci apa pun dengan kunci publik ED25519 lainnya (jangan bingung dengan kunci publik ECDH): Pasangan kunci ED25519 bersifat statis untuk masing-masing pihak (dan biasanya pra- didefinisikan), sedangkan pasangan kunci ECDH dihasilkan untuk setiap pertukaran kunci.
Juga setiap kunci pertukaran kunci diverifikasi oleh kunci publik (diteruskan bersama pesan).
Dengan pengaturan default, masing-masing pihak juga mengirimkan pesan pengakuan untuk memverifikasi apakah pesan tersebut diterima dan dirasakan. Dan (dengan pengaturan default) masing-masing pihak menunggu pesan pengakuan dari sisi jarak jauh (lihat juga SessionOptions.AnswersMode
).
Jika semuanya berhasil di sini maka *Session
masuk ke fase "Negosiasi". Namun proses pertukaran kunci masih dilakukan secara berkala di latar belakang.
Pada tahap ini kami mencoba menentukan ukuran paket yang dapat ditangani oleh io.Writer
. Jadi kami mencoba mengirim paket dengan 3 ukuran berbeda dan melihat paket mana yang bisa melakukan perjalanan pulang pergi. Kemudian ulangi prosedur ini dalam interval yang lebih pendek. Begitu seterusnya hingga 4 kali.
Perilaku ini dapat diaktifkan atau dinonaktifkan melalui SessionOptions.NegotiatorOptions.Enable
.
Ketika prosedur ini akan selesai (atau jika dinonaktifkan), maka *Session
dialihkan ke status "Ditetapkan".
Ini adalah keadaan di mana *Session
beroperasi secara normal, sehingga Anda dapat mengirim dan menerima pesan melaluinya. Dan pesan yang dicoba dikirim sebelum mencapai tahap ini akan dikirim segera setelah *Session
mencapai tahap ini.
Closing
merupakan keadaan peralihan sebelum menjadi Closed
. Jika keadaan Closed
tercapai berarti sesi sudah mati dan tidak akan terjadi apa-apa lagi.
Async
untuk penulisan sinkronisasi.notewakeup
daripada Cond.Wait