@item Verifier password hashing algorithm
@url{https://password-hashing.net/#argon2, Argon2d}.
@item Packet overhead
-26 bytes per packet.
+25 bytes per packet.
@item Handshake overhead
4 UDP (2 from client, 2 from server) packets (round-trips for TCP),
264 bytes total payload.
+--< ENCRYPT(KEY, NONCE, PAYLOAD)
^ ^
| |
- | +--< SIZE || DATA [|| NOISE]
+ | +--< DATA || PAD [|| ZEROS]
|
+--< PRP(PRP_KEY, SERIAL)
@end verbatim
Salsa20's output is ignored and only remaining is XORed with ther data,
encrypting it.
-@code{SIZE} is big-endian @emph{uint16} storing length of the
-@code{DATA}.
-
-@code{NOISE} is optional. It is just some junk data, intended to fill up
-packet to MTU size. This is useful for concealing payload packets length.
+@code{DATA} is padded with @code{PAD} (0x80 byte). Optional @code{ZEROS}
+may follow, to fillup packet with the junk to conceal pyload packet
+length.
@code{AUTH} is Poly1305 authentication function. First 256 bits of
Salsa20's output are used as a one-time key for @code{AUTH}.
package govpn
import (
+ "bytes"
"encoding/binary"
"io"
"sync"
S20BS = 64
// Maximal amount of bytes transfered with single key (4 GiB)
MaxBytesPerKey int64 = 1 << 32
- // Size of packet's size mark in bytes
- PktSizeSize = 2
// Heartbeat rate, relative to Timeout
TimeoutHeartbeat = 4
// Minimal valid packet length
- MinPktLength = 2 + 16 + 8
+ MinPktLength = 1 + 16 + 8
+ // Padding byte
+ PadByte = byte(0x80)
)
func newNonceCipher(key *[32]byte) *xtea.Cipher {
bufR []byte
tagR *[TagSize]byte
keyAuthR *[SSize]byte
- pktSizeR uint16
+ pktSizeR int
// Transmitter
BusyT sync.Mutex `json:"-"`
p.BusyT.Lock()
// Zero size is a heartbeat packet
+ SliceZero(p.bufT)
if len(data) == 0 {
// If this heartbeat is necessary
if !p.LastSent.Add(p.Timeout).Before(p.now) {
p.BusyT.Unlock()
return
}
- p.bufT[S20BS+0] = 0
- p.bufT[S20BS+1] = 0
+ p.bufT[S20BS+0] = PadByte
p.HeartbeatSent++
} else {
// Copy payload to our internal buffer and we are ready to
// accept the next one
- binary.BigEndian.PutUint16(
- p.bufT[S20BS:S20BS+PktSizeSize],
- uint16(len(data)),
- )
- copy(p.bufT[S20BS+PktSizeSize:], data)
+ copy(p.bufT[S20BS:], data)
+ p.bufT[S20BS+len(data)] = PadByte
p.BytesPayloadOut += int64(len(data))
}
} else if p.EncLess {
p.frameT = p.bufT[S20BS : S20BS+MTU]
} else {
- p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize]
+ p.frameT = p.bufT[S20BS : S20BS+len(data)+1+NonceSize]
}
p.nonceOur += 2
binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
}
out = append(out, p.frameT[len(p.frameT)-NonceSize:]...)
} else {
- for i := 0; i < SSize; i++ {
- p.bufT[i] = 0
- }
salsa20.XORKeyStream(
p.bufT[:S20BS+len(p.frameT)-NonceSize],
p.bufT[:S20BS+len(p.frameT)-NonceSize],
p.BusyR.Unlock()
return false
}
- out = p.bufR[S20BS:]
+ out = p.bufR[S20BS : S20BS+len(data)-TagSize-NonceSize]
}
// Check if received nonce is known to us in either of two buckets.
p.FramesIn++
atomic.AddInt64(&p.BytesIn, int64(len(data)))
p.LastPing = time.Now()
- p.pktSizeR = binary.BigEndian.Uint16(out[:PktSizeSize])
+ p.pktSizeR = bytes.LastIndexByte(out, PadByte)
+ if p.pktSizeR == -1 {
+ p.BusyR.Unlock()
+ return false
+ }
+ // Validate the pad
+ for i := p.pktSizeR + 1; i < len(out); i++ {
+ if out[i] != 0 {
+ p.BusyR.Unlock()
+ return false
+ }
+ }
if p.pktSizeR == 0 {
p.HeartbeatRecv++
p.BusyR.Unlock()
return true
}
- if int(p.pktSizeR) > len(data)-MinPktLength {
- return false
- }
p.BytesPayloadIn += int64(p.pktSizeR)
- tap.Write(out[PktSizeSize : PktSizeSize+p.pktSizeR])
+ tap.Write(out[:p.pktSizeR])
p.BusyR.Unlock()
return true
}
}
}
-func TestSymmetricEncLess(t *testing.T) {
+func TestSymmetricNoise(t *testing.T) {
peerd := newPeer(true, "foo", Dummy{nil}, conf, new([SSize]byte))
peer.NoiseEnable = true
+ peerd.NoiseEnable = true
+ f := func(payload []byte) bool {
+ if len(payload) == 0 {
+ return true
+ }
+ peer.EthProcess(payload)
+ return peerd.PktProcess(ciphertext, Dummy{nil}, true)
+ }
+ if err := quick.Check(f, nil); err != nil {
+ t.Error(err)
+ }
+ peer.NoiseEnable = true
+}
+
+func TestSymmetricEncLess(t *testing.T) {
+ peerd := newPeer(true, "foo", Dummy{nil}, conf, new([SSize]byte))
peer.EncLess = true
+ peer.NoiseEnable = true
peerd.EncLess = true
peerd.NoiseEnable = true
f := func(payload []byte) bool {
)
// Return maximal acceptable TAP interface MTU. This is daemon's MTU
-// minus nonce, MAC, packet size mark and Ethernet header sizes.
+// minus nonce, MAC, pad and Ethernet header sizes.
func TAPMaxMTU() int {
- return MTU - poly1305.TagSize - NonceSize - PktSizeSize - EtherSize
+ return MTU - poly1305.TagSize - NonceSize - 1 - EtherSize
}
func NewTAP(ifaceName string) (*TAP, error) {