]> Cypherpunks.ru repositories - govpn.git/commitdiff
Replace handshake NULLs with an IDtag
authorSergey Matveev <stargrave@stargrave.org>
Sun, 26 Apr 2015 16:19:54 +0000 (19:19 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 26 Apr 2015 16:22:46 +0000 (19:22 +0300)
Each handshake message contains so called IDtag: XTEA encrypted first 64
bits of transmitted message with client's identity as a key. To
determine if it is handshake message we check all possible client
identities as a key.

Now handshake messages became indistinguishable from the random.

Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
TODO
cmd/govpn-client/main.go
cmd/govpn-server/main.go
doc/govpn.texi
doc/handshake.txt
handshake.go
identify.go

diff --git a/TODO b/TODO
index 90dd1f31f11df543a7edb82ee3213d46651c0e64..63d6ef3c18755c3023c77845ad5ec731d2e0afbf 100644 (file)
--- a/TODO
+++ b/TODO
@@ -4,4 +4,3 @@
   human memorized passphrases to be used
 * Randomize ports usage
 * Fragmentize packets, noise the traffic
-* Indistinguishable from noise handshake packets
index 8b621c8bdcfef50d57316dabd169202fa6e8ee57..119a03d3ddbfe0152e8b877e5478563abd926e07 100644 (file)
@@ -52,6 +52,7 @@ func main() {
        govpn.Noncediff = *nonceDiff
 
        id := govpn.IDDecode(*IDRaw)
+       govpn.PeersInitDummy(id)
        key := govpn.KeyRead(*keyPath)
        if id == nil {
                panic("ID is not specified")
@@ -120,13 +121,18 @@ MainCycle:
                        }
 
                        udpPktData = udpBuf[:udpPkt.Size]
-                       if govpn.IsValidHandshakePkt(udpPktData) {
+                       if peer == nil {
                                if udpPkt.Addr.String() != remote.String() {
                                        udpReady <- struct{}{}
                                        log.Println("Unknown handshake message")
                                        continue
                                }
-                               if p := handshake.Client(conn, key, udpPktData); p != nil {
+                               if govpn.IDsCache.Find(udpPktData) == nil {
+                                       log.Println("Invalid identity in handshake packet")
+                                       udpReady <- struct{}{}
+                                       continue
+                               }
+                               if p := handshake.Client(id, conn, key, udpPktData); p != nil {
                                        log.Println("Handshake completed")
                                        if firstUpCall {
                                                go govpn.ScriptCall(*upPath, *ifaceName)
index b129f2dc2c37a19e5278eb40e570b810c5dae51b..3d952ea9e0d4e5a62cdb69fad9dba3c115124b74 100644 (file)
@@ -114,6 +114,8 @@ func main() {
        var udpPkt *govpn.UDPPkt
        var udpPktData []byte
        var ethEvent EthEvent
+       var peerId *govpn.PeerId
+       var handshakeProcessForce bool
        ethSink := make(chan EthEvent)
 
        log.Println(govpn.VersionGet())
@@ -187,13 +189,21 @@ MainCycle:
                        }
                        udpPktData = udpBuf[:udpPkt.Size]
                        addr = udpPkt.Addr.String()
-                       if govpn.IsValidHandshakePkt(udpPktData) {
+                       handshakeProcessForce = false
+               HandshakeProcess:
+                       if _, exists = peers[addr]; handshakeProcessForce || !exists {
+                               peerId = govpn.IDsCache.Find(udpPktData)
+                               if peerId == nil {
+                                       log.Println("Unknown identity from", addr)
+                                       udpReady <- struct{}{}
+                                       continue
+                               }
                                state, exists = states[addr]
                                if !exists {
                                        state = govpn.HandshakeNew(udpPkt.Addr)
                                        states[addr] = state
                                }
-                               peer = state.Server(conn, udpPktData)
+                               peer = state.Server(peerId, conn, udpPktData)
                                if peer != nil {
                                        log.Println("Peer handshake finished", peer)
                                        if _, exists = peers[addr]; exists {
@@ -216,7 +226,9 @@ MainCycle:
                                                }()
                                        }
                                }
-                               udpReady <- struct{}{}
+                               if !handshakeProcessForce {
+                                       udpReady <- struct{}{}
+                               }
                                continue
                        }
                        peerState, exists = peers[addr]
@@ -224,7 +236,12 @@ MainCycle:
                                udpReady <- struct{}{}
                                continue
                        }
-                       peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady)
+                       // If it fails during processing, then try to work with it
+                       // as with handshake packet
+                       if !peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady) {
+                               handshakeProcessForce = true
+                               goto HandshakeProcess
+                       }
                }
        }
 }
index 21656a03a9fde2aee90f4c62752d80a011a709bf..3f782098a47e829b2d85546cb154a98dda9a8c6c 100644 (file)
@@ -330,7 +330,7 @@ DH-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519}
 @item Packet overhead
 24 bytes per packet
 @item Handshake overhead
-4 UDP (2 from client, 2 from server) packets, 240 bytes total payload
+4 UDP (2 from client, 2 from server) packets, 200 bytes total payload
 @end table
 
 @menu
@@ -346,7 +346,8 @@ ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA) +
     AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA))
 @end verbatim
 
-Each transport message is indistinguishable from pseudo random noise.
+All transport and handshake messages are indistinguishable from
+pseudo random noise.
 
 @code{SERIAL} is message's serial number. Odds are reserved for
 client(→server) messages, evens for server(→client) messages.
@@ -384,13 +385,19 @@ rearranged UDP packets.
 
 @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 identity as a key.
+It is used to transmit identity and to mark packet as handshake message.
+Server can determine used identity by trying all possible known to him
+keys. It consumes resources, but XTEA is rather fast algorithm and
+handshake messages checking is seldom enough event.
+
 @enumerate
 @item
 client generates @code{CPubKey}, random 64bit @code{R} that is used as a
-nonce for encryption, and an encrypted @code{R} with XTEA, where the key
-equals to client's identity
+nonce for encryption
 @item
-@verb{|R + enc(PSK, R, CPubKey) + xtea(ID, R) + NULL + NULLs -> Server|} [65 bytes]
+@verb{|R + enc(PSK, R, CPubKey) + IDtag -> Server|} [48 bytes]
 @item
 server remembers clients address, decrypt @code{CPubKey}, generates
 @code{SPrivKey}/@code{SPubKey}, computes common shared key @code{K}
@@ -398,19 +405,19 @@ server remembers clients address, decrypt @code{CPubKey}, generates
 number @code{RS} and 256bit random @code{SS}. PSK-encryption uses
 incremented @code{R} (from previous message) for nonce
 @item
-@verb{|enc(PSK, R+1, SPubKey) + enc(K, R, RS + SS) + NULLs -> Client|} [88 bytes]
+@verb{|enc(PSK, R+1, SPubKey) + enc(K, R, RS + SS) + IDtag -> Client|} [80 bytes]
 @item
 client decrypt @code{SPubKey}, computes @code{K}, decrypts @code{RS},
 @code{SS} with key @code{K}, remembers @code{SS}, generates 64bit random
 number @code{RC} and 256bit random @code{SC},
 @item
-@verb{|enc(K, R+1, RS + RC + SC) + NULLs -> Server|} [64 bytes]
+@verb{|enc(K, R+1, RS + RC + SC) + IDtag -> Server|} [56 bytes]
 @item
 server decrypt @code{RS}, @code{RC}, @code{SC} with key @code{K},
 compares @code{RS} with it's own one send before, computes final main
 encryption key @code{S = SS XOR SC}
 @item
-@verb{|ENC(K, 0, RC) + NULLs -> Client|} [24 bytes]
+@verb{|ENC(K, 0, RC) + IDtag -> Client|} [16 bytes]
 @item
 server switches to the new client
 @item
@@ -418,12 +425,9 @@ client decrypts @code{RC} and compares with it's own generated one,
 computes final main encryption key @code{S}
 @end enumerate
 
-Where PSK is 256bit pre-shared key, @code{NULLs} are 16 null-bytes.
-@code{R*} are required for handshake randomization and two-way
-authentication. K key is used only during handshake. @code{NULLs} are
-required to differentiate common transport protocol messages from
-handshake ones. DH public keys can be trivially derived from private
-ones.
+Where PSK is 256bit pre-shared key. @code{R*} are required for handshake
+randomization and two-way authentication. K key is used only during
+handshake. DH public keys can be trivially derived from private ones.
 
 @node Reporting bugs
 @unnumbered Reporting bugs
index d946833876e7a2d052b1367100fe3a2655551eef..b36d9f3e8b5500d2d0a1654d57314ca61f7d6136 100644 (file)
@@ -4,7 +4,7 @@ participant Server
 
 Client -> Client : R=rand(64bit)
 Client -> Client : CPrivKey=rand(256bit)
-Client -> Server : R, enc(PSK, R, CPubKey), xtea(ID, R)
+Client -> Server : R, enc(PSK, R, CPubKey)
 Server -> Server : SPrivKey=rand(256bit)
 Server -> Server : K=DH(SPrivKey, CPubKey)
 Server -> Server : RS=rand(64bit)
index 44f9a2968c980ace0ef292ad9d1cd29395685e96..86e6083482a770e7e8b91d52ec5313598fac6352 100644 (file)
@@ -28,7 +28,6 @@ import (
        "time"
 
        "golang.org/x/crypto/curve25519"
-       "golang.org/x/crypto/poly1305"
        "golang.org/x/crypto/salsa20"
        "golang.org/x/crypto/salsa20/salsa"
        "golang.org/x/crypto/xtea"
@@ -55,25 +54,17 @@ func keyFromSecrets(server, client []byte) *[KeySize]byte {
        return k
 }
 
-// Check if it is valid handshake-related message.
-// Minimal size and last 16 zero bytes.
-func IsValidHandshakePkt(pkt []byte) bool {
-       if len(pkt) < 24 {
-               return false
-       }
-       for i := len(pkt) - poly1305.TagSize; i < len(pkt); i++ {
-               if pkt[i] != '\x00' {
-                       return false
-               }
-       }
-       return true
-}
-
 // Zero handshake's memory state
 func (h *Handshake) Zero() {
-       sliceZero(h.rNonce[:])
-       sliceZero(h.dhPriv[:])
-       sliceZero(h.key[:])
+       if h.rNonce != nil {
+               sliceZero(h.rNonce[:])
+       }
+       if h.dhPriv != nil {
+               sliceZero(h.dhPriv[:])
+       }
+       if h.key != nil {
+               sliceZero(h.key[:])
+       }
        if h.rServer != nil {
                sliceZero(h.rServer[:])
        }
@@ -119,6 +110,17 @@ func HandshakeNew(addr *net.UDPAddr) *Handshake {
        return &state
 }
 
+// Generate ID tag from client identification and data.
+func idTag(id *PeerId, data []byte) []byte {
+       ciph, err := xtea.NewCipher(id[:])
+       if err != nil {
+               panic(err)
+       }
+       enc := make([]byte, xtea.BlockSize)
+       ciph.Encrypt(enc, data[:xtea.BlockSize])
+       return enc
+}
+
 // Start handshake's procedure from the client.
 // It is the entry point for starting the handshake procedure.
 // You have to specify outgoing conn address, remote's addr address,
@@ -137,19 +139,8 @@ func HandshakeStart(conn *net.UDPConn, addr *net.UDPAddr, id *PeerId, key *[32]b
        }
        enc := make([]byte, 32)
        salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], key)
-
-       ciph, err := xtea.NewCipher(id[:])
-       if err != nil {
-               panic(err)
-       }
-       rEnc := make([]byte, xtea.BlockSize)
-       ciph.Encrypt(rEnc, state.rNonce[:])
-
        data := append(state.rNonce[:], enc...)
-       data = append(data, rEnc...)
-       data = append(data, '\x00')
-       data = append(data, make([]byte, poly1305.TagSize)...)
-
+       data = append(data, idTag(id, state.rNonce[:])...)
        if _, err := conn.WriteTo(data, addr); err != nil {
                panic(err)
        }
@@ -158,24 +149,14 @@ func HandshakeStart(conn *net.UDPConn, addr *net.UDPAddr, id *PeerId, key *[32]b
 
 // Process handshake message on the server side.
 // This function is intended to be called on server's side.
-// Our outgoing conn connection and received data are required.
+// Client identity, our outgoing conn connection and
+// received data are required.
 // If this is the final handshake message, then new Peer object
 // will be created and used as a transport. If no mutually
 // authenticated Peer is ready, then return nil.
-func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
-       switch len(data) {
-       case 65: // R + ENC(PSK, dh_client_pub) + xtea(ID, R) + NULL + NULLs
-               if h.rNonce != nil {
-                       log.Println("Invalid handshake stage from", h.addr)
-                       return nil
-               }
-
-               // Try to determine client's ID
-               id := IDsCache.Find(data[:8], data[8+32:8+32+8])
-               if id == nil {
-                       log.Println("Unknown identity from", h.addr)
-                       return nil
-               }
+func (h *Handshake) Server(id *PeerId, conn *net.UDPConn, data []byte) *Peer {
+       // R + ENC(PSK, dh_client_pub) + IDtag
+       if len(data) == 48 && h.rNonce == nil {
                key := KeyRead(path.Join(PeersPath, id.String(), "key"))
                h.Id = *id
 
@@ -210,17 +191,13 @@ func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
 
                // Send that to client
                if _, err := conn.WriteTo(
-                       append(encPub,
-                               append(encRs, make([]byte, poly1305.TagSize)...)...), h.addr); err != nil {
+                       append(encPub, append(encRs, idTag(id, encPub)...)...), h.addr); err != nil {
                        panic(err)
                }
                h.LastPing = time.Now()
-       case 64: // ENC(K, RS + RC + SC) + NULLs
-               if (h.rNonce == nil) || (h.rClient != nil) {
-                       log.Println("Invalid handshake stage from", h.addr)
-                       return nil
-               }
-
+       } else
+       // ENC(K, RS + RC + SC) + IDtag
+       if len(data) == 56 && h.rClient == nil {
                // Decrypted Rs compare rServer
                decRs := make([]byte, 8+8+32)
                salsa20.XORKeyStream(decRs, data[:8+8+32], h.rNonceNext(), h.key)
@@ -232,7 +209,7 @@ func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
                // Send final answer to client
                enc := make([]byte, 8)
                salsa20.XORKeyStream(enc, decRs[8:8+8], make([]byte, 8), h.key)
-               if _, err := conn.WriteTo(append(enc, make([]byte, poly1305.TagSize)...), h.addr); err != nil {
+               if _, err := conn.WriteTo(append(enc, idTag(id, enc)...), h.addr); err != nil {
                        panic(err)
                }
 
@@ -240,7 +217,7 @@ func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
                peer := newPeer(h.addr, h.Id, 0, keyFromSecrets(h.sServer[:], decRs[8+8:]))
                h.LastPing = time.Now()
                return peer
-       default:
+       } else {
                log.Println("Invalid handshake message from", h.addr)
        }
        return nil
@@ -248,15 +225,14 @@ func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
 
 // Process handshake message on the client side.
 // This function is intended to be called on client's side.
-// Our outgoing conn connection, authentication key and received data
-// are required. Client does not work with identities, as he is the
-// only one, so key is a requirement.
+// Our outgoing conn connection, authentication
+// key and received data are required.
 // If this is the final handshake message, then new Peer object
 // will be created and used as a transport. If no mutually
 // authenticated Peer is ready, then return nil.
-func (h *Handshake) Client(conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer {
+func (h *Handshake) Client(id *PeerId, conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer {
        switch len(data) {
-       case 88: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + NULLs
+       case 80: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + IDtag
                if h.key != nil {
                        log.Println("Invalid handshake stage from", h.addr)
                        return nil
@@ -290,11 +266,11 @@ func (h *Handshake) Client(conn *net.UDPConn, key *[KeySize]byte, data []byte) *
                                append(h.rClient[:], h.sClient[:]...)...), h.rNonceNext(), h.key)
 
                // Send that to server
-               if _, err := conn.WriteTo(append(encRs, make([]byte, poly1305.TagSize)...), h.addr); err != nil {
+               if _, err := conn.WriteTo(append(encRs, idTag(id, encRs)...), h.addr); err != nil {
                        panic(err)
                }
                h.LastPing = time.Now()
-       case 24: // ENC(K, RC) + NULLs
+       case 16: // ENC(K, RC) + IDtag
                if h.key == nil {
                        log.Println("Invalid handshake stage from", h.addr)
                        return nil
index c6fcc9d1d24c8224e8941d9406f042fd00ee3161..e4baea14c5f89b45bc0b8dcaeeabaa4a63ac8c07 100644 (file)
@@ -60,6 +60,17 @@ func PeersInit(path string) {
        }()
 }
 
+// Initialize dummy cache for client-side usage. It will consist only
+// of single key.
+func PeersInitDummy(id *PeerId) {
+       IDsCache = make(map[PeerId]*xtea.Cipher)
+       cipher, err := xtea.NewCipher(id[:])
+       if err != nil {
+               panic(err)
+       }
+       IDsCache[*id] = cipher
+}
+
 // Refresh IDsCache: remove disappeared keys, add missing ones with
 // initialized ciphers.
 func (cc cipherCache) refresh() {
@@ -103,13 +114,17 @@ func (cc cipherCache) refresh() {
 }
 
 // Try to find peer's identity (that equals to an encryption key)
-// by providing cipher and plain texts.
-func (cc cipherCache) Find(plaintext, ciphertext []byte) *PeerId {
+// 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 {
+               return nil
+       }
        buf := make([]byte, xtea.BlockSize)
        cipherCacheLock.RLock()
        for pid, cipher := range cc {
-               cipher.Decrypt(buf, ciphertext)
-               if subtle.ConstantTimeCompare(buf, plaintext) == 1 {
+               cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
+               if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
                        ppid := PeerId(pid)
                        cipherCacheLock.RUnlock()
                        return &ppid