]> Cypherpunks.ru repositories - govpn.git/commitdiff
Add time synchronization requirement option
authorSergey Matveev <stargrave@stargrave.org>
Fri, 29 Jan 2016 21:32:08 +0000 (00:32 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Fri, 29 Jan 2016 22:01:50 +0000 (01:01 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
15 files changed:
VERSION
doc/about.ru.texi
doc/about.texi
doc/client.texi
doc/faq.ru.texi
doc/faq.texi
doc/glossary.texi
doc/news.texi
doc/server.texi
doc/timesync.texi [new file with mode: 0644]
src/cypherpunks.ru/govpn/cmd/govpn-client/main.go
src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go
src/cypherpunks.ru/govpn/conf.go
src/cypherpunks.ru/govpn/handshake.go
src/cypherpunks.ru/govpn/identify.go

diff --git a/VERSION b/VERSION
index d346e2ab7f2109b12c9bf53299bbbf6e7527fe7f..37c2d9960ec72414b3941f8f4c1172fb6e3c6e7f 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.3
+5.4
index e301259441fe5e8aab9de1f31f6423ecbe1285f9..f24725c73063e738ff897567c7a973d2a94d2802 100644 (file)
@@ -50,7 +50,7 @@ A-EKE (Diffie-Hellman Augmented Encrypted Key Exchange)).
 
 @item
 Защита от атак повторного воспроизведения (replay) (используя
-одноразовые MAC).
+одноразовые MAC и опциональное условие синхронизации времени).
 
 @item
 Встроенные функции пересогласования ключей (ротация сессионных ключей) и 
index de1abe907534bee6e6aff56dfbe9dd4d1f2dad06..25f0f12dc9e3e9f74ef441df11048478c11418a3 100644 (file)
@@ -44,7 +44,8 @@ indistinguishable from the noise with optionally hidden packets length.
 property.
 
 @item
-Replay attack protection (using one-time MACs).
+Replay attack protection (using one-time MACs and optional time
+synchronization requirement).
 
 @item
 Built-in rehandshake (session key rotation) and heartbeat features.
index ba7503874b41e9e02e3b1334929ddbc4486fc3eb..654ec98bd555010439a310746fb0e21decae29af 100644 (file)
@@ -43,6 +43,10 @@ to enter it in the terminal.
 @item -timeout
 @ref{Timeout} setting in seconds.
 
+@item -timesync
+Optional @ref{Timesync, time synchronization} requirement. If set to
+zero, then no synchronization required.
+
 @item -noise
 Enable @ref{Noise}.
 
index 903513760a44a2ff37e337532574378a0a9b608b..b12451e60c9fa7be19e052e9911d3c6018a8a77c 100644 (file)
@@ -99,6 +99,17 @@ GoVPN трафике (так же как и в TLS, IPsec, SSH и других V
 происходить в сети. Без CPR опции вы можете только сказать в online ли
 кто или нет -- ни больше, ни меньше.
 
+@item Когда я должен использовать опцию @ref{Timesync, синхронизации времени}?
+Без этой опции, если кто-либо перехватит первоначальный пакет
+рукопожатия от настоящего клиента, то он сможет его повторить и получить
+пакет рукопожатия от сервера в ответ. Это может быть использовано для
+определения известен ли определённый пользователь серверу и то что мы
+имеем дело именно с GoVPN сервером. Timesync опция заставляет добавлять
+временные штампы в пакеты рукопожатия, разрешая быть пакетам
+действительными только в указанном временном окне. Если вы имеете более
+или менее синхронизированные часы между вашими клиентами и сервером, то
+можете всего использовать эту опцию.
+
 @item Могу ли я сделать DoS (отказ в обслуживании) демона?
 Каждый транспортный пакет первым делом аутентифицируется очень быстрым
 UMAC алгоритмом -- в большинстве случаев потребление ресурсов TCP/UDP
index c9da019f1e2000b887469846527041e618d393a0..021cc552163f25bc90a09f49d0c716f308682d0a 100644 (file)
@@ -106,6 +106,16 @@ timestamps and sizes. You can run traffic analysis and predict what is
 going on in the network. With CPR option enabled you can tell either
 somebody is online, or not -- nothing less, nothing more.
 
+@item When should I use @ref{Timesync, time synchronization} option?
+Without that option, if someone captured initial handshake packet from
+the valid client, then it could repeat it and receive a handshake packet
+as an answer from server. This can be used to detect if some user's
+identity is known to server and that it is exactly GoVPN server.
+Timesync option forces adding timestamps to each handshake packet
+allowing only specified time window of packet validness. If you have got
+more or less synchronized clocks between your clients and server, then
+you can use always use this option.
+
 @cindex DoS
 @item Can I DoS (denial of service) the daemon?
 Each transport packet is authenticated first with the very fast UMAC
index 46b8e70b0de2679f4a6c944e53eb32cf2f4846bf..fd865cdbde5552ade5d2bdaadd59dc7752495a87 100644 (file)
@@ -6,6 +6,7 @@
 * Identity::
 * Password Authenticated Key Agreement: PAKE.
 * Timeout::
+* Time synchronization: Timesync.
 * Network transport: Network.
 * Proxy::
 * Maximum Transmission Unit: MTU.
@@ -20,6 +21,7 @@
 @include identity.texi
 @include pake.texi
 @include timeout.texi
+@include timesync.texi
 @include netproto.texi
 @include proxy.texi
 @include mtu.texi
index 21cdb992f0fc2093323b3d42ca649e991e122a7f..54d794428ac1d61eabaf435bf6f1157e7f78695a 100644 (file)
@@ -5,6 +5,15 @@
 
 @table @strong
 
+@item @anchor{Release_5.4} Release 5.4
+@cindex Release 5.4
+@itemize
+@item Added optional @ref{Timesync, time synchronization} requirement.
+It will add timestamps in handshake PRP authentication, disallowing to
+repeat captured packet and get reply from the server, making it visible
+to DPI.
+@end itemize
+
 @item @anchor{Release_5.3} Release 5.3
 @cindex Release 5.3
 @itemize
index ee132bdac8ae003c1ec18f70d0491665a43af41e..489402c0ed5e600e32e6040b6c7dbd869f23390a 100644 (file)
@@ -38,6 +38,7 @@ stargrave: {                        <-- Peer human readable name
     up: ./stargrave-up.sh           <-- OPTIONAL up-script
     down: ./stargrave-down.sh       <-- OPTIONAL down-script
     timeout: 60                     <-- OPTIONAL overriden timeout
+    timesync: 0                     <-- OPTIONAL time synchronization requirement
     noise: No                       <-- OPTIONAL noise enabler
     cpr: 64                         <-- OPTIONAL constant packet rate, KiB/sec
     encless: No                     <-- OPTIONAL Encryptionless mode
diff --git a/doc/timesync.texi b/doc/timesync.texi
new file mode 100644 (file)
index 0000000..a212490
--- /dev/null
@@ -0,0 +1,21 @@
+@node Timesync
+@cindex Timesync
+@cindex Time synchronization
+@subsection Time synchronization
+
+DPI systems can be active. They could intercept first (assuming
+handshake) packets and repeat them again sooner. Without the correct
+PRP-authentication, server won't answer them, but repeated ones are
+still valid. In that case DPI will get the answer from the server,
+probably without noise padding and understand that it is GoVPN server's
+handshake packet.
+
+Time synchronization requirement could be used for preventing this.
+Client and server clocks must be synced together more or less. You
+enable it by specifying @code{timesync} option with allowable time
+accuracy (time window width) in seconds.
+
+Each handshake's packet PRP authentication just XORed with current
+timestamp rounded to @code{timesync} number of seconds. Timesync option
+is higher: less clock synchronization accuracy required, but bigger time
+window of possible packet repeating.
index b46a16eb93203cb93a7002d372b24b64de050602..8991462edd9b45a81c151373b16acd5daf26bfaa 100644 (file)
@@ -43,6 +43,7 @@ var (
        proxyAuth   = flag.String("proxy-auth", "", "user:password Basic proxy auth")
        mtu         = flag.Int("mtu", govpn.MTUDefault, "MTU of TAP interface")
        timeoutP    = flag.Int("timeout", 60, "Timeout seconds")
+       timeSync    = flag.Int("timesync", 0, "Time synchronization requirement")
        noisy       = flag.Bool("noise", false, "Enable noise appending")
        encless     = flag.Bool("encless", false, "Encryptionless mode")
        cpr         = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
@@ -90,13 +91,16 @@ func main() {
                Iface:    *ifaceName,
                MTU:      *mtu,
                Timeout:  time.Second * time.Duration(timeout),
+               TimeSync: *timeSync,
                Noise:    *noisy,
                CPR:      *cpr,
                Encless:  *encless,
                Verifier: verifier,
                DSAPriv:  priv,
        }
-       idsCache = govpn.NewCipherCache([]govpn.PeerId{*verifier.Id})
+       idsCache = govpn.NewCipherCache()
+       confs := map[govpn.PeerId]*govpn.PeerConf{*verifier.Id: conf}
+       idsCache.Update(&confs)
        log.Println(govpn.VersionGet())
 
        tap, err = govpn.TAPListen(*ifaceName, *mtu)
index 131eeba502201fc46f56d077e65113af69a6c8e4..e486ec97b12a92b37f03a9b2846612bf70903701 100644 (file)
@@ -76,6 +76,7 @@ func confRead() (*map[govpn.PeerId]*govpn.PeerConf, error) {
                        Noise:    pc.Noise,
                        CPR:      pc.CPR,
                        Encless:  pc.Encless,
+                       TimeSync: pc.TimeSync,
                }
                if pc.TimeoutInt <= 0 {
                        pc.TimeoutInt = govpn.TimeoutDefault
@@ -93,16 +94,12 @@ func confRefresh() error {
                return err
        }
        confs = *newConfs
-       ids := make([]govpn.PeerId, 0, len(confs))
-       for peerId, _ := range confs {
-               ids = append(ids, peerId)
-       }
-       idsCache.Update(ids)
+       idsCache.Update(newConfs)
        return nil
 }
 
 func confInit() {
-       idsCache = govpn.NewCipherCache(nil)
+       idsCache = govpn.NewCipherCache()
        if err := confRefresh(); err != nil {
                log.Fatalln(err)
        }
index 0639475254d8be37b091ee28c653ca502e9d810b..c8232d64b047a91644b47111b8cf6db8384613b0 100644 (file)
@@ -36,6 +36,7 @@ type PeerConf struct {
        Noise       bool          `yaml:"noise"`
        CPR         int           `yaml:"cpr"`
        Encless     bool          `yaml:"encless"`
+       TimeSync    int           `yaml:"timesync"`
        VerifierRaw string        `yaml:"verifier"`
 
        // This is passphrase verifier
index d9e86352dbde5e26b6a22d40e66859b8908d7e6d..4f080eb34647c1494c3b78f6b2e9bdabf082177f 100644 (file)
@@ -133,13 +133,15 @@ func NewHandshake(addr string, conn io.Writer, conf *PeerConf) *Handshake {
 }
 
 // Generate ID tag from client identification and data.
-func idTag(id *PeerId, data []byte) []byte {
+func idTag(id *PeerId, timeSync int, data []byte) []byte {
        ciph, err := xtea.NewCipher(id[:])
        if err != nil {
                panic(err)
        }
        enc := make([]byte, xtea.BlockSize)
-       ciph.Encrypt(enc, data[:xtea.BlockSize])
+       copy(enc, data)
+       AddTimeSync(timeSync, enc)
+       ciph.Encrypt(enc, enc)
        return enc
 }
 
@@ -172,7 +174,7 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake {
                salsa20.XORKeyStream(enc, enc, state.rNonce[:], state.dsaPubH)
        }
        data := append(state.rNonce[:], enc...)
-       data = append(data, idTag(state.Conf.Id, state.rNonce[:])...)
+       data = append(data, idTag(state.Conf.Id, state.Conf.TimeSync, state.rNonce[:])...)
        state.conn.Write(data)
        return state
 }
@@ -262,7 +264,9 @@ func (h *Handshake) Server(data []byte) *Peer {
                }
 
                // Send that to client
-               h.conn.Write(append(encPub, append(encRs, idTag(h.Conf.Id, encPub)...)...))
+               h.conn.Write(append(encPub, append(
+                       encRs, idTag(h.Conf.Id, h.Conf.TimeSync, encPub)...,
+               )...))
                h.LastPing = time.Now()
        } else
        // ENC(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag
@@ -317,7 +321,7 @@ func (h *Handshake) Server(data []byte) *Peer {
                } else {
                        salsa20.XORKeyStream(enc, enc, h.rNonceNext(2), h.key)
                }
-               h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...))
+               h.conn.Write(append(enc, idTag(h.Conf.Id, h.Conf.TimeSync, enc)...))
 
                // Switch peer
                peer := newPeer(
@@ -431,7 +435,7 @@ func (h *Handshake) Client(data []byte) *Peer {
                }
 
                // Send that to server
-               h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...))
+               h.conn.Write(append(enc, idTag(h.Conf.Id, h.Conf.TimeSync, enc)...))
                h.LastPing = time.Now()
        } else
        // ENC(K, R+2, RC) + IDtag
index 0583921e668c0a604650abaaed45ccef708405e2..f85631b01839639b8bf981572f4c8b6db106c984 100644 (file)
@@ -21,8 +21,10 @@ package govpn
 import (
        "crypto/subtle"
        "encoding/base64"
+       "encoding/binary"
        "log"
        "sync"
+       "time"
 
        "golang.org/x/crypto/xtea"
 )
@@ -41,43 +43,56 @@ func (id PeerId) MarshalJSON() ([]byte, error) {
        return []byte(`"` + id.String() + `"`), nil
 }
 
+type CipherAndTimeSync struct {
+       c *xtea.Cipher
+       t int
+}
+
 type CipherCache struct {
-       c map[PeerId]*xtea.Cipher
+       c map[PeerId]*CipherAndTimeSync
        l sync.RWMutex
 }
 
-func NewCipherCache(peerIds []PeerId) *CipherCache {
-       cc := CipherCache{c: make(map[PeerId]*xtea.Cipher, len(peerIds))}
-       cc.Update(peerIds)
-       return &cc
+func NewCipherCache() *CipherCache {
+       return &CipherCache{c: make(map[PeerId]*CipherAndTimeSync)}
 }
 
 // Remove disappeared keys, add missing ones with initialized ciphers.
-func (cc *CipherCache) Update(peerIds []PeerId) {
-       available := make(map[PeerId]struct{})
-       for _, peerId := range peerIds {
-               available[peerId] = struct{}{}
-       }
+func (cc *CipherCache) Update(peers *map[PeerId]*PeerConf) {
        cc.l.Lock()
-       for k, _ := range cc.c {
-               if _, exists := available[k]; !exists {
-                       log.Println("Cleaning key:", k)
-                       delete(cc.c, k)
+       for pid, _ := range cc.c {
+               if _, exists := (*peers)[pid]; !exists {
+                       log.Println("Cleaning key:", pid)
+                       delete(cc.c, pid)
                }
        }
-       for peerId, _ := range available {
-               if _, exists := cc.c[peerId]; !exists {
-                       log.Println("Adding key", peerId)
-                       cipher, err := xtea.NewCipher(peerId[:])
+       for pid, pc := range *peers {
+               if _, exists := cc.c[pid]; exists {
+                       cc.c[pid].t = pc.TimeSync
+               } else {
+                       log.Println("Adding key", pid)
+                       cipher, err := xtea.NewCipher(pid[:])
                        if err != nil {
                                panic(err)
                        }
-                       cc.c[peerId] = cipher
+                       cc.c[pid] = &CipherAndTimeSync{cipher, pc.TimeSync}
                }
        }
        cc.l.Unlock()
 }
 
+// If timeSync > 0, then XOR timestamp with the data.
+func AddTimeSync(ts int, data []byte) {
+       if ts == 0 {
+               return
+       }
+       buf := make([]byte, 8)
+       binary.BigEndian.PutUint64(buf, uint64(time.Now().Unix()/int64(ts)*int64(ts)))
+       for i := 0; i < 8; i++ {
+               data[i] ^= buf[i]
+       }
+}
+
 // Try to find peer's identity (that equals to an encryption key)
 // by taking first blocksize sized bytes from data at the beginning
 // as plaintext and last bytes as cyphertext.
@@ -87,8 +102,9 @@ func (cc *CipherCache) Find(data []byte) *PeerId {
        }
        buf := make([]byte, xtea.BlockSize)
        cc.l.RLock()
-       for pid, cipher := range cc.c {
-               cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
+       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 {
                        ppid := PeerId(pid)
                        cc.l.RUnlock()