Files
weave/router/frame_crypto.go
T

285 lines
7.7 KiB
Go

package router
import (
"encoding/binary"
"fmt"
"github.com/andybalholm/go-bit"
"golang.org/x/crypto/nacl/secretbox"
)
// Frame Encryptors
type Encryptor interface {
FrameOverhead() int
PacketOverhead() int
Bytes() ([]byte, error)
AppendFrame(src []byte, dst []byte, frame []byte)
TotalLen() int
}
type NonEncryptor struct {
buf []byte
bufTail []byte
buffered int
prefixLen int
}
type NaClEncryptor struct {
NonEncryptor
buf []byte
prefixLen int
sessionKey *[32]byte
nonce [24]byte
seqNo uint64
df bool
}
func NewNonEncryptor(prefix []byte) *NonEncryptor {
buf := make([]byte, MaxUDPPacketSize)
prefixLen := copy(buf, prefix)
return &NonEncryptor{
buf: buf,
bufTail: buf[prefixLen:],
buffered: prefixLen,
prefixLen: prefixLen}
}
func (ne *NonEncryptor) PacketOverhead() int {
return ne.prefixLen
}
func (ne *NonEncryptor) FrameOverhead() int {
return NameSize + NameSize + 2
}
func (ne *NonEncryptor) Bytes() ([]byte, error) {
buf := ne.buf[:ne.buffered]
ne.buffered = ne.prefixLen
ne.bufTail = ne.buf[ne.prefixLen:]
return buf, nil
}
func (ne *NonEncryptor) AppendFrame(src []byte, dst []byte, frame []byte) {
bufTail := ne.bufTail
srcLen := copy(bufTail, src)
bufTail = bufTail[srcLen:]
dstLen := copy(bufTail, dst)
bufTail = bufTail[dstLen:]
binary.BigEndian.PutUint16(bufTail, uint16(len(frame)))
bufTail = bufTail[2:]
copy(bufTail, frame)
ne.bufTail = bufTail[len(frame):]
ne.buffered += srcLen + dstLen + 2 + len(frame)
}
func (ne *NonEncryptor) TotalLen() int {
return ne.buffered
}
func NewNaClEncryptor(prefix []byte, sessionKey *[32]byte, outbound bool, df bool) *NaClEncryptor {
buf := make([]byte, MaxUDPPacketSize)
prefixLen := copy(buf, prefix)
ne := &NaClEncryptor{
NonEncryptor: *NewNonEncryptor([]byte{}),
buf: buf,
prefixLen: prefixLen,
sessionKey: sessionKey,
df: df}
if outbound {
ne.nonce[0] |= (1 << 7)
}
return ne
}
func (ne *NaClEncryptor) Bytes() ([]byte, error) {
plaintext, err := ne.NonEncryptor.Bytes()
if err != nil {
return nil, err
}
// We carry the DF flag in the (unencrypted portion of the)
// payload, rather than just extracting it from the packet headers
// at the receiving end, since we do not trust routers not to mess
// with headers. As we have different decryptors for non-DF and
// DF, that would result in hard to track down packet drops due to
// crypto errors.
seqNoAndDF := ne.seqNo
if ne.df {
seqNoAndDF |= (1 << 63)
}
ciphertext := ne.buf
binary.BigEndian.PutUint64(ciphertext[ne.prefixLen:], seqNoAndDF)
binary.BigEndian.PutUint64(ne.nonce[16:24], seqNoAndDF)
// Seal *appends* to ciphertext
ciphertext = secretbox.Seal(ciphertext[:ne.prefixLen+8], plaintext, &ne.nonce, ne.sessionKey)
ne.seqNo++
return ciphertext, nil
}
func (ne *NaClEncryptor) PacketOverhead() int {
return ne.prefixLen + 8 + secretbox.Overhead + ne.NonEncryptor.PacketOverhead()
}
func (ne *NaClEncryptor) TotalLen() int {
return ne.PacketOverhead() + ne.NonEncryptor.TotalLen()
}
// Frame Decryptors
type FrameConsumer func(src []byte, dst []byte, frame []byte)
type Decryptor interface {
IterateFrames([]byte, FrameConsumer) error
}
type NonDecryptor struct {
}
type NaClDecryptor struct {
NonDecryptor
sessionKey *[32]byte
instance *NaClDecryptorInstance
instanceDF *NaClDecryptorInstance
}
type NaClDecryptorInstance struct {
nonce [24]byte
currentWindow uint64
usedOffsets *bit.Set
previousUsedOffsets *bit.Set
}
func NewNaClDecryptorInstance(outbound bool) *NaClDecryptorInstance {
di := &NaClDecryptorInstance{usedOffsets: bit.New()}
if !outbound {
di.nonce[0] |= (1 << 7)
}
return di
}
type PacketDecodingError struct {
Desc string
}
func (pde PacketDecodingError) Error() string {
return fmt.Sprint("Failed to decode packet: ", pde.Desc)
}
func NewNonDecryptor() *NonDecryptor {
return &NonDecryptor{}
}
func (nd *NonDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) error {
for len(packet) >= (2 + NameSize + NameSize) {
srcNameByte := packet[:NameSize]
packet = packet[NameSize:]
dstNameByte := packet[:NameSize]
packet = packet[NameSize:]
length := binary.BigEndian.Uint16(packet[:2])
packet = packet[2:]
if len(packet) < int(length) {
return PacketDecodingError{Desc: fmt.Sprintf("too short; expected frame of length %d, got %d", length, len(packet))}
}
frame := packet[:length]
packet = packet[length:]
consumer(srcNameByte, dstNameByte, frame)
}
if len(packet) > 0 {
return PacketDecodingError{Desc: fmt.Sprintf("%d octets of trailing garbage", len(packet))}
}
return nil
}
func NewNaClDecryptor(sessionKey *[32]byte, outbound bool) *NaClDecryptor {
return &NaClDecryptor{
NonDecryptor: *NewNonDecryptor(),
sessionKey: sessionKey,
instance: NewNaClDecryptorInstance(outbound),
instanceDF: NewNaClDecryptorInstance(outbound)}
}
func (nd *NaClDecryptor) IterateFrames(packet []byte, consumer FrameConsumer) error {
if len(packet) < 8 {
return PacketDecodingError{Desc: fmt.Sprintf("encrypted UDP packet too short; expected length >= 8, got %d", len(packet))}
}
buf, success := nd.decrypt(packet)
if !success {
return PacketDecodingError{Desc: fmt.Sprint("UDP packet decryption failed")}
}
return nd.NonDecryptor.IterateFrames(buf, consumer)
}
func (nd *NaClDecryptor) decrypt(buf []byte) ([]byte, bool) {
seqNoAndDF := binary.BigEndian.Uint64(buf[:8])
df := (seqNoAndDF & (1 << 63)) != 0
seqNo := seqNoAndDF & ((1 << 63) - 1)
var di *NaClDecryptorInstance
if df {
di = nd.instanceDF
} else {
di = nd.instance
}
binary.BigEndian.PutUint64(di.nonce[16:24], seqNoAndDF)
result, success := secretbox.Open(nil, buf[8:], &di.nonce, nd.sessionKey)
if !success {
return nil, false
}
// Drop duplicates. We do this *after* decryption since we must
// not advance our state unless decryption succeeded. Doing so
// would open an easy attack vector where an adversary could
// inject a packet with a sequence number of (1 << 63) - 1,
// causing all subsequent genuine packets to get dropped.
offset, usedOffsets := di.advanceState(seqNo)
if usedOffsets == nil || usedOffsets.Contains(offset) {
// We have detected a possible replay attack, but it is
// possible we may have just received a very old packet, or
// duplication may have occurred in the network. So let's just
// drop the packet silently.
return nil, true
}
usedOffsets.Add(offset)
return result, success
}
// We record seen message sequence numbers in a sliding window of
// 2*WindowSize which slides in WindowSize increments. This allows us
// to process out-of-order delivery within the window, while
// accurately discarding duplicates. By contrast any messages with
// sequence numbers below the window are discarded as potential
// duplicates.
//
// There are two sets, corresponding to the lower and upper half of
// the window. We slide the window s.t. that 2nd set always contains
// the highest seen sequence number. We do this regardless of how far
// ahead of the current window that sequence number might be, so we
// can cope with large gaps resulting from packet loss.
const (
WindowSize = 20 // bits
)
func (di *NaClDecryptorInstance) advanceState(seqNo uint64) (int, *bit.Set) {
var (
offset = int(seqNo & ((1 << WindowSize) - 1))
window = seqNo >> WindowSize
)
switch delta := int64(window - di.currentWindow); {
case delta < -1:
return offset, nil
case delta == -1:
return offset, di.previousUsedOffsets
default:
return offset, di.usedOffsets
case delta == +1:
di.currentWindow = window
di.previousUsedOffsets = di.usedOffsets
di.usedOffsets = bit.New()
return offset, di.usedOffsets
case delta > +1:
di.currentWindow = window
di.previousUsedOffsets = bit.New()
di.usedOffsets = bit.New()
return offset, di.usedOffsets
}
}