"ошибка времени выполнения: границы среза вне диапазона" в зашифрованном чате
Обновление: благодаря peterSO ошибка, по-видимому, заключается в том, что случайные байты, читаемые как строки, будут содержать "\n", что вызывает символ новой строки и ошибку. Проблема ни
io.Copy(conn, bytes.NewReader(encrypted))
ни
conn.Write([]byte(encrypted))
Работа. У кого-нибудь есть идея, как записать чип-текст в conn?
Исходное сообщение: Программа чата состоит из одного сервера и двух клиентов. Он использует TLS и NaCl для (сквозного) шифрования. В 3/4 случаев это работает, но иногда я получаю ошибку:
panic: runtime error: slice bounds out of range
goroutine 34 [running]:
main.handleConnection(0x600a60, 0xc04246c000)
path-to/client.go:79
+0x3a6
created by main.main
path-to/client.go:44
+0x436
exit status 2
Линия 44 звонков
go handleConnection(conn)
Строка 79 - это "расшифрованная" строка:
func handleConnection(conn net.Conn) {
defer conn.Close()
input := bufio.NewScanner(conn)
for input.Scan() {
senderPublicKey := readKey("localPublic")
recipientPrivateKey := readKey("remotePrivate")
var decryptNonce [24]byte
encrypted := input.Bytes()
copy(decryptNonce[:], encrypted[:24])
decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
if !ok {
fmt.Println("decryption error")
}
fmt.Println(BytesToString(decrypted))
}
}
Полный код ниже. Поскольку он работает безупречно без шифрования, и тестовая реализация только шифрования также работает, я бы указал на передачу между клиент-сервер-клиент. Обычно длина среза не должна изменяться, так как вывод должен оставаться неизменным?
Клиент читает:
package main
import (
"bufio"
crypto_rand "crypto/rand"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"golang.org/x/crypto/nacl/box"
)
func main() {
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
log.Fatalln("Unable to load cert", err)
}
clientCACert, err := ioutil.ReadFile("cert.pem")
if err != nil {
log.Fatal("Unable to open cert", err)
}
clientCertPool := x509.NewCertPool()
clientCertPool.AppendCertsFromPEM(clientCACert)
conf := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: clientCertPool,
//InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "localhost:443", conf)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
go handleConnection(conn)
for {
stdin := bufio.NewReader(os.Stdin)
textIn, err := stdin.ReadBytes('\n')
if err != nil {
fmt.Println(err)
}
var nonce [24]byte
if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil {
panic(err)
}
senderPrivateKey := readKey("localPrivate")
recipientPublicKey := readKey("remotePublic")
encrypted := box.Seal(nonce[:], textIn, &nonce, recipientPublicKey, senderPrivateKey)
text := BytesToString(encrypted)
fmt.Fprintf(conn, text+"\n")
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
input := bufio.NewScanner(conn)
for input.Scan() {
senderPublicKey := readKey("localPublic")
recipientPrivateKey := readKey("remotePrivate")
var decryptNonce [24]byte
encrypted := input.Bytes()
copy(decryptNonce[:], encrypted[:24])
decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
if !ok {
fmt.Println("decryption error")
}
fmt.Println(BytesToString(decrypted))
}
}
//BytesToString converts []byte to str
func BytesToString(data []byte) string {
return string(data[:])
}
//Read the keys from file, pass filename without .ending
func readKey(name string) (prv *[32]byte) {
prv = new([32]byte)
f, err := os.Open(name + ".key")
if err != nil {
panic(err)
}
_, err = io.ReadFull(f, prv[:])
if err != nil {
panic(err)
}
return
}
На стороне сервера:
package main
import (
"bufio"
"crypto/tls"
"fmt"
"log"
"net"
)
type client chan<- string // an outgoing message channel
var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string) // all incoming client messages
)
// Broadcast incoming message to all clients' outgoing message channels.
func broadcaster() {
clients := make(map[client]bool) // all connected clients
for {
select {
case msg := <-messages:
for cli := range clients {
cli <- msg
}
case cli := <-entering:
clients[cli] = true
case cli := <-leaving:
delete(clients, cli)
close(cli)
}
}
}
func handleConn(conn net.Conn) {
ch := make(chan string) // outgoing client messages
go clientWriter(conn, ch)
//who := conn.RemoteAddr().String()
entering <- ch
//messages <- who + " has arrived"
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- input.Text()
}
//messages <- who + " has left"
leaving <- ch
conn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg)
}
}
func main() {
cer, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
log.Println(err)
return
}
config := &tls.Config{
Certificates: []tls.Certificate{cer},
//PFS, this will reject client with RSA certificates
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
//Force it server side
PreferServerCipherSuites: true,
//Force TLS Version
MinVersion: tls.VersionTLS12}
listener, err := tls.Listen("tcp", "localhost:443", config)
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
}
1 ответ
Без всякой видимой причины, вы надеетесь, что len(input.Bytes()) >= 24
, Когда это не так: panic: runtime error: slice bounds out of range
,
Например,
package main
func main() {
/*
var decryptNonce [24]byte
encrypted := input.Bytes()
copy(decryptNonce[:], encrypted[:24])
decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
if !ok {
fmt.Println("decryption error")
}
*/
var decryptNonce [24]byte
encrypted := make([]byte, 23, 24) // len(input.Bytes()) < 24
copy(decryptNonce[:], encrypted[:24])
// panic: runtime error: slice bounds out of range
_ = encrypted[24:]
}
Детская площадка: https://play.golang.org/p/LZ34NgLV84G
Выход:
panic: runtime error: slice bounds out of range
goroutine 1 [running]:
main.main()
/tmp/sandbox792172306/main.go:18 +0xa0
Комментарий от Halux9000:
Это очень вероятно причина. Но
len(input.Bytes()) >= 24
должно быть правдой, когдаinput.Bytes()
генерируется черезvar nonce [24]byte if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil { panic(err) } senderPrivateKey := readKey("localPrivate") recipientPublicKey := readKey("remotePublic") encrypted := box.Seal(nonce[:], textIn, &nonce, recipientPublicKey, senderPrivateKey) text := BytesToString(encrypted) fmt.Fprintf(conn, text+"\n")
Шифрование без передачи работает. Так, где это сокращено / изменено?
Я не убежден вашим аргументом "должен". Я верю программе.
Если у вас есть случайные или зашифрованные байты, то некоторые из них будут символами новой строки. Я рассчитал ожидаемый процент строк с новой строкой в первых 24 байтах (одноразовый номер) как 8,966% и подтвердил это экспериментально.
package main
import (
"bytes"
"crypto/rand"
"fmt"
"io"
)
var nonce [24]byte
func expected() float64 {
e := 0.0
for range nonce {
e += (float64(len(nonce)) - e) / 256
}
return e * 100 / float64(len(nonce))
}
func actual() float64 {
a, n := 0, 1024*1024
for i := 0; i < n; i++ {
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
if bytes.IndexByte(nonce[:], '\n') >= 0 {
a++
}
}
return float64(a*100) / float64(n)
}
func main() {
fmt.Printf("expected: %.3f%%\n", expected())
fmt.Printf("actual: %.3f%%\n", actual())
}
Детская площадка: https://play.golang.org/p/cKJlGUSbi3u
Выход:
$ go run newlines.go
expected: 8.966%
actual: 8.943%
$ go run newlines.go
expected: 8.966%
actual: 8.956%
$ go run newlines.go
expected: 8.966%
actual: 8.976%
$ go run newlines.go
expected: 8.966%
actual: 8.992%
$
Комментарий от Halux9000:
Вы бы посоветовали другой способ отправки байтов зашифрованного текста на conn?
Вам нужен более надежный протокол сообщений, который не чувствителен к содержимому сообщений. Например, префикс содержимого сообщения с длиной содержимого сообщения.
Простая иллюстрация,
package main
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
func main() {
// Connection
var conn = new(bytes.Buffer)
{
// Server
buf := make([]byte, 0, 2+64*1024)
msgLen := uint16(16)
buf = buf[0 : 2+msgLen]
binary.BigEndian.PutUint16(buf[0:2], msgLen)
for i := uint16(0); i < msgLen; i++ {
buf[2+i] = byte(i)
}
fmt.Println(msgLen, len(buf), buf)
n, err := conn.Write(buf)
if err != nil {
fmt.Println(n, err)
return
}
}
{
// Client
buf := make([]byte, 0, 2+64*1024)
n, err := io.ReadFull(conn, buf[:2])
if err != nil {
fmt.Println(n, err)
return
}
msgLen := binary.BigEndian.Uint16(buf[0:2])
buf = buf[0 : 2+msgLen]
n, err = io.ReadFull(conn, buf[2:2+msgLen])
if err != nil {
fmt.Println(err)
return
}
fmt.Println(msgLen, len(buf), buf)
}
}
Детская площадка: https://play.golang.org/p/nS3xpFrG4uB
Выход:
16 18 [0 16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
16 18 [0 16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]