Saya membuat paket ini untuk membuat koneksi stateful virtual antara klien & server menggunakan protokol UDP untuk server game golang (seperti yang Anda tahu protokol UDP tidak memiliki kewarganegaraan, paket mungkin tidak sampai secara berurutan & tidak ada ACK).
udpsocket
mendukung tiruan jabat tangan DTLS, kriptografi, manajemen sesi & otentikasi. Bertanggung jawab untuk membuat saluran aman antara klien & server di UDP (jabat tangan), mengautentikasi & mengelolanya, mendekripsi & mengenkripsi data & menyediakan API untuk mengirim atau menyiarkan data ke klien & mendengarkannya.
Server udpsocket
menerima beberapa parameter untuk memulai:
*net.UDPConn
Opsi dapat diteruskan menggunakan fungsi yang telah ditentukan sebelumnya:
udpsocket . WithAuthClient ()
udpsocket . WithTranscoder ()
udpsocket . WithSymmetricCrypto ()
udpsocket . WithAsymmetricCrypto ()
udpsocket . WithReadBufferSize ()
udpsocket . WithMinimumPayloadSize ()
udpsocket . WithProtocolVersion ()
udpsocket . WithHeartbeatExpiration ()
udpsocket . WithLogger ()
Implementasi antarmuka AuthClient
yang digunakan untuk mengautentikasi token pengguna. Jika server Anda tidak memerlukan autentikasi berbasis token, jangan berikan apa pun, sehingga Server akan menggunakan implementasi default yang tidak memiliki autentikasi.
Implementasi antarmuka Transcoder
yang digunakan untuk menyandikan & mendekode data antara klien & server. Implementasi default Transcoder
adalah Protobuf
. Transcoder dapat menyandikan & mendekode beberapa jenis pesan default, seperti jabat tangan & ping & juga mendukung metode umum Marshal
& Unmarshal
untuk mendukung tipe data khusus Anda.
Implementasi antarmuka crypto.Symmetric
untuk mengenkripsi & mendekripsi melalui algoritma kunci Simetris. Implementasi defaultnya menggunakan AES CBC
dengan padding PKCS#7
.
Implementasi antarmuka crypto.Asymmetric
untuk mengenkripsi & mendekripsi melalui kriptografi kunci publik. Implementasi defaultnya menggunakan RSA
dengan ukuran kunci khusus.
ReadBufferSize
: Ukuran buffer membacaMinimumPayloadSize
: untuk memotong data yang ukurannya tidak mencukupi (untuk mencegah beberapa metode serangan)ProtocolVersionMajor
: versi utama protokolProtocolVersionMinor
: versi minor protokol package main
import (
"context"
"log"
"net"
"fmt"
"crypto/rsa"
"time"
"os"
"demo/auth"
"demo/encoding"
"github.com/theredrad/udpsocket"
"github.com/theredrad/udpsocket/crypto"
)
var (
pk * rsa. PrivateKey
udpServerIP = "127.0.0.1"
udpServerPort = "7009"
defaultRSAPrivateKeySize = 2048
)
func main () {
f , err := auth . NewFirebaseClient ( context . Background (), "firebase-config.json" ) // firebase implementation of auth client to validate firebase-issued tokens
if err != nil {
panic ( err )
}
udpAddr , err := net . ResolveUDPAddr ( "udp" , fmt . Sprintf ( "%s:%s" , udpServerIP , udpServerPort ))
if err != nil {
panic ( err )
}
udpConn , err := net . ListenUDP ( "udp" , udpAddr )
if err != nil {
panic ( err )
}
defer udpConn . Close ()
pk , err = crypto . GenerateRSAKey ( defaultRSAPrivateKeySize )
if err != nil {
panic ( err )
}
r := crypto . NewRSAFromPK ( pk ) // creating a new instance of the RSA implementation
if err != nil {
panic ( err )
}
a := crypto . NewAES ( crypto . AES_CBC ) // creating a new instance of the AES implementation
t := & encoding. MessagePack {} // an implementation of msgpack for the Transcoder
s , err := udpsocket . NewServer ( udpConn ,
udpsocket . WithAuthClient ( f ),
udpsocket . WithTranscoder ( t ),
udpsocket . WithSymmetricCrypto ( a ),
udpsocket . WithAsymmetricCrypto ( r ),
udpsocket . WithReadBufferSize ( 2048 ),
udpsocket . WithMinimumPayloadSize ( 4 ),
udpsocket . WithProtocolVersion ( 1 , 0 ),
udpsocket . WithHeartbeatExpiration ( 3 * time . Second ),
udpsocket . WithLogger ( log . New ( os . Stdout , "udp server: " , log . Ldate )),
)
if err != nil {
panic ( err )
}
s . SetHandler ( incomingHandler )
go s . Serve () // start to run the server, listen to incoming records
// TODO: need to serve the public key on HTTPS (TLS) to secure the download for the client
}
func incomingHandler ( id string , t byte , p [] byte ) {
// handle the incoming
}
Server mengekspor dua metode untuk mengirim pesan ke klien atau menyiarkan pesan ke semua klien. Untuk mengirim pesan ke klien tertentu, Anda harus memiliki ID klien.
Payloadnya adalah pesan yang disandikan Transcoder & dienkripsi ditangani oleh Server.
Penangan adalah fungsi dengan tanda tangan func(id string, t byte, p []byte)
. Anda dapat mengatur fungsi handler Anda dengan metode SetHandler
. Fungsi ini dipanggil ketika rekaman tipe khusus diterima & diautentikasi. Parameter id
adalah ID klien (yang diambil dari token, atau UUID baru yang dihasilkan jika tidak diperlukan otentikasi), parameter t
adalah jenis catatan & parameter p
adalah payload yang didekripsi, Anda harus Unmarshal
ke kustom Anda jenis pesan.
Setiap pesan dari klien adalah Record
. Catatan memiliki format untuk diurai & didekripsi.
1 0 1 1 0 2 52 91 253 115 22 78 39 28 5 192 47 211...
|-| |-| |-| |------------------------------------------|
a b c d
a: record type
b: record protocol major version
c: record protocol minor version
d: record body
Anda mungkin bertanya mengapa ukuran pesan terenkripsi RSA ditambahkan ke catatan sementara ukuran kunci privat RSA diketahui dan ukuran pesan terenkripsi dapat dihitung. Server
sebenarnya menggunakan antarmuka Asymmetric
untuk enkripsi dan RSA mengimplementasikannya, jadi mungkin pada implementasi lain, ukuran pesan terenkripsi tidak mengikuti ukuran kunci privat, oleh karena itu kita perlu meneruskan ukuran pesan terenkripsi dalam jabat tangan agar dapat memisahkannya. Bagian terenkripsi AES dan terenkripsi RSA.
Byte pertama dari record adalah tipe & menunjukkan cara mengurai record. tipe cadangan yang didukung:
1
2
3
4
5
Proses jabat tangan dibuat berdasarkan meniru protokol DTLS, klien mengirimkan catatan ClientHello
, catatan ini berisi byte acak & kunci AES & dienkripsi oleh kunci publik server, yang diperlukan untuk mengunduh di TLS, kemudian server menghasilkan acak cookie berdasarkan parameter klien, mengenkripsinya dengan kunci AES klien (yang diterima oleh ClientHello
& mengirimkannya sebagai catatan HelloVerify
. Klien mendekripsi catatan & mengulangi pesan ClientHello
dengan cookie, catatan tersebut diperlukan untuk dienkripsi dengan kunci publik server, kemudian menambahkan token pengguna terenkripsi (dengan AES) ke badan catatan server akan mendaftarkan klien setelah verifikasi cookie & mengautentikasi token pengguna & kemudian mengembalikan catatan ServerHello
yang berisi ID sesi rahasia acak. Proses jabat tangan dilakukan di sini.
Client Server
------ ------
ClientHello ------>
<----- HelloVerifyRequest
(contains cookie)
ClientHello ------>
(with cookie & token)
<----- ServerHello
(contains session ID)
Server
menggunakan enkripsi simetris & asimetris untuk berkomunikasi dengan klien.
Catatan ClientHello
(untuk server) berisi kunci AES 256 bit yang aman & dienkripsi oleh kunci publik server, sehingga server dapat mendekripsinya dengan kunci pribadi.
Catatan HelloVerify
(untuk klien) mengenkripsi dengan kunci AES klien (yang telah didekripsi sebelumnya).
Jika otentikasi pengguna diperlukan, klien harus mengirimkan token pengguna dengan ClientHello
(catatan yang juga berisi cookie), tetapi enkripsi asimetris memiliki batasan ukuran. misalnya RSA hanya mampu mengenkripsi data hingga jumlah maksimal sama dengan ukuran kunci (misal 2048 bits = 256 bytes) & ukuran token pengguna bisa lebih, sehingga token pengguna harus dienkripsi oleh AES klien, maka server dapat mendekripsinya setelah validasi catatan ClientHello
.
Struktur catatan jabat tangan sedikit berbeda karena menggunakan enkripsi hybrid. dua byte setelah byte versi protokol menunjukkan ukuran badan jabat tangan yang dienkripsi oleh kunci publik server. ukuran badan jabat tangan lewat karena ukuran kunci, ukuran badan terenkripsi bergantung pada ukuran kunci RSA.
1 0 1 1 0 2 52 91 253 115 22 78 39 28 5 192 47 211 ... 4 22 64 91 195 37 225
|-| |-| |-| |-| |-| |----------------------------------------| |---------------------|
a b c d e f g
a: record type (1 => handshake)
b: record protocol major version (0 => 0.1v)
c: record protocol minor version (1 => 0.1v)
d: handshake body size ([1] 0 => 256 bytes, key size: 2048 bits) (first digit number in base of 256)
e: handshake body size (1 [0] => 256 bytes, key size: 2048 bits) (second digit number in base of 256)
f: handshake body which is encrypted by the server public key & contains the client AES key
g: user token which is encrypted by the client AES key size
Setelah jabat tangan berhasil, server mengembalikan SessionID
rahasia dalam catatan ServerHello
yang dienkripsi oleh Kunci AES klien. Klien harus menambahkan SessionID
ke byte rekaman khusus, lalu mengenkripsinya dengan kunci AES, menambahkan header rekaman (tipe rekaman & versi protokol), lalu mengirim byte ke server. Server akan mendekripsi badan rekaman dengan kunci AES klien (yang telah didaftarkan sebelumnya dengan IP & port klien), kemudian mengurai SessionID
dari badan yang didekripsi, mengotorisasi ID sesi & meneruskan byte ke fungsi Handler
.
Untuk catatan Ping
, server segera mengirimkan catatan Pong
ke klien.