]> Cypherpunks.ru repositories - govpn.git/commitdiff
Replace -noncediff with the hash keeping up to 256 seen nonces
authorSergey Matveev <stargrave@stargrave.org>
Sun, 24 May 2015 07:53:09 +0000 (10:53 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 24 May 2015 07:53:09 +0000 (10:53 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
12 files changed:
VERSION
doc/client.texi
doc/news.texi
doc/noncediff.texi [deleted file]
doc/server.texi
doc/todo.texi
doc/transport.texi
doc/user.texi
src/govpn/cmd/govpn-client/main.go
src/govpn/identify.go
src/govpn/transport.go
src/govpn/transport_test.go

diff --git a/VERSION b/VERSION
index eb39e5382f4f035e4d71c7f67712cdbfa6c0c335..2f4b60750dc3500b0e4cf08f316a960a7ca42b40 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.3
+3.4
index a6fc6b017602ffcb3cb9fac33dfbc98ec948c10d..e2c26fd727b83089a079e0d1afb14b899ce2d18f 100644 (file)
@@ -22,9 +22,6 @@ how to enter passphrase from stdin silently and store it in the file.
 @item -timeout
 @ref{Timeout} setting in seconds.
 
-@item -noncediff
-Allowable @ref{Nonce difference}.
-
 @item -noise
 Enable @ref{Noise}.
 
index b0b64372755ffcbdcbfa6b57059092a2cfd5add3..d3a9ab13a56cb511b7afdea17373fd83efa65282 100644 (file)
@@ -8,6 +8,9 @@
 @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
diff --git a/doc/noncediff.texi b/doc/noncediff.texi
deleted file mode 100644 (file)
index 7ce3aa2..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-@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).
index 810461ee5077016f6d2830e4cb5d0ca733ec5b8f..989a8a8a8f8e17f0964a7e15a36d82aed7d3fa4f 100644 (file)
@@ -46,10 +46,6 @@ Optional. Contains human readable username. Used to beauty output of
 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".
 
index 8e912e3a704d823f5b106b624c98f4aa2a0b9b14..481bb19415e8d8d2aec7a5aa989f7981c5031727 100644 (file)
@@ -2,7 +2,6 @@
 @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.
index b76286138f63af78bb9f43bc02ec5d774d1e5716..c1e0d7f02ad0dfa39788500d6c0c7165e9e9a48e 100644 (file)
@@ -34,12 +34,9 @@ Salsa20 output are used as a one-time key for @code{AUTH}. Next 256 bits
 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.
index 19e24e300b0cd204d6110d51d984f27474166c24..3702f8488b7c5a005e7b003cbcb9b466e7b62b93 100644 (file)
@@ -14,7 +14,6 @@ automate it using up and down shell scripts.
 * Identity::
 * PAKE:: Password Authenticated Key Agreement
 * Timeout::
-* Nonce difference::
 * MTU:: Maximum Transmission Unit
 * Stats::
 * Noise::
@@ -29,7 +28,6 @@ automate it using up and down shell scripts.
 @include identity.texi
 @include pake.texi
 @include timeout.texi
-@include noncediff.texi
 @include mtu.texi
 @include stats.texi
 @include noise.texi
index f21249061d4ed0e637f775beccca6c88291ed712..9b6567763ecea2e8f1787c9230f7f712aea0a78a 100644 (file)
@@ -39,7 +39,6 @@ var (
        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")
@@ -68,7 +67,6 @@ func main() {
        conf := &govpn.PeerConf{
                Id:          id,
                Timeout:     time.Second * time.Duration(timeout),
-               Noncediff:   *nonceDiff,
                NoiseEnable: *noisy,
                CPR:         *cpr,
                DSAPub:      pub,
index 663afe73599bf14b8fc40acf7e2b738c9ae6d7e6..c7675b6368b48cc62e7eef3180e8515ac1421e1b 100644 (file)
@@ -59,7 +59,6 @@ func (id PeerId) MarshalJSON() ([]byte, error) {
 type PeerConf struct {
        Id          *PeerId
        Timeout     time.Duration
-       Noncediff   int
        NoiseEnable bool
        CPR         int
        // This is passphrase verifier
@@ -180,7 +179,7 @@ func (id *PeerId) Conf() *PeerConf {
        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")
@@ -207,9 +206,6 @@ func (id *PeerId) Conf() *PeerConf {
        }
        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
        }
index f46eeefb62b3aae5c9af6fbebe46705d9688e1b7..2f5a345e57a6f5c1dd9034f7885408ff886db64b 100644 (file)
@@ -30,7 +30,8 @@ import (
 )
 
 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)
@@ -56,11 +57,14 @@ type Peer struct {
        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:"-"`
@@ -243,23 +247,24 @@ func newPeer(addr *net.UDPAddr, conf *PeerConf, nonce int, key *[SSize]byte) *Pe
                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
 }
@@ -286,14 +291,31 @@ func (p *Peer) UDPProcess(udpPkt []byte, tap io.Writer, ready chan struct{}) boo
                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()
index d12c26dacb68210e1f54ebf8d93ced4580d9eb3f..45f4d01cb6b6997bd871b7d9b6a5df29c87894d0 100644 (file)
@@ -24,7 +24,6 @@ func init() {
        conf = &PeerConf{
                Id:          peerId,
                Timeout:     time.Second * time.Duration(TimeoutDefault),
-               Noncediff:   1,
                NoiseEnable: false,
                CPR:         0,
        }
@@ -62,6 +61,8 @@ func BenchmarkDec(b *testing.B) {
        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()
                }