@item
Защита от атак повторного воспроизведения (replay) (используя
-одноразовые MAC).
+одноразовые MAC и опциональное условие синхронизации времени).
@item
Встроенные функции пересогласования ключей (ротация сессионных ключей) и
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.
@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}.
происходить в сети. Без CPR опции вы можете только сказать в online ли
кто или нет -- ни больше, ни меньше.
+@item Когда я должен использовать опцию @ref{Timesync, синхронизации времени}?
+Без этой опции, если кто-либо перехватит первоначальный пакет
+рукопожатия от настоящего клиента, то он сможет его повторить и получить
+пакет рукопожатия от сервера в ответ. Это может быть использовано для
+определения известен ли определённый пользователь серверу и то что мы
+имеем дело именно с GoVPN сервером. Timesync опция заставляет добавлять
+временные штампы в пакеты рукопожатия, разрешая быть пакетам
+действительными только в указанном временном окне. Если вы имеете более
+или менее синхронизированные часы между вашими клиентами и сервером, то
+можете всего использовать эту опцию.
+
@item Могу ли я сделать DoS (отказ в обслуживании) демона?
Каждый транспортный пакет первым делом аутентифицируется очень быстрым
UMAC алгоритмом -- в большинстве случаев потребление ресурсов TCP/UDP
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
* Identity::
* Password Authenticated Key Agreement: PAKE.
* Timeout::
+* Time synchronization: Timesync.
* Network transport: Network.
* Proxy::
* Maximum Transmission Unit: MTU.
@include identity.texi
@include pake.texi
@include timeout.texi
+@include timesync.texi
@include netproto.texi
@include proxy.texi
@include mtu.texi
@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
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
--- /dev/null
+@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.
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")
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)
Noise: pc.Noise,
CPR: pc.CPR,
Encless: pc.Encless,
+ TimeSync: pc.TimeSync,
}
if pc.TimeoutInt <= 0 {
pc.TimeoutInt = govpn.TimeoutDefault
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)
}
Noise bool `yaml:"noise"`
CPR int `yaml:"cpr"`
Encless bool `yaml:"encless"`
+ TimeSync int `yaml:"timesync"`
VerifierRaw string `yaml:"verifier"`
// This is passphrase verifier
}
// 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
}
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
}
}
// 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
} 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(
}
// 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
import (
"crypto/subtle"
"encoding/base64"
+ "encoding/binary"
"log"
"sync"
+ "time"
"golang.org/x/crypto/xtea"
)
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.
}
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()