From 831cb44403d1c94eddff4ac9f56d386a9707c47b Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sat, 30 Jan 2016 00:32:08 +0300 Subject: [PATCH] Add time synchronization requirement option Signed-off-by: Sergey Matveev --- VERSION | 2 +- doc/about.ru.texi | 2 +- doc/about.texi | 3 +- doc/client.texi | 4 ++ doc/faq.ru.texi | 11 ++++ doc/faq.texi | 10 ++++ doc/glossary.texi | 2 + doc/news.texi | 9 +++ doc/server.texi | 1 + doc/timesync.texi | 21 +++++++ .../govpn/cmd/govpn-client/main.go | 6 +- .../govpn/cmd/govpn-server/conf.go | 9 +-- src/cypherpunks.ru/govpn/conf.go | 1 + src/cypherpunks.ru/govpn/handshake.go | 16 +++-- src/cypherpunks.ru/govpn/identify.go | 58 ++++++++++++------- 15 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 doc/timesync.texi diff --git a/VERSION b/VERSION index d346e2a..37c2d99 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.3 +5.4 diff --git a/doc/about.ru.texi b/doc/about.ru.texi index e301259..f24725c 100644 --- a/doc/about.ru.texi +++ b/doc/about.ru.texi @@ -50,7 +50,7 @@ A-EKE (Diffie-Hellman Augmented Encrypted Key Exchange)). @item Защита от атак повторного воспроизведения (replay) (используя -одноразовые MAC). +одноразовые MAC и опциональное условие синхронизации времени). @item Встроенные функции пересогласования ключей (ротация сессионных ключей) и diff --git a/doc/about.texi b/doc/about.texi index de1abe9..25f0f12 100644 --- a/doc/about.texi +++ b/doc/about.texi @@ -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. diff --git a/doc/client.texi b/doc/client.texi index ba75038..654ec98 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -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}. diff --git a/doc/faq.ru.texi b/doc/faq.ru.texi index 9035137..b12451e 100644 --- a/doc/faq.ru.texi +++ b/doc/faq.ru.texi @@ -99,6 +99,17 @@ GoVPN трафике (так же как и в TLS, IPsec, SSH и других V происходить в сети. Без CPR опции вы можете только сказать в online ли кто или нет -- ни больше, ни меньше. +@item Когда я должен использовать опцию @ref{Timesync, синхронизации времени}? +Без этой опции, если кто-либо перехватит первоначальный пакет +рукопожатия от настоящего клиента, то он сможет его повторить и получить +пакет рукопожатия от сервера в ответ. Это может быть использовано для +определения известен ли определённый пользователь серверу и то что мы +имеем дело именно с GoVPN сервером. Timesync опция заставляет добавлять +временные штампы в пакеты рукопожатия, разрешая быть пакетам +действительными только в указанном временном окне. Если вы имеете более +или менее синхронизированные часы между вашими клиентами и сервером, то +можете всего использовать эту опцию. + @item Могу ли я сделать DoS (отказ в обслуживании) демона? Каждый транспортный пакет первым делом аутентифицируется очень быстрым UMAC алгоритмом -- в большинстве случаев потребление ресурсов TCP/UDP diff --git a/doc/faq.texi b/doc/faq.texi index c9da019..021cc55 100644 --- a/doc/faq.texi +++ b/doc/faq.texi @@ -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 diff --git a/doc/glossary.texi b/doc/glossary.texi index 46b8e70..fd865cd 100644 --- a/doc/glossary.texi +++ b/doc/glossary.texi @@ -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 diff --git a/doc/news.texi b/doc/news.texi index 21cdb99..54d7944 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -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 diff --git a/doc/server.texi b/doc/server.texi index ee132bd..489402c 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -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 index 0000000..a212490 --- /dev/null +++ b/doc/timesync.texi @@ -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. diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-client/main.go b/src/cypherpunks.ru/govpn/cmd/govpn-client/main.go index b46a16e..8991462 100644 --- a/src/cypherpunks.ru/govpn/cmd/govpn-client/main.go +++ b/src/cypherpunks.ru/govpn/cmd/govpn-client/main.go @@ -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) diff --git a/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go b/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go index 131eeba..e486ec9 100644 --- a/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go +++ b/src/cypherpunks.ru/govpn/cmd/govpn-server/conf.go @@ -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) } diff --git a/src/cypherpunks.ru/govpn/conf.go b/src/cypherpunks.ru/govpn/conf.go index 0639475..c8232d6 100644 --- a/src/cypherpunks.ru/govpn/conf.go +++ b/src/cypherpunks.ru/govpn/conf.go @@ -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 diff --git a/src/cypherpunks.ru/govpn/handshake.go b/src/cypherpunks.ru/govpn/handshake.go index d9e8635..4f080eb 100644 --- a/src/cypherpunks.ru/govpn/handshake.go +++ b/src/cypherpunks.ru/govpn/handshake.go @@ -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 diff --git a/src/cypherpunks.ru/govpn/identify.go b/src/cypherpunks.ru/govpn/identify.go index 0583921..f85631b 100644 --- a/src/cypherpunks.ru/govpn/identify.go +++ b/src/cypherpunks.ru/govpn/identify.go @@ -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() -- 2.44.0