]> Cypherpunks.ru repositories - govpn.git/commitdiff
Replace XTEA with BLAKE2b-based MAC
authorSergey Matveev <stargrave@stargrave.org>
Mon, 9 May 2016 14:02:19 +0000 (17:02 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Mon, 9 May 2016 16:12:02 +0000 (19:12 +0300)
14 files changed:
doc/developer.texi
doc/encless.texi
doc/handshake.texi
doc/news.ru.texi
doc/news.texi
doc/transport.texi
src/cypherpunks.ru/govpn/cmd/govpn-client/main.go
src/cypherpunks.ru/govpn/cmd/govpn-client/tcp.go
src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go
src/cypherpunks.ru/govpn/cmd/govpn-server/tcp.go
src/cypherpunks.ru/govpn/handshake.go
src/cypherpunks.ru/govpn/identity.go
src/cypherpunks.ru/govpn/peer.go
src/cypherpunks.ru/govpn/peer_test.go

index 4293f800c06d8084790c564e26768c911d4509a4..b32f1cb0150ec30a76f0d670e9a7f0aee8fb8cd6 100644 (file)
@@ -4,8 +4,8 @@
 Pay attention how to get @ref{Sources, development source code}.
 
 @table @asis
-@item Nonce and identity encryption
-    @url{http://www.cix.co.uk/~klockstone/xtea.pdf, XTEA}.
+@item Nonce and identity obfuscation
+    @url{https://blake2.net/, BLAKE2b-MAC}.
 @item Data encryption
     @url{http://cr.yp.to/snuffle.html, Salsa20}.
 @item Message authentication
@@ -24,7 +24,7 @@ Pay attention how to get @ref{Sources, development source code}.
     @url{http://theory.lcs.mit.edu/~cis/pubs/rivest/fusion.ps,
     All-Or-Nothing-Transformed} (based on
     @url{http://cseweb.ucsd.edu/~mihir/papers/oaep.html, OAEP} using
-    Salsa20 with @url{https://blake2.net/, BLAKE2b-256} based
+    Salsa20 with BLAKE2b-256 based
     @url{http://crypto.stanford.edu/~dabo/abstracts/saep.html, SAEP+}
     checksums) data with 128-bits of feeded random.
 @item Packet overhead
index 6d44191ebdc1c5d492ffc321568e146f8d2acada..6d694b2a984217740f0dff2c321aaa088c28d4c3 100644 (file)
@@ -25,12 +25,5 @@ signature algorithms. No encryption and steganography involved.
 In this mode each outgoing packet became larger on 4128 bytes and
 @ref{Noise, noise} is forcefully enabled. So this is resource hungry mode!
 
-@strong{Beware}: by default packet serial numbers are still processed
-through the XTEA encryption. It is not required for confidentiality and
-security, but for randomizing some parts of the traffic to make it
-indistinguishable from the noise, for making it more DPI-proof. It
-safely can be disabled, turned off or maybe its keys even can be
-revealed without security and forward secrecy loss.
-
 See @code{govpn/cnw} and @code{govpn/aont} packages for details of AONT
 and chaffing operations.
index f19fde0c153d9f0b7afe9bd2411aee9916d233a4..75a65085f712082136675d6b34dc54860db1cd54 100644 (file)
@@ -3,10 +3,10 @@
 
 @verbatiminclude handshake.utxt
 
-Each handshake message ends with so called @code{IDtag}: it is an XTEA
-encrypted first 64 bits of each message with client's @ref{Identity} as
-a key. It is used to transmit identity and to mark packet as handshake
-message.
+Each handshake message ends with so called @code{IDtag}: it is
+BLAKE2b-MAC of the first 64 bits of the handshake message, with client's
+@ref{Identity} used as a key. It is used to transmit identity and to
+mark packet as handshake message.
 
 If @ref{Noise, noise} is enabled, then data is padded to fill up packet
 to MTU's size.
index da0216816832f2b661e12bbe07cfbbddb07d89b5..a654dbff73854a5e56691b1043a7dcab2d2ceb05 100644 (file)
@@ -7,6 +7,9 @@
 @item Опциональная возможность использовать syslog для журналирования,
 с @url{https://tools.ietf.org/html/rfc5424, RFC 5424}-похожими
 структурированными записями.
+@item XTEA алгоритм больше не используется для обфускации nonce. Вместо
+него BLAKE2b-MAC. Теперь нешифрованный режим действительно не зависит
+от алгоритмов шифрования.
 @end itemize
 
 @node Релиз 5.7
index cdb7d982f085ed5b72b2b27b1af4e3556266fe43..70e5051cf21a1b84b2a6c447467078b1186c4dad 100644 (file)
@@ -9,6 +9,9 @@ See also this page @ref{Новости, on russian}.
 @item Optional ability to use syslog for logging, with
 @url{https://tools.ietf.org/html/rfc5424, RFC 5424}-like
 structured records.
+@item XTEA algorithm is not used anymore for nonce obfuscation.
+BLAKE2-MAC is used instead. Encryptionless mode now really does not
+depend on encryption functions.
 @end itemize
 
 @node Release 5.7
index 4b8413b23e470b0cb58543ecaa4c92c89d572731..cdb5950524820360b2d15fa7fb749e473ab154b3 100644 (file)
@@ -21,23 +21,18 @@ TAG || ENCRYPTED || NONCE --> PACKET
                     |       |
                     |       +--< DATA || PAD [|| ZEROS]
                     |
-                    +--< PRP(PRP_KEY, SERIAL)
+                    +--< MAC(MAC_KEY, SERIAL)
 @end verbatim
 
 @code{SERIAL} is message's serial number. Odds are reserved for
 client (to server) messages, evens for server (to client) messages.
 
-@code{PRP} is XTEA block cipher algorithm used here as PRP (pseudo
-random permutation function) to obfuscate @code{SERIAL}. Plaintext
-@code{SERIAL} state is kept in peers internal state, but encrypted
-before transmission.
-
-XTEA's encryption key @code{PRP_KEY} is the first 128-bit of Salsa20's
-output with established common key and zero nonce (message nonces start
-from 1).
+@code{MAC} is BLAKE2b-MAC used to obfuscate @code{SERIAL}. MAC's key
+@code{MAC_KEY} is the first 256-bit of Salsa20's output with established
+common key and zero nonce (message nonces start from 1).
 
 @verbatim
-PRP_KEY = 128bit(ENCRYPT(KEY, 0))
+MAC_KEY = 256bit(ENCRYPT(KEY, 0))
 @end verbatim
 
 @code{ENCRYPT} is Salsa20 stream cipher, with established session
@@ -63,7 +58,7 @@ In @ref{Encless, encryptionless mode} this scheme is slightly different:
 @verbatim
  PACKET = ENCODED || NONCE
 ENCODED = ENCLESS(DATA || PAD || ZEROS)
-  NONCE = PRP(PRP_KEY, SERIAL)
+  NONCE = MAC(MAC_KEY, SERIAL)
 @end verbatim
 
 @code{ENCLESS} is AONT and chaffing function. There is no need in
index 38bdae3bec704dd0e099bb9b7b0771577106052f..3d8cbe65953fd935f480a9aefde3efec28ed61d8 100644 (file)
@@ -57,7 +57,7 @@ var (
        timeout     int
        firstUpCall bool = true
        knownPeers  govpn.KnownPeers
-       idsCache    *govpn.CipherCache
+       idsCache    *govpn.MACCache
 )
 
 func main() {
@@ -108,7 +108,7 @@ func main() {
                Verifier: verifier,
                DSAPriv:  priv,
        }
-       idsCache = govpn.NewCipherCache()
+       idsCache = govpn.NewMACCache()
        confs := map[govpn.PeerId]*govpn.PeerConf{*verifier.Id: conf}
        idsCache.Update(&confs)
        log.Println(govpn.VersionGet())
index dc0eb6008a7ede9aa469bac0f553b15e7b7eae02..57a412d31d687772c691fa2dc07cb7a65ddf8946 100644 (file)
@@ -98,8 +98,6 @@ HandshakeCycle:
                return
        }
 
-       nonceExpectation := make([]byte, govpn.NonceSize)
-       peer.NonceExpectation(nonceExpectation)
        prev = 0
        var i int
 TransportCycle:
@@ -126,7 +124,7 @@ TransportCycle:
                if prev < govpn.MinPktLength {
                        continue
                }
-               i = bytes.Index(buf[:prev], nonceExpectation)
+               i = bytes.Index(buf[:prev], peer.NonceExpect)
                if i == -1 {
                        continue
                }
@@ -140,7 +138,6 @@ TransportCycle:
                        rehandshaking <- struct{}{}
                        break TransportCycle
                }
-               peer.NonceExpectation(nonceExpectation)
                copy(buf, buf[i+govpn.NonceSize:prev])
                prev = prev - i - govpn.NonceSize
                goto CheckMore
index 28ee501443a0a7dbcc3ceb332c3eee22b80f7644..ea61c813f22494be3b469b9736f24c99ef436df6 100644 (file)
@@ -35,7 +35,7 @@ const (
 
 var (
        confs    map[govpn.PeerId]*govpn.PeerConf
-       idsCache *govpn.CipherCache
+       idsCache *govpn.MACCache
 )
 
 func confRead() (*map[govpn.PeerId]*govpn.PeerConf, error) {
@@ -99,7 +99,7 @@ func confRefresh() error {
 }
 
 func confInit() {
-       idsCache = govpn.NewCipherCache()
+       idsCache = govpn.NewMACCache()
        if err := confRefresh(); err != nil {
                log.Fatalln(err)
        }
index 7a8f7384af03de520e36527fb6b84bc6aa5284e0..bfb4709771e5cdc8680aea9ada107a5bae908c69 100644 (file)
@@ -164,8 +164,6 @@ func handleTCP(conn net.Conn) {
                return
        }
 
-       nonceExpectation := make([]byte, govpn.NonceSize)
-       peer.NonceExpectation(nonceExpectation)
        prev = 0
        var i int
        for {
@@ -183,7 +181,7 @@ func handleTCP(conn net.Conn) {
                if prev < govpn.MinPktLength {
                        continue
                }
-               i = bytes.Index(buf[:prev], nonceExpectation)
+               i = bytes.Index(buf[:prev], peer.NonceExpect)
                if i == -1 {
                        continue
                }
@@ -194,7 +192,6 @@ func handleTCP(conn net.Conn) {
                        )
                        break
                }
-               peer.NonceExpectation(nonceExpectation)
                copy(buf, buf[i+govpn.NonceSize:prev])
                prev = prev - i - govpn.NonceSize
                goto CheckMore
index 9d5f6888bfef6c4aea98e34bfcff760c9b1de361..9c536b329371fe7a5a946229407285214dbd5e30 100644 (file)
@@ -30,7 +30,6 @@ import (
        "github.com/dchest/blake2b"
        "golang.org/x/crypto/curve25519"
        "golang.org/x/crypto/salsa20"
-       "golang.org/x/crypto/xtea"
 )
 
 const (
@@ -134,14 +133,12 @@ func NewHandshake(addr string, conn io.Writer, conf *PeerConf) *Handshake {
 
 // Generate ID tag from client identification and data.
 func idTag(id *PeerId, timeSync int, data []byte) []byte {
-       ciph, err := xtea.NewCipher(id[:])
-       if err != nil {
-               panic(err)
-       }
-       enc := make([]byte, xtea.BlockSize)
+       enc := make([]byte, 8)
        copy(enc, data)
        AddTimeSync(timeSync, enc)
-       ciph.Encrypt(enc, enc)
+       mac := blake2b.NewMAC(8, id[:])
+       mac.Write(enc)
+       mac.Sum(enc[:0])
        return enc
 }
 
@@ -159,7 +156,7 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake {
        }
        var enc []byte
        if conf.Noise {
-               enc = make([]byte, conf.MTU-xtea.BlockSize-RSize)
+               enc = make([]byte, conf.MTU-8-RSize)
        } else {
                enc = make([]byte, 32)
        }
@@ -197,7 +194,7 @@ func (h *Handshake) Server(data []byte) *Peer {
                        out, err := EnclessDecode(
                                h.dsaPubH,
                                h.rNonce[:],
-                               data[RSize:len(data)-xtea.BlockSize],
+                               data[RSize:len(data)-8],
                        )
                        if err != nil {
                                log.Println("Unable to decode packet from", h.addr, err)
@@ -205,12 +202,7 @@ func (h *Handshake) Server(data []byte) *Peer {
                        }
                        copy(cDHRepr[:], out)
                } else {
-                       salsa20.XORKeyStream(
-                               cDHRepr[:],
-                               data[RSize:RSize+32],
-                               h.rNonce[:],
-                               h.dsaPubH,
-                       )
+                       salsa20.XORKeyStream(cDHRepr[:], data[RSize:RSize+32], h.rNonce[:], h.dsaPubH)
                }
 
                // Generate DH keypair
@@ -247,9 +239,9 @@ func (h *Handshake) Server(data []byte) *Peer {
                }
                var encRs []byte
                if h.Conf.Noise && !h.Conf.Encless {
-                       encRs = make([]byte, h.Conf.MTU-len(encPub)-xtea.BlockSize)
+                       encRs = make([]byte, h.Conf.MTU-len(encPub)-8)
                } else if h.Conf.Encless {
-                       encRs = make([]byte, h.Conf.MTU-xtea.BlockSize)
+                       encRs = make([]byte, h.Conf.MTU-8)
                } else {
                        encRs = make([]byte, RSize+SSize)
                }
@@ -278,7 +270,7 @@ func (h *Handshake) Server(data []byte) *Peer {
                        dec, err = EnclessDecode(
                                h.key,
                                h.rNonceNext(1),
-                               data[:len(data)-xtea.BlockSize],
+                               data[:len(data)-8],
                        )
                        if err != nil {
                                log.Println("Unable to decode packet from", h.addr, err)
@@ -308,7 +300,7 @@ func (h *Handshake) Server(data []byte) *Peer {
                // Send final answer to client
                var enc []byte
                if h.Conf.Noise {
-                       enc = make([]byte, h.Conf.MTU-xtea.BlockSize)
+                       enc = make([]byte, h.Conf.MTU-8)
                } else {
                        enc = make([]byte, RSize)
                }
@@ -364,12 +356,7 @@ func (h *Handshake) Client(data []byte) *Peer {
                        }
                        copy(sDHRepr[:], tmp[:32])
                } else {
-                       salsa20.XORKeyStream(
-                               sDHRepr[:],
-                               data[:32],
-                               h.rNonceNext(1),
-                               h.dsaPubH,
-                       )
+                       salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH)
                }
 
                // Compute shared key
@@ -384,7 +371,7 @@ func (h *Handshake) Client(data []byte) *Peer {
                        tmp, err = EnclessDecode(
                                h.key,
                                h.rNonce[:],
-                               data[len(data)/2:len(data)-xtea.BlockSize],
+                               data[len(data)/2:len(data)-8],
                        )
                        if err != nil {
                                log.Println("Unable to decode packet from", h.addr, err)
@@ -394,12 +381,7 @@ func (h *Handshake) Client(data []byte) *Peer {
                        copy(h.sServer[:], tmp[RSize:RSize+SSize])
                } else {
                        decRs := make([]byte, RSize+SSize)
-                       salsa20.XORKeyStream(
-                               decRs,
-                               data[SSize:SSize+RSize+SSize],
-                               h.rNonce[:],
-                               h.key,
-                       )
+                       salsa20.XORKeyStream(decRs, data[SSize:SSize+RSize+SSize], h.rNonce[:], h.key)
                        copy(h.rServer[:], decRs[:RSize])
                        copy(h.sServer[:], decRs[RSize:])
                }
@@ -417,7 +399,7 @@ func (h *Handshake) Client(data []byte) *Peer {
 
                var enc []byte
                if h.Conf.Noise {
-                       enc = make([]byte, h.Conf.MTU-xtea.BlockSize)
+                       enc = make([]byte, h.Conf.MTU-8)
                } else {
                        enc = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
                }
@@ -445,11 +427,7 @@ func (h *Handshake) Client(data []byte) *Peer {
                // Decrypt rClient
                var dec []byte
                if h.Conf.Encless {
-                       dec, err = EnclessDecode(
-                               h.key,
-                               h.rNonceNext(2),
-                               data[:len(data)-xtea.BlockSize],
-                       )
+                       dec, err = EnclessDecode(h.key, h.rNonceNext(2), data[:len(data)-8])
                        if err != nil {
                                log.Println("Unable to decode packet from", h.addr, err)
                                return nil
index f85631b01839639b8bf981572f4c8b6db106c984..4dc74eed0e330f4a3ec73570e0c9d73b2503baee 100644 (file)
@@ -22,11 +22,12 @@ import (
        "crypto/subtle"
        "encoding/base64"
        "encoding/binary"
+       "hash"
        "log"
        "sync"
        "time"
 
-       "golang.org/x/crypto/xtea"
+       "github.com/dchest/blake2b"
 )
 
 const (
@@ -43,42 +44,42 @@ func (id PeerId) MarshalJSON() ([]byte, error) {
        return []byte(`"` + id.String() + `"`), nil
 }
 
-type CipherAndTimeSync struct {
-       c *xtea.Cipher
-       t int
+type MACAndTimeSync struct {
+       mac hash.Hash
+       ts  int
+       l   sync.Mutex
 }
 
-type CipherCache struct {
-       c map[PeerId]*CipherAndTimeSync
-       l sync.RWMutex
+type MACCache struct {
+       cache map[PeerId]*MACAndTimeSync
+       l     sync.RWMutex
 }
 
-func NewCipherCache() *CipherCache {
-       return &CipherCache{c: make(map[PeerId]*CipherAndTimeSync)}
+func NewMACCache() *MACCache {
+       return &MACCache{cache: make(map[PeerId]*MACAndTimeSync)}
 }
 
-// Remove disappeared keys, add missing ones with initialized ciphers.
-func (cc *CipherCache) Update(peers *map[PeerId]*PeerConf) {
-       cc.l.Lock()
-       for pid, _ := range cc.c {
+// Remove disappeared keys, add missing ones with initialized MACs.
+func (mc *MACCache) Update(peers *map[PeerId]*PeerConf) {
+       mc.l.Lock()
+       for pid, _ := range mc.cache {
                if _, exists := (*peers)[pid]; !exists {
                        log.Println("Cleaning key:", pid)
-                       delete(cc.c, pid)
+                       delete(mc.cache, pid)
                }
        }
        for pid, pc := range *peers {
-               if _, exists := cc.c[pid]; exists {
-                       cc.c[pid].t = pc.TimeSync
+               if _, exists := mc.cache[pid]; exists {
+                       mc.cache[pid].ts = pc.TimeSync
                } else {
                        log.Println("Adding key", pid)
-                       cipher, err := xtea.NewCipher(pid[:])
-                       if err != nil {
-                               panic(err)
+                       mc.cache[pid] = &MACAndTimeSync{
+                               mac: blake2b.NewMAC(8, pid[:]),
+                               ts:  pc.TimeSync,
                        }
-                       cc.c[pid] = &CipherAndTimeSync{cipher, pc.TimeSync}
                }
        }
-       cc.l.Unlock()
+       mc.l.Unlock()
 }
 
 // If timeSync > 0, then XOR timestamp with the data.
@@ -93,24 +94,29 @@ func AddTimeSync(ts int, data []byte) {
        }
 }
 
-// Try to find peer's identity (that equals to an encryption key)
+// Try to find peer's identity (that equals to MAC)
 // by taking first blocksize sized bytes from data at the beginning
 // as plaintext and last bytes as cyphertext.
-func (cc *CipherCache) Find(data []byte) *PeerId {
-       if len(data) < xtea.BlockSize*2 {
+func (mc *MACCache) Find(data []byte) *PeerId {
+       if len(data) < 8*2 {
                return nil
        }
-       buf := make([]byte, xtea.BlockSize)
-       cc.l.RLock()
-       for pid, ct := range cc.c {
-               ct.c.Decrypt(buf, data[len(data)-xtea.BlockSize:])
-               AddTimeSync(ct.t, buf)
-               if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
+       buf := make([]byte, 8)
+       mc.l.RLock()
+       for pid, mt := range mc.cache {
+               copy(buf, data)
+               AddTimeSync(mt.ts, buf)
+               mt.l.Lock()
+               mt.mac.Reset()
+               mt.mac.Write(buf)
+               mt.mac.Sum(buf[:0])
+               mt.l.Unlock()
+               if subtle.ConstantTimeCompare(buf, data[len(data)-8:]) == 1 {
                        ppid := PeerId(pid)
-                       cc.l.RUnlock()
+                       mc.l.RUnlock()
                        return &ppid
                }
        }
-       cc.l.RUnlock()
+       mc.l.RUnlock()
        return nil
 }
index 9312b64b69710cef8a3993d7a9ba1b8444811e7e..fc361790237df8cfa17876864089757018c0faa7 100644 (file)
@@ -20,6 +20,7 @@ package govpn
 
 import (
        "bytes"
+       "crypto/subtle"
        "encoding/binary"
        "io"
        "log"
@@ -27,14 +28,14 @@ import (
        "sync/atomic"
        "time"
 
+       "github.com/dchest/blake2b"
        "golang.org/x/crypto/poly1305"
        "golang.org/x/crypto/salsa20"
-       "golang.org/x/crypto/xtea"
 )
 
 const (
        NonceSize       = 8
-       NonceBucketSize = 128
+       NonceBucketSize = 256
        TagSize         = poly1305.TagSize
        // S20BS is Salsa20's internal blocksize in bytes
        S20BS = 64
@@ -48,19 +49,23 @@ const (
        PadByte = byte(0x80)
 )
 
-func newNonceCipher(key *[32]byte) *xtea.Cipher {
-       nonceKey := make([]byte, 16)
-       salsa20.XORKeyStream(
-               nonceKey,
-               make([]byte, 32),
-               make([]byte, xtea.BlockSize),
-               key,
-       )
-       ciph, err := xtea.NewCipher(nonceKey)
-       if err != nil {
-               panic(err)
-       }
-       return ciph
+func newNonces(key *[32]byte, i uint64) chan *[NonceSize]byte {
+       macKey := make([]byte, 32)
+       salsa20.XORKeyStream(macKey, make([]byte, 32), make([]byte, 8), key)
+       mac := blake2b.NewMAC(NonceSize, macKey)
+       nonces := make(chan *[NonceSize]byte, NonceBucketSize*3)
+       go func() {
+               for {
+                       buf := new([NonceSize]byte)
+                       binary.BigEndian.PutUint64(buf[:], i)
+                       mac.Write(buf[:])
+                       mac.Sum(buf[:0])
+                       nonces <- buf
+                       mac.Reset()
+                       i += 2
+               }
+       }()
+       return nonces
 }
 
 type Peer struct {
@@ -88,18 +93,7 @@ type Peer struct {
        Encless     bool
        MTU         int
 
-       // Cryptography related
-       Key          *[SSize]byte `json:"-"`
-       NonceCipher  *xtea.Cipher `json:"-"`
-       nonceRecv    uint64
-       nonceLatest  uint64
-       nonceOur     uint64
-       NonceExpect  uint64 `json:"-"`
-       nonceBucket0 map[uint64]struct{}
-       nonceBucket1 map[uint64]struct{}
-       nonceFound0  bool
-       nonceFound1  bool
-       nonceBucketN int32
+       key *[SSize]byte `json:"-"`
 
        // Timers
        Timeout     time.Duration `json:"-"`
@@ -113,12 +107,24 @@ type Peer struct {
        keyAuthR *[SSize]byte
        pktSizeR int
 
+       // UDP-related
+       noncesR      chan *[NonceSize]byte
+       nonceRecv    [NonceSize]byte
+       nonceBucketL map[[NonceSize]byte]struct{}
+       nonceBucketM map[[NonceSize]byte]struct{}
+       nonceBucketH map[[NonceSize]byte]struct{}
+
+       // TCP-related
+       NonceExpect  []byte `json:"-"`
+       noncesExpect chan *[NonceSize]byte
+
        // Transmitter
        BusyT    sync.Mutex `json:"-"`
        bufT     []byte
        tagT     *[TagSize]byte
        keyAuthT *[SSize]byte
        frameT   []byte
+       noncesT  chan *[NonceSize]byte
 }
 
 func (p *Peer) String() string {
@@ -129,7 +135,7 @@ func (p *Peer) String() string {
 func (p *Peer) Zero() {
        p.BusyT.Lock()
        p.BusyR.Lock()
-       SliceZero(p.Key[:])
+       SliceZero(p.key[:])
        SliceZero(p.bufR)
        SliceZero(p.bufT)
        SliceZero(p.keyAuthR[:])
@@ -138,11 +144,6 @@ func (p *Peer) Zero() {
        p.BusyR.Unlock()
 }
 
-func (p *Peer) NonceExpectation(buf []byte) {
-       binary.BigEndian.PutUint64(buf, p.NonceExpect)
-       p.NonceCipher.Encrypt(buf, buf)
-}
-
 func cprCycleCalculate(conf *PeerConf) time.Duration {
        if conf.CPR == 0 {
                return time.Duration(0)
@@ -174,6 +175,7 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S
                bufSize += EnclessEnlargeSize
                noiseEnable = true
        }
+
        peer := Peer{
                Addr: addr,
                Id:   conf.Id,
@@ -185,10 +187,7 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S
                Encless:     conf.Encless,
                MTU:         conf.MTU,
 
-               Key:          key,
-               NonceCipher:  newNonceCipher(key),
-               nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
-               nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
+               key: key,
 
                Timeout:     timeout,
                Established: now,
@@ -201,15 +200,39 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S
                keyAuthR: new([SSize]byte),
                keyAuthT: new([SSize]byte),
        }
+
        if isClient {
-               peer.nonceOur = 1
-               peer.NonceExpect = 0 + 2
+               peer.noncesT = newNonces(peer.key, 1 + 2)
+               peer.noncesR = newNonces(peer.key, 0 + 2)
+               peer.noncesExpect = newNonces(peer.key, 0 + 2)
        } else {
-               peer.nonceOur = 0
-               peer.NonceExpect = 1 + 2
+               peer.noncesT = newNonces(peer.key, 0 + 2)
+               peer.noncesR = newNonces(peer.key, 1 + 2)
+               peer.noncesExpect = newNonces(peer.key, 1 + 2)
        }
-       return &peer
 
+       peer.NonceExpect = make([]byte, NonceSize)
+       nonce := <-peer.noncesExpect
+       copy(peer.NonceExpect, nonce[:])
+
+       var i int
+       peer.nonceBucketL = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+       for i = 0; i < NonceBucketSize; i++ {
+               nonce = <-peer.noncesR
+               peer.nonceBucketL[*nonce] = struct{}{}
+       }
+       peer.nonceBucketM = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+       for i = 0; i < NonceBucketSize; i++ {
+               nonce = <-peer.noncesR
+               peer.nonceBucketM[*nonce] = struct{}{}
+       }
+       peer.nonceBucketH = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+       for i = 0; i < NonceBucketSize; i++ {
+               nonce = <-peer.noncesR
+               peer.nonceBucketH[*nonce] = struct{}{}
+       }
+
+       return &peer
 }
 
 // Process incoming Ethernet packet.
@@ -243,17 +266,12 @@ func (p *Peer) EthProcess(data []byte) {
        } else {
                p.frameT = p.bufT[S20BS : S20BS+len(data)+1+NonceSize]
        }
-       p.nonceOur += 2
-       binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
-       p.NonceCipher.Encrypt(
-               p.frameT[len(p.frameT)-NonceSize:],
-               p.frameT[len(p.frameT)-NonceSize:],
-       )
+       copy(p.frameT[len(p.frameT)-NonceSize:], (<-p.noncesT)[:])
        var out []byte
        if p.Encless {
                var err error
                out, err = EnclessEncode(
-                       p.Key,
+                       p.key,
                        p.frameT[len(p.frameT)-NonceSize:],
                        p.frameT[:len(p.frameT)-NonceSize],
                )
@@ -266,7 +284,7 @@ func (p *Peer) EthProcess(data []byte) {
                        p.bufT[:S20BS+len(p.frameT)-NonceSize],
                        p.bufT[:S20BS+len(p.frameT)-NonceSize],
                        p.frameT[len(p.frameT)-NonceSize:],
-                       p.Key,
+                       p.key,
                )
                copy(p.keyAuthT[:], p.bufT[:SSize])
                poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
@@ -290,7 +308,7 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
        if p.Encless {
                var err error
                out, err = EnclessDecode(
-                       p.Key,
+                       p.key,
                        data[len(data)-NonceSize:],
                        data[:len(data)-NonceSize],
                )
@@ -308,7 +326,7 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
                        p.bufR[:S20BS+len(data)-TagSize-NonceSize],
                        p.bufR[:S20BS+len(data)-TagSize-NonceSize],
                        data[len(data)-NonceSize:],
-                       p.Key,
+                       p.key,
                )
                copy(p.keyAuthR[:], p.bufR[:SSize])
                copy(p.tagR[:], data[:TagSize])
@@ -320,41 +338,45 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
                out = p.bufR[S20BS : S20BS+len(data)-TagSize-NonceSize]
        }
 
-       // 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(
-               data[len(data)-NonceSize:],
-               data[len(data)-NonceSize:],
-       )
-       p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
        if reorderable {
-               _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
-               _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
-               if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
+               copy(p.nonceRecv[:], data[len(data)-NonceSize:])
+               _, foundL := p.nonceBucketL[p.nonceRecv]
+               _, foundM := p.nonceBucketM[p.nonceRecv]
+               _, foundH := p.nonceBucketH[p.nonceRecv]
+               // If found is none of buckets: either it is too old,
+               // or too new (many packets were lost)
+               if !(foundL || foundM || foundH) {
                        p.FramesDup++
                        p.BusyR.Unlock()
                        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
+               // Delete seen nonce
+               if foundL {
+                       delete(p.nonceBucketL, p.nonceRecv)
+               }
+               if foundM {
+                       delete(p.nonceBucketM, p.nonceRecv)
+               }
+               if foundH {
+                       delete(p.nonceBucketH, p.nonceRecv)
+               }
+               // If we are dealing with the latest bucket, create the new one
+               if foundH {
+                       p.nonceBucketL, p.nonceBucketM = p.nonceBucketM, p.nonceBucketH
+                       p.nonceBucketH = make(map[[NonceSize]byte]struct{})
+                       var nonce *[NonceSize]byte
+                       for i := 0; i < NonceBucketSize; i++ {
+                               nonce = <-p.noncesR
+                               p.nonceBucketH[*nonce] = struct{}{}
+                       }
                }
        } else {
-               if p.nonceRecv != p.NonceExpect {
+               if subtle.ConstantTimeCompare(data[len(data)-NonceSize:], p.NonceExpect) != 1 {
                        p.FramesDup++
                        p.BusyR.Unlock()
                        return false
                }
-               p.NonceExpect += 2
-       }
-       if p.nonceRecv > p.nonceLatest {
-               p.nonceLatest = p.nonceRecv
+               copy(p.NonceExpect, (<-p.noncesExpect)[:])
        }
 
        p.FramesIn++
index 7fa8aab8438a7fd04eaccf53972064edd91b8792..4a0c7523f6a71abc67061c82102116b2fa478a4f 100644 (file)
@@ -51,12 +51,16 @@ func init() {
                MTU:     MTUDefault,
                Timeout: time.Second * time.Duration(TimeoutDefault),
        }
-       testPeer = newPeer(true, "foo", Dummy{&testCt}, testConf, new([SSize]byte))
        testPt = make([]byte, 789)
 }
 
+func testPeerNew() {
+       testPeer = newPeer(true, "foo", Dummy{&testCt}, testConf, new([SSize]byte))
+}
+
 func TestTransportSymmetric(t *testing.T) {
-       peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
+       testPeerNew()
+       peerd := newPeer(false, "foo", Dummy{nil}, testConf, new([SSize]byte))
        f := func(payload []byte) bool {
                if len(payload) == 0 {
                        return true
@@ -70,7 +74,8 @@ func TestTransportSymmetric(t *testing.T) {
 }
 
 func TestTransportSymmetricNoise(t *testing.T) {
-       peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
+       testPeerNew()
+       peerd := newPeer(false, "foo", Dummy{nil}, testConf, new([SSize]byte))
        testPeer.NoiseEnable = true
        peerd.NoiseEnable = true
        f := func(payload []byte) bool {
@@ -87,7 +92,8 @@ func TestTransportSymmetricNoise(t *testing.T) {
 }
 
 func TestTransportSymmetricEncless(t *testing.T) {
-       peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
+       testPeerNew()
+       peerd := newPeer(false, "foo", Dummy{nil}, testConf, new([SSize]byte))
        testPeer.Encless = true
        testPeer.NoiseEnable = true
        peerd.Encless = true
@@ -119,10 +125,14 @@ func BenchmarkDec(b *testing.B) {
        testPeer = newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte))
        orig := make([]byte, len(testCt))
        copy(orig, testCt)
+       nonce := new([NonceSize]byte)
+       copy(nonce[:], testCt[len(testCt)-NonceSize:])
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
-               testPeer.nonceBucket0 = make(map[uint64]struct{}, 1)
-               testPeer.nonceBucket1 = make(map[uint64]struct{}, 1)
+               testPeer.nonceBucketL = make(map[[NonceSize]byte]struct{}, 1)
+               testPeer.nonceBucketM = make(map[[NonceSize]byte]struct{}, 1)
+               testPeer.nonceBucketH = make(map[[NonceSize]byte]struct{}, 1)
+               testPeer.nonceBucketL[*nonce] = struct{}{}
                copy(testCt, orig)
                if !testPeer.PktProcess(testCt, Dummy{nil}, true) {
                        b.Fail()