เตรียมกุญแจ(ทั้งสองด้าน):
[ -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
นอกจากนี้ยังมี MessageType 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
จึงเป็น wrapper โง่ ๆ รอบ ๆ โค้ดของ WriteMessageAsync
เพื่อให้ได้รับปริมาณงานมากขึ้น ระบบจะรวมข้อความทั้งหมดของคุณที่รวบรวมในเวลา 50 ไมโครวินาทีเป็นข้อความเดียว จากนั้นส่ง แล้วแยกกลับ ช่วยลดจำนวนการโทรระบบและค่าใช้จ่ายอื่นๆ ดังนั้นเพื่อให้ได้ความเร็วประมาณ 1.86MiB/s สำหรับข้อความขนาด 1 ไบต์ คุณจะต้องส่งข้อความจำนวนมากแบบอะซิงโครนัส (จากกัน) ดังนั้นข้อความเหล่านั้นจะถูกรวมเข้าด้วยกันในขณะที่ส่ง/รับผ่านการเชื่อมต่อแบ็กเอนด์
นอกจากนี้ 800MiB/s นี้ยังเกี่ยวกับ localhost-case มากกว่าอีกด้วย และกรณีเครือข่ายที่สมจริงยิ่งขึ้น (ถ้าเรามี 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
น้อยกว่า (มากกว่าค่าต่ำสุดที่เป็นไปได้ในหน้าต่าง) แพ็กเก็ตนั้นจะถูกละเว้น
เซสชันที่ประสบความสำเร็จด้วยการตั้งค่าเริ่มต้นจะต้องผ่านสถานะ/ระยะ/ระยะ:
นี่คือขั้นตอนที่ตัวเลือกทั้งหมดจะถูกแยกวิเคราะห์และ goroutines ที่จำเป็นทั้งหมดกำลังถูกเตรียมใช้งาน
หลังจากนั้น *Session
จะเปลี่ยนเป็นสถานะ "การแลกเปลี่ยนคีย์"
ในขั้นตอนนี้ ทั้งสองฝ่าย (ทั้งในพื้นที่และระยะไกล) กำลังแลกเปลี่ยนกับกุญแจสาธารณะ ECDH เพื่อรับกุญแจที่ใช้ร่วมกันแบบสมมาตร (ดู "การออกแบบความปลอดภัย")
นอกจากนี้ หากมีการตั้งค่าข้อมูลระบุตัวตนระยะไกล เราจะตรวจสอบว่าตรงกันหรือไม่ และไม่สนใจข้อความแลกเปลี่ยนคีย์ใดๆ กับคีย์สาธารณะ ED25519 อื่นๆ (อย่าสับสนกับคีย์สาธารณะ ECDH): คู่คีย์ ED25519 เป็นแบบคงที่สำหรับแต่ละฝ่าย (และโดยปกติจะก่อน- กำหนดไว้) ในขณะที่คู่คีย์ ECDH จะถูกสร้างขึ้นสำหรับการแลกเปลี่ยนคีย์แต่ละรายการ
นอกจากนี้ คีย์การแลกเปลี่ยนคีย์แต่ละคีย์ยังได้รับการตรวจสอบโดยคีย์สาธารณะ (ส่งผ่านพร้อมกับข้อความ)
ด้วยการตั้งค่าเริ่มต้น แต่ละฝ่ายยังส่งข้อความตอบรับเพื่อตรวจสอบว่าข้อความได้รับและรับรู้หรือไม่ และ (ด้วยการตั้งค่าเริ่มต้น) แต่ละฝ่ายจะรอข้อความตอบรับจากระยะไกล (ดู SessionOptions.AnswersMode
เพิ่มเติม )
หากทุกอย่างสำเร็จที่นี่ *Session
จะเข้าสู่ช่วง "การเจรจา" แต่กระบวนการแลกเปลี่ยนคีย์ยังคงดำเนินการอยู่เบื้องหลังเป็นระยะๆ
ในขั้นตอนนี้ เราพยายามกำหนดขนาดของแพ็กเก็ตที่ io.Writer
พื้นฐานสามารถจัดการได้ เราเลยลองส่งซองมา 3 ขนาดด้วยกันดูว่าอันไหนจะสามารถไป-กลับได้ จากนั้นทำซ้ำขั้นตอนในช่วงเวลาที่สั้นลง และต่อเนื่องกันถึง 4 ครั้ง
ลักษณะการทำงานนี้สามารถเปิดหรือปิดใช้งานได้ผ่าน SessionOptions.NegotiatorOptions.Enable
เมื่อขั้นตอนนี้จะเสร็จสิ้น (หรือหากปิดใช้งานอยู่) *Session
จะเปลี่ยนเป็นสถานะ "Established"
นี่คือสถานะที่ *Session
ทำงานตามปกติ ดังนั้นคุณจึงสามารถส่งและรับข้อความผ่านเซสชันได้ และข้อความที่พยายามส่งก่อนที่จะถึงขั้นตอนนี้จะถูกส่งทันทีที่ *Session
จะถึงขั้นตอนนี้
Closing
เป็นสถานะเปลี่ยนผ่านก่อนที่จะกลายเป็น Closed
หากถึงสถานะ Closed
หมายความว่าเซสชั่นนั้นตายและจะไม่มีอะไรเกิดขึ้นอีกต่อไป
Async
สำหรับการเขียนซิงค์notewakeup
แทน Cond.Wait