X-Git-Url: http://www.git.cypherpunks.ru/?p=govpn.git;a=blobdiff_plain;f=src%2Fcypherpunks.ru%2Fgovpn%2Fpeer.go;h=22c893c80fb51d3bb0bb1affbfa395e32a137257;hp=9312b64b69710cef8a3993d7a9ba1b8444811e7e;hb=0bf04621961589bc735dc8bd8a075d7db24c4178;hpb=ce2d12cc15b31a2a1157123f47e58e7857436783 diff --git a/src/cypherpunks.ru/govpn/peer.go b/src/cypherpunks.ru/govpn/peer.go index 9312b64..22c893c 100644 --- a/src/cypherpunks.ru/govpn/peer.go +++ b/src/cypherpunks.ru/govpn/peer.go @@ -20,6 +20,7 @@ package govpn import ( "bytes" + "crypto/subtle" "encoding/binary" "io" "log" @@ -27,16 +28,16 @@ import ( "sync/atomic" "time" + "chacha20" + "golang.org/x/crypto/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 is ChaCha20's internal blocksize in bytes S20BS = 64 // Maximal amount of bytes transfered with single key (4 GiB) MaxBytesPerKey uint64 = 1 << 32 @@ -48,19 +49,26 @@ 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) +func newNonces(key *[32]byte, i uint64) chan *[NonceSize]byte { + macKey := make([]byte, 32) + chacha20.XORKeyStream(macKey, make([]byte, 32), new([16]byte), key) + mac, err := blake2b.New256(macKey) if err != nil { panic(err) } - return ciph + 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 +96,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:"-"` @@ -111,14 +108,28 @@ type Peer struct { bufR []byte tagR *[TagSize]byte keyAuthR *[SSize]byte + nonceR *[16]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 + nonceT *[16]byte frameT []byte + noncesT chan *[NonceSize]byte } func (p *Peer) String() string { @@ -129,7 +140,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 +149,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 +180,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 +192,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, @@ -199,17 +203,43 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S tagR: new([TagSize]byte), tagT: new([TagSize]byte), keyAuthR: new([SSize]byte), + nonceR: new([16]byte), keyAuthT: new([SSize]byte), + nonceT: new([16]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) + } + + 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 + return &peer } // Process incoming Ethernet packet. @@ -243,30 +273,22 @@ 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 + copy(p.nonceT[8:], p.frameT[len(p.frameT)-NonceSize:]) if p.Encless { var err error - out, err = EnclessEncode( - p.Key, - p.frameT[len(p.frameT)-NonceSize:], - p.frameT[:len(p.frameT)-NonceSize], - ) + out, err = EnclessEncode(p.key, p.nonceT, p.frameT[:len(p.frameT)-NonceSize]) if err != nil { panic(err) } out = append(out, p.frameT[len(p.frameT)-NonceSize:]...) } else { - salsa20.XORKeyStream( + chacha20.XORKeyStream( p.bufT[:S20BS+len(p.frameT)-NonceSize], p.bufT[:S20BS+len(p.frameT)-NonceSize], - p.frameT[len(p.frameT)-NonceSize:], - p.Key, + p.nonceT, + p.key, ) copy(p.keyAuthT[:], p.bufT[:SSize]) poly1305.Sum(p.tagT, p.frameT, p.keyAuthT) @@ -287,13 +309,10 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool { } var out []byte p.BusyR.Lock() + copy(p.nonceR[8:], data[len(data)-NonceSize:]) if p.Encless { var err error - out, err = EnclessDecode( - p.Key, - data[len(data)-NonceSize:], - data[:len(data)-NonceSize], - ) + out, err = EnclessDecode(p.key, p.nonceR, data[:len(data)-NonceSize]) if err != nil { p.FramesUnauth++ p.BusyR.Unlock() @@ -304,11 +323,11 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool { p.bufR[i] = 0 } copy(p.bufR[S20BS:], data[TagSize:]) - salsa20.XORKeyStream( + chacha20.XORKeyStream( p.bufR[:S20BS+len(data)-TagSize-NonceSize], p.bufR[:S20BS+len(data)-TagSize-NonceSize], - data[len(data)-NonceSize:], - p.Key, + p.nonceR, + p.key, ) copy(p.keyAuthR[:], p.bufR[:SSize]) copy(p.tagR[:], data[:TagSize]) @@ -320,41 +339,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++