This repository has been archived on 2020-04-20. You can view files and clone it, but cannot push or open issues or pull requests.
libricochet-go/utils/networking.go

144 lines
3.9 KiB
Go

package utils
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"git.openprivacy.ca/openprivacy/log"
"golang.org/x/crypto/nacl/secretbox"
"io"
"sync"
)
const (
// InvalidPacketLengthError is returned whenever ricochet receives a packet too small or too large to conform to the spec.
InvalidPacketLengthError = Error("InvalidPacketLengthError")
// InvalidChannelIDError channels must be between 0 and 65535
InvalidChannelIDError = Error("InvalidChannelIDError")
)
// RicochetData is a structure containing the raw data and the channel it the
// message originated on.
type RicochetData struct {
Channel int32
Data []byte
}
// Equals compares a RicochetData object to another and returns true if contain
// the same data.
func (rd RicochetData) Equals(other RicochetData) bool {
return rd.Channel == other.Channel && bytes.Equal(rd.Data, other.Data)
}
// RicochetNetworkInterface abstract operations that interact with ricochet's
// packet layer.
type RicochetNetworkInterface interface {
SendRicochetPacket(dst io.Writer, channel int32, data []byte) error
RecvRicochetPacket(reader io.Reader) (RicochetData, error)
}
// RicochetNetwork is a concrete implementation of the RicochetNetworkInterface
type RicochetNetwork struct {
// Derived ephemeral session key for connection
key [32]byte
encrypt bool
lock sync.Mutex
}
// SetEncryptionKey sets the ephemeral encryption key for this session.
func (rn *RicochetNetwork) SetEncryptionKey(key [32]byte) {
rn.lock.Lock()
defer rn.lock.Unlock()
log.Debugf("turning on ephemeral session encryption for connection")
copy(rn.key[:], key[:])
rn.encrypt = true
}
// SendRicochetPacket places the data into a structure needed for the client to
// decode the packet and writes the packet to the network.
func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data []byte) error {
packet := make([]byte, 4+len(data))
if len(packet) > 65535 {
return InvalidPacketLengthError
}
binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet)))
if channel < 0 || channel > 65535 {
return InvalidChannelIDError
}
binary.BigEndian.PutUint16(packet[2:4], uint16(channel))
copy(packet[4:], data[:])
rn.lock.Lock()
if rn.encrypt {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
encrypted := secretbox.Seal(nonce[:], packet[2:], &nonce, &rn.key)
binary.BigEndian.PutUint16(packet[0:2], uint16(len(encrypted)+2))
packet = append(packet[0:2], encrypted...)
}
rn.lock.Unlock()
for pos := 0; pos < len(packet); {
n, err := dst.Write(packet[pos:])
if err != nil {
return err
}
pos += n
}
return nil
}
// RecvRicochetPacket returns the next packet from reader as a RicochetData
// structure, or an error.
func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, error) {
packet := RicochetData{}
// Read the four-byte header to get packet length
header := make([]byte, 2)
if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil {
return packet, err
}
size := int(binary.BigEndian.Uint16(header[0:2]))
if size < 4 {
return packet, InvalidPacketLengthError
}
packetBytes := make([]byte, size-2)
_, err := io.ReadAtLeast(reader, packetBytes, size-2)
if err != nil {
return packet, err
}
rn.lock.Lock()
if rn.encrypt {
var decryptNonce [24]byte
if len(packetBytes) > 24 {
copy(decryptNonce[:], packetBytes[:24])
decrypted, ok := secretbox.Open(nil, packetBytes[24:], &decryptNonce, &rn.key)
if ok {
packetBytes = decrypted
} else {
return packet, errors.New("failed to decrypt encrypted ricochet packet")
}
} else {
return packet, errors.New("ciphertext length was too short")
}
}
rn.lock.Unlock()
packet.Channel = int32(binary.BigEndian.Uint16(packetBytes[0:2]))
packet.Data = make([]byte, len(packetBytes)-2)
copy(packet.Data[:], packetBytes[2:])
return packet, nil
}