@item -timeout
@ref{Timeout} setting in seconds.
-@item -noncediff
-Allowable @ref{Nonce difference}.
-
@item -noise
Enable @ref{Noise}.
@item Ability to use external @ref{EGD}-compatible PRNGs. Now you are
able to use GoVPN even on systems with the bad @code{/dev/random},
providing higher quality entropy from external sources.
+@item Removed @code{-noncediff} option. It is replaced with in-memory
+storage of seen nonces, thus eliminating possible replay attacks at all
+without performance degradation related to inbound packets reordering.
@end itemize
@item Release 3.3
+++ /dev/null
-@node Nonce difference
-@section Nonce difference
-
-GoVPN prevents replay attacks by remembering the last used nonce in
-messages from the remote peer. All incoming messages must have higher
-nonce number (technically it is counter), otherwise they are dropped.
-
-Because of UDP nature that does not guarantee packet ordering during
-transmission, GoVPN will drop valid non-replayed UDP packets. That leads
-to performance decrease.
-
-In most cases there is no need in so strict nonce boundaries and
-@code{-noncediff} command line option allows to create the window of
-allowable nonce differences. This is trade-off between highest security
-and possible performance degradation. For example @code{-noncediff 128}
-works rather well (no packet drops) with 1 Gbps link with two switches.
-By default no nonce differences are allowed (highest security).
Optional. Contains @ref{Timeout} setting (decimal notation) in seconds.
Otherwise default minute timeout will be used.
-@item noncediff
-Optional. Contains allowable @ref{Nonce difference} setting (decimal
-notation).
-
@item noise
Optional. Contains either "1" (enable @ref{Noise} adding), or "0".
@unnumbered TODO
@itemize
-@item Replace -noncediff with in-memory storage of previously seen nonces.
@item Ability to tunnel only specified TCP connections, without full
featured VPN solution. Similar to ssh -R.
@item Ability to work over HTTP, WebSockets and something similar.
of Salsa20 are ignored. All remaining output is XORed with the data,
encrypting it.
-To prevent replay attacks we remember latest @code{SERIAL} from the
-remote peer. If received message's @code{SERIAL} is not greater that the
-saved one, then drop it. Optionally, because some UDP packets can be
-reordered during transmission, we can allow some window for valid
-serials with the @code{-noncediff} option. @code{-noncediff 10} with
-current saved serial state equals to 78 allows messages with 68…78
-serials. That time window can be used by attacker to replay packets, so
-by default it equals to 1. However it can improve performance because of
-rearranged UDP packets.
+To prevent replay attacks we must remember received @code{SERIAL}s and
+if meet one, then drop it. Basically we could just store latest number
+and check if received one is greater, but because of UDP packets
+reordering this can lead to valid packets dropping and overall
+performance degradation. We store 256 seen nonces in hash structure, in
+two swapping buckets.
* Identity::
* PAKE:: Password Authenticated Key Agreement
* Timeout::
-* Nonce difference::
* MTU:: Maximum Transmission Unit
* Stats::
* Noise::
@include identity.texi
@include pake.texi
@include timeout.texi
-@include noncediff.texi
@include mtu.texi
@include stats.texi
@include noise.texi
downPath = flag.String("down", "", "Path to down-script")
stats = flag.String("stats", "", "Enable stats retrieving on host:port")
mtu = flag.Int("mtu", 1452, "MTU for outgoing packets")
- nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference")
timeoutP = flag.Int("timeout", 60, "Timeout seconds")
noisy = flag.Bool("noise", false, "Enable noise appending")
cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
conf := &govpn.PeerConf{
Id: id,
Timeout: time.Second * time.Duration(timeout),
- Noncediff: *nonceDiff,
NoiseEnable: *noisy,
CPR: *cpr,
DSAPub: pub,
type PeerConf struct {
Id *PeerId
Timeout time.Duration
- Noncediff int
NoiseEnable bool
CPR int
// This is passphrase verifier
if dummyConf != nil {
return dummyConf
}
- conf := PeerConf{Id: id, Noncediff: 1, NoiseEnable: false, CPR: 0}
+ conf := PeerConf{Id: id, NoiseEnable: false, CPR: 0}
peerPath := path.Join(PeersPath, id.String())
verPath := path.Join(peerPath, "verifier")
}
conf.Timeout = time.Second * time.Duration(timeout)
- if val, err := readIntFromFile(path.Join(peerPath, "noncediff")); err == nil {
- conf.Noncediff = val
- }
if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
conf.NoiseEnable = true
}
)
const (
- NonceSize = 8
+ NonceSize = 8
+ NonceBucketSize = 128
// S20BS is Salsa20's internal blocksize in bytes
S20BS = 64
// Maximal amount of bytes transfered with single key (4 GiB)
CPRCycle time.Duration `json:"-"`
// Cryptography related
- Key *[SSize]byte `json:"-"`
- Noncediff int
- NonceOur uint64 `json:"-"`
- NonceRecv uint64 `json:"-"`
- NonceCipher *xtea.Cipher `json:"-"`
+ Key *[SSize]byte `json:"-"`
+ NonceOur uint64 `json:"-"`
+ NonceRecv uint64 `json:"-"`
+ NonceCipher *xtea.Cipher `json:"-"`
+ nonceBucket0 map[uint64]struct{}
+ nonceBucket1 map[uint64]struct{}
+ nonceFound bool
+ nonceBucketN int32
// Timers
Timeout time.Duration `json:"-"`
timeout = timeout / TimeoutHeartbeat
}
peer := Peer{
- Addr: addr,
- Timeout: timeout,
- Established: now,
- LastPing: now,
- Id: conf.Id,
- NoiseEnable: noiseEnable,
- CPR: conf.CPR,
- CPRCycle: cprCycle,
- Noncediff: conf.Noncediff,
- NonceOur: uint64(conf.Noncediff + nonce),
- NonceRecv: uint64(conf.Noncediff + 0),
- Key: key,
- NonceCipher: newNonceCipher(key),
- buf: make([]byte, MTU+S20BS),
- tag: new([poly1305.TagSize]byte),
- keyAuth: new([SSize]byte),
- nonce: make([]byte, NonceSize),
+ Addr: addr,
+ Timeout: timeout,
+ Established: now,
+ LastPing: now,
+ Id: conf.Id,
+ NoiseEnable: noiseEnable,
+ CPR: conf.CPR,
+ CPRCycle: cprCycle,
+ NonceOur: uint64(nonce),
+ NonceRecv: uint64(0),
+ nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
+ nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
+ Key: key,
+ NonceCipher: newNonceCipher(key),
+ buf: make([]byte, MTU+S20BS),
+ tag: new([poly1305.TagSize]byte),
+ keyAuth: new([SSize]byte),
+ nonce: make([]byte, NonceSize),
}
return &peer
}
p.FramesUnauth++
return false
}
+
+ // Check if received nonce is known to us in either of two buckets.
+ // If yes, then this is ignored duplicate.
+ // Check from the oldest bucket, as in most cases this will result
+ // in constant time check.
+ // If Bucket0 is filled, then it becomes Bucket1.
p.NonceCipher.Decrypt(p.buf, udpPkt[:NonceSize])
+ ready <- struct{}{}
p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize])
- if int(p.NonceRecv)-p.Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-p.Noncediff {
- ready <- struct{}{}
+ if _, p.nonceFound = p.nonceBucket1[p.NonceRecv]; p.nonceFound {
p.FramesDup++
return false
}
- ready <- struct{}{}
+ if _, p.nonceFound = p.nonceBucket0[p.NonceRecv]; p.nonceFound {
+ p.FramesDup++
+ return false
+ }
+ p.nonceBucket0[p.NonceRecv] = struct{}{}
+ p.nonceBucketN++
+ if p.nonceBucketN == NonceBucketSize {
+ p.nonceBucket1 = p.nonceBucket0
+ p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
+ p.nonceBucketN = 0
+ }
+
p.FramesIn++
p.BytesIn += int64(p.size)
p.LastPing = time.Now()
conf = &PeerConf{
Id: peerId,
Timeout: time.Second * time.Duration(TimeoutDefault),
- Noncediff: 1,
NoiseEnable: false,
CPR: 0,
}
peer = newPeer(addr, conf, 128, new([SSize]byte))
b.ResetTimer()
for i := 0; i < b.N; i++ {
+ peer.nonceBucket0 = make(map[uint64]struct{}, 1)
+ peer.nonceBucket1 = make(map[uint64]struct{}, 1)
if !peer.UDPProcess(ciphertext, dummy, ready) {
b.Fail()
}