]> Cypherpunks.ru repositories - govpn.git/commitdiff
Merge branch 'develop' 3.0
authorSergey Matveev <stargrave@stargrave.org>
Sun, 3 May 2015 12:52:16 +0000 (15:52 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 3 May 2015 12:52:16 +0000 (15:52 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
46 files changed:
INSTALL [changed from file to symlink]
NEWS [changed from file to symlink]
README
THANKS [changed from file to symlink]
TODO
VERSION
cmd/govpn-client/main.go
cmd/govpn-server/main.go
cmd/govpn-verifier/main.go [new file with mode: 0644]
common.go
doc/client.texi [new file with mode: 0644]
doc/contacts.texi [moved from doc/bugs.texi with 88% similarity]
doc/cpr.texi [new file with mode: 0644]
doc/developer.texi
doc/download.texi
doc/example.texi [new file with mode: 0644]
doc/govpn.texi
doc/handshake.texi
doc/handshake.txt
doc/identity.texi [new file with mode: 0644]
doc/installation.texi
doc/keywords.texi [new file with mode: 0644]
doc/mtu.texi [new file with mode: 0644]
doc/news.texi [new file with mode: 0644]
doc/noise.texi [new file with mode: 0644]
doc/noncediff.texi [new file with mode: 0644]
doc/overview.texi
doc/pake.texi [new file with mode: 0644]
doc/precautions.texi
doc/server.texi [new file with mode: 0644]
doc/stats.texi [new file with mode: 0644]
doc/thanks.texi [new file with mode: 0644]
doc/timeout.texi [new file with mode: 0644]
doc/transport.texi
doc/user.texi
doc/verifier.texi [new file with mode: 0644]
doc/verifierstruct.texi [new file with mode: 0644]
handshake.go
identify.go
makefile
tap.go
transport.go
transport_test.go [new file with mode: 0644]
utils/newclient.sh
utils/storekey.sh [new file with mode: 0755]
verifier.go [new file with mode: 0644]

diff --git a/INSTALL b/INSTALL
deleted file mode 100644 (file)
index 0f2864b4104f041410be1f90e3c52231848bcbd1..0000000000000000000000000000000000000000
--- a/INSTALL
+++ /dev/null
@@ -1,5 +0,0 @@
-GoVPN is a program written on Go programming language. If you have set
-up $GOPATH environment properly, then simple "make all" should build
-govpn-client and govpn-server executable binaries.
-
-For details see either doc/govpn.info or doc/installation.texi.
diff --git a/INSTALL b/INSTALL
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..09eda899cc0e1df858c1185a925f9d800aadc642
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1 @@
+doc/installation.texi
\ No newline at end of file
diff --git a/NEWS b/NEWS
deleted file mode 100644 (file)
index 4bf946c8d21151a65b851251f90a25cf776fe1b0..0000000000000000000000000000000000000000
--- a/NEWS
+++ /dev/null
@@ -1,51 +0,0 @@
-Release 2.4
------------
-* Added ability to optionally run built-in HTTP-server responding with
-  JSON of all known connected peers information. Real-time client's
-  statistics
-* Documentation is explicitly licensed under GNU FDL 1.3+
-
-Release 2.3
------------
-* Handshake packets became indistinguishable from the random.
-  Now all GoVPN's traffic is the noise for men in the middle.
-* Handshake messages are smaller (16% traffic reduce).
-* Adversary now can not create malicious fake handshake packets that
-  will force server to generate private DH key, preventing entropy
-  consuming and resource heavy computations.
-
-Release 2.2
------------
-* Fixed several possible channel deadlocks.
-
-Release 2.1
------------
-* Fixed Linux-related building.
-
-Release 2.0
------------
-* Added clients identification.
-* Simultaneous several clients support by server.
-* Per-client up/down scripts.
-
-Release 1.5
------------
-* Nonce obfuscation/encryption.
-
-Release 1.4
------------
-* Performance optimizations.
-
-Release 1.3
------------
-* Heartbeat feature.
-* Rehandshake feature.
-* up- and down- optinal scripts.
-
-Release 1.1
------------
-* FreeBSD support.
-
-Release 1.0
------------
-* Initial stable release.
diff --git a/NEWS b/NEWS
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..8be52e420232a063e15ad94bdb5c9e3575056fe7
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+doc/news.texi
\ No newline at end of file
diff --git a/README b/README
index a47dd94a7222f6b938ea7593a3cb5e73cb3f26d1..7fdd2e87e8c1d227e5a84bad6476aac5cabed460 100644 (file)
--- a/README
+++ b/README
@@ -1,10 +1,13 @@
 GoVPN is simple secure free software virtual private network daemon,
-written on Go programming language. It uses Diffie-Hellman Encrypted Key
-Exchange (DH-EKE) for mutual zero-knowledge peers authentication and
-authenticated encrypted data transport. Other features include:
-IPv4/IPv6, rehandshake, heartbeat, pre-shared authentication keys (PSK),
-perfect forward secrecy (PFS), replay attack protection, JSON real-time
-statistics. GNU/Linux and FreeBSD support.
+aimed to be reviewable, secure, DPI-resistant, written on Go.
+
+It uses fast PAKE DH A-EKE for mutual strong zero-knowledge peers
+authentication. Data transport is encrypted, authenticated, hides
+message's length and timestamp. PFS property, resistance to dictionary
+attacks, replay attacks. Built-in heartbeating, rehandshaking, real-time
+statistics, IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support.
+
+GoVPN is free software: see the file COPYING for copying conditions.
 
 Home page: http://www.cypherpunks.ru/govpn/
 also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/
@@ -19,6 +22,4 @@ subject "subscribe" to govpn-devel-request@lists.cypherpunks.ru.
 Development Git source code repository currently is located here:
 https://github.com/stargrave/govpn.git
 
-GoVPN is free software: see the file COPYING for copying conditions.
-
 For futher information please read either doc/govpn.info or doc/govpn.texi.
diff --git a/THANKS b/THANKS
deleted file mode 100644 (file)
index c3172540fc02720b546f74b709d9bf650c4bccf0..0000000000000000000000000000000000000000
--- a/THANKS
+++ /dev/null
@@ -1,5 +0,0 @@
-* Applied Cryptography (C) 1996 Bruce Schneier
-* http://grouper.ieee.org/groups/1363/passwdPK/contributions/jablon.pdf
-* http://cr.yp.to/ecdh.html
-* http://cr.yp.to/snuffle.html
-* http://cr.yp.to/mac.html
diff --git a/THANKS b/THANKS
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..7862103b6ce3dcb94d50a3a99cf56a073679a89e
--- /dev/null
+++ b/THANKS
@@ -0,0 +1 @@
+doc/thanks.texi
\ No newline at end of file
diff --git a/TODO b/TODO
index 63d6ef3c18755c3023c77845ad5ec731d2e0afbf..a2043caacde4e0a934049ddc1a15beca7921bbca 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,2 @@
 * Increase performance
-* Implement rate-control
-* Probably implement alternative Secure Remote Password protocol to allow
-  human memorized passphrases to be used
 * Randomize ports usage
-* Fragmentize packets, noise the traffic
diff --git a/VERSION b/VERSION
index 6b4950e3de2f6c99801a4f5e2ffa01669f358572..9f55b2ccb5f234fc6b87ada62389a3d73815d0d1 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.4
+3.0
index b5f86f9657a0fae84ff5b7336d2f5a8f630cf00a..f49edf8de8c05be0a87c7633e18aaace92e5eeea 100644 (file)
@@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-// Simple secure free software virtual private network daemon.
+// Simple secure free software virtual private network daemon client.
 package main
 
 import (
@@ -25,6 +25,7 @@ import (
        "net"
        "os"
        "os/signal"
+       "time"
 
        "govpn"
 )
@@ -33,13 +34,15 @@ var (
        remoteAddr = flag.String("remote", "", "Remote server address")
        ifaceName  = flag.String("iface", "tap0", "TAP network interface")
        IDRaw      = flag.String("id", "", "Client identification")
-       keyPath    = flag.String("key", "", "Path to authentication key file")
+       keyPath    = flag.String("key", "", "Path to passphrase file")
        upPath     = flag.String("up", "", "Path to up-script")
        downPath   = flag.String("down", "", "Path to down-script")
        stats      = flag.String("stats", "", "Enable stats retrieving on host:port")
-       mtu        = flag.Int("mtu", 1500, "MTU")
+       mtu        = flag.Int("mtu", 1452, "MTU for outgoing packets")
        nonceDiff  = flag.Int("noncediff", 1, "Allow nonce difference")
        timeoutP   = flag.Int("timeout", 60, "Timeout seconds")
+       noisy      = flag.Bool("noise", false, "Enable noise appending")
+       cpr        = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
 )
 
 func main() {
@@ -49,16 +52,24 @@ func main() {
        log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
 
        govpn.MTU = *mtu
-       govpn.Timeout = timeout
-       govpn.Noncediff = *nonceDiff
 
        id := govpn.IDDecode(*IDRaw)
-       govpn.PeersInitDummy(id)
-       key := govpn.KeyRead(*keyPath)
        if id == nil {
                panic("ID is not specified")
        }
 
+       pub, priv := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath))
+       conf := &govpn.PeerConf{
+               Id:          id,
+               Timeout:     time.Second * time.Duration(timeout),
+               Noncediff:   *nonceDiff,
+               NoiseEnable: *noisy,
+               CPR:         *cpr,
+               DSAPub:      pub,
+               DSAPriv:     priv,
+       }
+       govpn.PeersInitDummy(id, conf)
+
        bind, err := net.ResolveUDPAddr("udp", "0.0.0.0:0")
        if err != nil {
                panic(err)
@@ -72,7 +83,11 @@ func main() {
                panic(err)
        }
 
-       tap, ethSink, ethReady, _, err := govpn.TAPListen(*ifaceName)
+       tap, ethSink, ethReady, _, err := govpn.TAPListen(
+               *ifaceName,
+               time.Second*time.Duration(timeout),
+               *cpr,
+       )
        if err != nil {
                panic(err)
        }
@@ -82,11 +97,12 @@ func main() {
        firstUpCall := true
        var peer *govpn.Peer
        var ethPkt []byte
-       var udpPkt *govpn.UDPPkt
+       var udpPkt govpn.UDPPkt
        var udpPktData []byte
        knownPeers := govpn.KnownPeers(map[string]**govpn.Peer{remote.String(): &peer})
 
        log.Println(govpn.VersionGet())
+       log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU())
        if *stats != "" {
                log.Println("Stats are going to listen on", *stats)
                statsPort, err := net.Listen("tcp", *stats)
@@ -100,14 +116,14 @@ func main() {
        signal.Notify(termSignal, os.Interrupt, os.Kill)
 
        log.Println("Starting handshake")
-       handshake := govpn.HandshakeStart(conn, remote, id, key)
+       handshake := govpn.HandshakeStart(conf, conn, remote)
 
 MainCycle:
        for {
                if peer != nil && (peer.BytesIn+peer.BytesOut) > govpn.MaxBytesPerKey {
                        peer.Zero()
                        peer = nil
-                       handshake = govpn.HandshakeStart(conn, remote, id, key)
+                       handshake = govpn.HandshakeStart(conf, conn, remote)
                        log.Println("Rehandshaking")
                }
                select {
@@ -126,7 +142,7 @@ MainCycle:
                        if timeouts >= timeout {
                                break MainCycle
                        }
-                       if udpPkt == nil {
+                       if udpPkt.Addr == nil {
                                udpReady <- struct{}{}
                                continue
                        }
@@ -143,7 +159,7 @@ MainCycle:
                                        udpReady <- struct{}{}
                                        continue
                                }
-                               if p := handshake.Client(id, conn, key, udpPktData); p != nil {
+                               if p := handshake.Client(conn, udpPktData); p != nil {
                                        log.Println("Handshake completed")
                                        if firstUpCall {
                                                go govpn.ScriptCall(*upPath, *ifaceName)
index 2761d340a1b85b032731c31f9982675d225771a8..8aa26ec83bdb8878766fd28efd96c06f6a6e9d98 100644 (file)
@@ -36,9 +36,7 @@ var (
        bindAddr  = flag.String("bind", "[::]:1194", "Bind to address")
        peersPath = flag.String("peers", "peers", "Path to peers keys directory")
        stats     = flag.String("stats", "", "Enable stats retrieving on host:port")
-       mtu       = flag.Int("mtu", 1500, "MTU")
-       nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference")
-       timeoutP  = flag.Int("timeout", 60, "Timeout seconds")
+       mtu       = flag.Int("mtu", 1452, "MTU for outgoing packets")
 )
 
 type PeerReadyEvent struct {
@@ -55,7 +53,7 @@ type PeerState struct {
 }
 
 func NewPeerState(peer *govpn.Peer, iface string) *PeerState {
-       tap, sink, ready, terminate, err := govpn.TAPListen(iface)
+       tap, sink, ready, terminate, err := govpn.TAPListen(iface, peer.Timeout, peer.CPR)
        if err != nil {
                log.Println("Unable to create Eth", err)
                return nil
@@ -78,13 +76,11 @@ type EthEvent struct {
 
 func main() {
        flag.Parse()
-       timeout := time.Second * time.Duration(*timeoutP)
+       timeout := time.Second * time.Duration(govpn.TimeoutDefault)
        var err error
        log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
 
        govpn.MTU = *mtu
-       govpn.Timeout = *timeoutP
-       govpn.Noncediff = *nonceDiff
        govpn.PeersInit(*peersPath)
 
        bind, err := net.ResolveUDPAddr("udp", *bindAddr)
@@ -113,14 +109,16 @@ func main() {
        peerReadySink := make(chan PeerReadyEvent)
        knownPeers := govpn.KnownPeers(make(map[string]**govpn.Peer))
        var peerReady PeerReadyEvent
-       var udpPkt *govpn.UDPPkt
+       var udpPkt govpn.UDPPkt
        var udpPktData []byte
        var ethEvent EthEvent
        var peerId *govpn.PeerId
+       var peerConf *govpn.PeerConf
        var handshakeProcessForce bool
        ethSink := make(chan EthEvent)
 
        log.Println(govpn.VersionGet())
+       log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU())
        if *stats != "" {
                log.Println("Stats are going to listen on", *stats)
                statsPort, err := net.Listen("tcp", *stats)
@@ -196,7 +194,7 @@ MainCycle:
                        }
                        ethEvent.peer.EthProcess(ethEvent.data, conn, ethEvent.ready)
                case udpPkt = <-udpSink:
-                       if udpPkt == nil {
+                       if udpPkt.Addr == nil {
                                udpReady <- struct{}{}
                                continue
                        }
@@ -211,12 +209,18 @@ MainCycle:
                                        udpReady <- struct{}{}
                                        continue
                                }
+                               peerConf = peerId.Conf()
+                               if peerConf == nil {
+                                       log.Println("Can not get peer configuration", peerId.String())
+                                       udpReady <- struct{}{}
+                                       continue
+                               }
                                state, exists = states[addr]
                                if !exists {
-                                       state = govpn.HandshakeNew(udpPkt.Addr)
+                                       state = govpn.HandshakeNew(udpPkt.Addr, peerConf)
                                        states[addr] = state
                                }
-                               peer = state.Server(peerId, conn, udpPktData)
+                               peer = state.Server(conn, udpPktData)
                                if peer != nil {
                                        log.Println("Peer handshake finished", peer)
                                        if _, exists = peers[addr]; exists {
diff --git a/cmd/govpn-verifier/main.go b/cmd/govpn-verifier/main.go
new file mode 100644 (file)
index 0000000..e7cc0e9
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Verifier generator and validator for GoVPN VPN daemon.
+package main
+
+import (
+       "crypto/subtle"
+       "encoding/hex"
+       "flag"
+       "fmt"
+
+       "govpn"
+)
+
+var (
+       IDRaw        = flag.String("id", "", "Client identification")
+       keyPath      = flag.String("key", "", "Path to passphrase file")
+       verifierPath = flag.String("verifier", "", "Optional path to verifier")
+)
+
+func main() {
+       flag.Parse()
+       id := govpn.IDDecode(*IDRaw)
+       if id == nil {
+               panic("ID is not specified")
+       }
+       pub, _ := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath))
+       if *verifierPath == "" {
+               fmt.Println(hex.EncodeToString(pub[:]))
+       } else {
+               verifier, err := hex.DecodeString(govpn.StringFromFile(*verifierPath))
+               if err != nil {
+                       panic("Can not decode verifier")
+               }
+               fmt.Println(subtle.ConstantTimeCompare(verifier[:], pub[:]) == 1)
+       }
+}
index 7eb40600c9fc0a868af4de56b26727a83b228736..fbafd56ab680de6afc61eb50f41890e1ed1babfc 100644 (file)
--- a/common.go
+++ b/common.go
@@ -19,19 +19,19 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package govpn
 
 import (
-       "encoding/hex"
-       "io/ioutil"
        "log"
        "os"
        "os/exec"
        "runtime"
 )
 
+const (
+       TimeoutDefault = 60
+)
+
 var (
-       MTU       int
-       Timeout   int
-       Noncediff int
-       Version   string
+       MTU     int
+       Version string
 )
 
 // Call external program/script.
@@ -52,25 +52,6 @@ func ScriptCall(path, ifaceName string) ([]byte, error) {
        return out, err
 }
 
-// Read authentication key from the file.
-// Key is 64 hexadecimal chars long.
-func KeyRead(path string) *[KeySize]byte {
-       keyData, err := ioutil.ReadFile(path)
-       if err != nil {
-               panic("Unable to read keyfile: " + err.Error())
-       }
-       if len(keyData) < 64 {
-               panic("Key must be 64 hex characters long")
-       }
-       keyDecoded, err := hex.DecodeString(string(keyData[0:64]))
-       if err != nil {
-               panic("Unable to decode the key: " + err.Error())
-       }
-       key := new([KeySize]byte)
-       copy(key[:], keyDecoded)
-       return key
-}
-
 // Zero each byte
 func sliceZero(data []byte) {
        for i := 0; i < len(data); i++ {
diff --git a/doc/client.texi b/doc/client.texi
new file mode 100644 (file)
index 0000000..dfee4f1
--- /dev/null
@@ -0,0 +1,54 @@
+@node Client part
+@section Client part
+
+Except for common @code{-mtu}, @code{-stats} options client has the
+following ones:
+
+@table @code
+
+@item -remote
+Address (@code{host:port} format) of remote server we need to connect to.
+
+@item -iface
+TAP interface name.
+
+@item -id
+Our client's @ref{Identity} (hexadecimal string).
+
+@item -key
+Path to the file with the passphrase. See @ref{Verifier} for
+how to enter passphrase from stdin silently and store it in the file.
+
+@item -timeout
+@ref{Timeout} setting in seconds.
+
+@item -noncediff
+Allowable @ref{Nonce difference}.
+
+@item -noise
+Enable @ref{Noise}.
+
+@item -cpr
+Enable @ref{CPR} in KiB/sec.
+
+@item -up
+Optional path to script that will be executed after connection is
+established. Interface name will be given to it as a first argument.
+
+@item -down
+Same as @code{-up} above, but it is executed when connection is lost,
+when we exit.
+
+@end table
+
+Example up-script that calls DHCP client and IPv6 advertisement
+solicitation:
+
+@example
+client% cat > up.sh <<EOF
+#!/bin/sh
+dhclient $1
+rtsol $1
+EOF
+client% chmod +x up.sh
+@end example
similarity index 88%
rename from doc/bugs.texi
rename to doc/contacts.texi
index b81e6b5022d7afa09dad046bfcc0080f726aace7..9acdbf071f532c4a2938689b974b8e50ce92d0ac 100644 (file)
@@ -1,9 +1,9 @@
-@node Reporting bugs
-@unnumbered Reporting bugs
+@node Contacts
+@unnumbered Contacts
 
 Please send all questions, bug reports and patches to to
 @email{govpn-devel@@lists.cypherpunks.ru} mailing list.
-Also it is used for announcements.
+Announcements go to this mailing list too.
 
 Either visit @url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel}
 for information about subscription options and archived messages access, or
diff --git a/doc/cpr.texi b/doc/cpr.texi
new file mode 100644 (file)
index 0000000..88339db
--- /dev/null
@@ -0,0 +1,10 @@
+@node CPR
+@section CPR
+
+Constant Packet Rate is used to hide fact of underlying payload packets
+appearance. In this mode daemon inserts necessary dummy packets and
+delays other ones.
+
+This mode is turned by @code{-cpr} option, where you specify desired
+outgoing traffic rate in KiB/sec (kibibytes per second). This option also
+forces using of the @ref{Noise}! It is turned off by default.
index b257885b02858362b7ff9a4b72f2d9cddbe8701d..3fd23ed817fae43d18409af8c9862dc351163aad 100644 (file)
@@ -2,24 +2,30 @@
 @unnumbered Developer manual
 
 @table @asis
-@item Nonce and identification encryption
+@item Nonce and identity encryption
 @url{http://143.53.36.235:8080/tea.htm, XTEA}
 @item Data encryption
 @url{http://cr.yp.to/snuffle.html, Salsa20}
 @item Message authentication
 @url{http://cr.yp.to/mac.html, Poly1305}
 @item Password authenticated key agreement
-DH-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519}
+DH-A-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519}
+and @url{http://ed25519.cr.yp.to/, Ed25519}
+@item Key derivation function for verifier generation
+@url{https://en.wikipedia.org/wiki/PBKDF2, PBKDF2} based on
+@url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}
 @item Packet overhead
-24 bytes per packet
+26 bytes per packet
 @item Handshake overhead
-4 UDP (2 from client, 2 from server) packets, 200 bytes total payload
+4 UDP (2 from client, 2 from server) packets, 264 bytes total payload
 @end table
 
 @menu
+* Verifier structure::
 * Transport protocol::
 * Handshake protocol::
 @end menu
 
+@include verifierstruct.texi
 @include transport.texi
 @include handshake.texi
index df60b2416bc2491641a606ad10c4f3a1ca4ce274..9c6193b8f707a65e44b01941a395e85412c3068e 100644 (file)
@@ -19,11 +19,17 @@ You can obtain releases source code prepared tarballs from the links below:
 @tab @url{download/govpn-2.3.tar.xz, link} @url{download/govpn-2.3.tar.xz.sig, sign}
 @tab @code{92986ec6d6da107c6cc1143659e5a154cd19b8f2ede5fa7f5ccc4525ae468e97}
 
+@item 2.4 @tab 42 KiB
+@tab @url{download/govpn-2.4.tar.xz, link} @url{download/govpn-2.4.tar.xz.sig, sign}
+@tab @code{df45225bac2384c5eed73c5cdb05dc3581495e08d365317beb03a2487d46b98c}
+
 @end multitable
 
+Also you can try it's @ref{Contacts, .onion} version.
 Sourceforge.net also provides mirror for the files above:
 @url{http://sourceforge.net/projects/govpn/files/}.
 
-You can obtain it's development source code either by cloning
+You can obtain it's development source code by cloning
 Git repository: @code{git clone https://github.com/stargrave/govpn.git}.
-Pay attention that it does not contain compiled documentation.
+Pay attention that it does not contain compiled documentation and is not
+recommended for porters because of that.
diff --git a/doc/example.texi b/doc/example.texi
new file mode 100644 (file)
index 0000000..c203d43
--- /dev/null
@@ -0,0 +1,70 @@
+@node Example usage
+@section Example usage
+
+Let's assume that there is some insecure link between your computer and
+WiFi-reachable gateway.
+
+@itemize @bullet
+@item You have got @code{wlan0} NIC with 192.168.0/24 network on it.
+@item You want to create virtual encrypted and authenticated 172.16.0/24
+network and use it as a default transport.
+@item @code{wlan0} MTU is 1500, 20 bytes overhead per IPv4. So MTU for
+GoVPN is 1500 - 20 - 8 = 1472.
+@item During startup client and server will say that TAP interface MTU
+is 1432.
+@end itemize
+
+Do not forget about setting @code{GOMAXPROC} environment variable for
+using more than one CPU.
+
+As a preparation you have to generate peer directory (register new
+client) on the server side using @code{utils/newsclient.sh}, generate
+@ref{Verifier} on client side and place it on the server. Assume that
+you made those steps.
+
+GNU/Linux IPv4 client-server example:
+
+@example
+server% echo "echo tap10" >> peers/CLIENTID/up.sh
+server% ip addr add 192.168.0.1/24 dev wlan0
+server% tunctl -t tap10
+server% ip link set mtu 1432 dev tap10
+server% ip addr add 172.16.0.1/24 dev tap10
+server% ip link set up dev tap10
+server% GOMAXPROC=4 govpn-server -bind 192.168.0.1:1194 -mtu 1472
+@end example
+
+@example
+client% umask 066
+client% utils/storekey.sh key.txt
+client% ip addr add 192.168.0.2/24 dev wlan0
+client% tunctl -t tap10
+client% ip link set mtu 1432 dev tap10
+client% ip addr add 172.16.0.2/24 dev tap10
+client% ip link set up dev tap10
+client% ip route add default via 172.16.0.1
+client% export GOMAXPROC=4
+client% while :; do
+    govpn-client -key key.txt -id CLIENTID -iface tap10 \
+        -remote 192.168.0.1:1194 -mtu 1472
+done
+@end example
+
+FreeBSD IPv6 client-server example:
+
+@example
+server% ifconfig em0 inet6 fe80::1/64
+server% GOMAXPROC=4 govpn-server -bind "fe80::1%em0"
+@end example
+
+@example
+client% ifconfig me0 inet6 -ifdisabled auto_linklocal
+client% ifconfig tap10
+client% ifconfig tap10 inet6 fc00::2/96 mtu 1412 up
+client% route -6 add default fc00::1
+client% export GOMAXPROC=4
+client% while :; do
+    govpn-client -key key.txt -id CLIENTID -iface tap10 \
+        -remote [fe80::1%me0]:1194
+done
+@end example
index 8d365711980f65168bc3e3c9c642cfa299bcad3f..fe842b3a04d73c9f6944f52d6b60d629836f3d12 100644 (file)
@@ -21,8 +21,16 @@ A copy of the license is included below.
 @node Top
 @top GoVPN
 
-This manual is for GoVPN -- simple secure free software virtual private
-network (VPN) daemon, written entirely on Go programming language.
+GoVPN is simple secure free software virtual private network daemon,
+aimed to be reviewable, secure, DPI-resistant, written on Go.
+
+It uses fast PAKE DH A-EKE for mutual strong zero-knowledge peers
+authentication. Data transport is encrypted, authenticated, hides
+message's length and timestamp. PFS property, resistance to dictionary
+attacks, replay attacks. Built-in heartbeating, rehandshaking, real-time
+statistics, IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support.
+
+@include keywords.texi
 
 @menu
 * Overview::
@@ -31,26 +39,18 @@ network (VPN) daemon, written entirely on Go programming language.
 * Precautions::
 * User manual::
 * Developer manual::
-* Reporting bugs::
+* Contacts::
 * Copying conditions::
+* Thanks::
 @end menu
 
 @include overview.texi
-
-@node News
-@unnumbered News
-
-@verbatiminclude ../NEWS
-
+@include news.texi
 @include installation.texi
-
 @include precautions.texi
-
 @include user.texi
-
 @include developer.texi
-
-@include bugs.texi
+@include contacts.texi
 
 @node Copying conditions
 @unnumbered Copying conditions
@@ -58,4 +58,5 @@ network (VPN) daemon, written entirely on Go programming language.
 @insertcopying
 @verbatiminclude fdl.txt
 
+@include thanks.texi
 @bye
index 533fef9cc90d16a068b266dd38a2ecaabefdf1c9..6f1e37e32a72098cb00abb3b28238fe8ae1cfcf2 100644 (file)
@@ -4,45 +4,89 @@
 @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.
+encrypted first 64 bits of each message with client's @ref{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.
+
+@strong{Preparation stage}:
 
 @enumerate
 @item
-client generates @code{CPubKey}, random 64bit @code{R} that is used as a
-nonce for encryption
+Client knows only his identity and passphrase written somewhere in the
+human. Server knows his identity and
+@ref{Verifier structure, verifier}: @code{DSAPub}.
 @item
-@verb{|R + enc(PSK, R, CPubKey) + IDtag -> Server|} [48 bytes]
+Client computes verifier which produces @code{DSAPriv} and
+@code{DSAPub}. @code{H()} is @emph{HSalsa20} hash function.
 @item
-server remembers clients address, decrypt @code{CPubKey}, generates
-@code{SPrivKey}/@code{SPubKey}, computes common shared key @code{K}
-(based on @code{CPubKey} and @code{SPrivKey}), generates 64bit random
-number @code{RS} and 256bit random @code{SS}. PSK-encryption uses
-incremented @code{R} (from previous message) for nonce
+Client generates DH keypair: @code{CDHPub} and @code{CDHPriv}.
+Also it generates random 64-bit @code{R} that is used as a nonce for
+symmetric encryption.
+@end enumerate
+
+@strong{Interaction stage}:
+
+@enumerate
 @item
-@verb{|enc(PSK, R+1, SPubKey) + enc(K, R, RS + SS) + IDtag -> Client|} [80 bytes]
+@verb{|R + enc(H(DSAPub), R, CDHPub) + IDtag -> Server|} [48 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},
+@itemize @bullet
+@item Server remembers client address.
+@item Decrypts @code{CDHPub}.
+@item Generates DH keypair: @code{SDHPriv}/@code{SDHPub}.
+@item Computes common shared key @code{K = H(DH(SDHPriv, CDHPub))}.
+@item Generates 64-bit random number @code{RS}.
+@item Generates 256-bit pre-master secret @code{SS}.
+@end itemize
+
 @item
-@verb{|enc(K, R+1, RS + RC + SC) + IDtag -> Server|} [56 bytes]
+@verb{|enc(H(DSAPub), R+1, SDHPub) + enc(K, R, RS + SS) + IDtag -> Client|} [80 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}
+@itemize @bullet
+@item Client decrypts @code{SDHPub}.
+@item Computes @code{K}.
+@item Decrypts @code{RS} and @code{SS}.
+@item Remembers @code{SS}.
+@item Generates 64-bit random number @code{RC}.
+@item Generates 256-bit pre-master secret @code{SC}.
+@item Signs with @code{DSAPriv} key @code{K}.
+@end itemize
+
 @item
-@verb{|ENC(K, 0, RC) + IDtag -> Client|} [16 bytes]
+@verb{|enc(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag -> Server|} [120 bytes]
+
+@item
+@itemize @bullet
+    @item Server decrypts @code{RS}, @code{RC}, @code{SC},
+    @code{Sign(DSAPriv, K)}.
+
+    @item Compares @code{RS} with it's own one sent before. Server
+    decrypts @code{RS}, @code{RC}, @code{SC} with key @code{K}, compares
+    @code{RS} with it's own one sent before.
+
+    @item Verifies @code{K} signature with verifier @code{DSAPub}.
+
+    @item Computes final session encryption key:
+    @code{MasterKey=SS XOR SC}.
+@end itemize
+
 @item
-server switches to the new client
+@verb{|ENC(K, R+2, RC) + IDtag -> Client|} [16 bytes]
+
 @item
-client decrypts @code{RC} and compares with it's own generated one,
-computes final main encryption key @code{S}
+@itemize @bullet
+@item Client decrypts @code{RC}
+@item Compares with it's own one sent before.
+@item Computes final session encryption key as server did.
+@end itemize
+
 @end enumerate
 
-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.
+@code{MasterKey} is high entropy 256-bit key. @code{K} DH-derived one
+has 128-bit security margin and that is why are not in use except in
+handshake process. @code{R*} are required for handshake randomization
+and two-way authentication.
index b36d9f3e8b5500d2d0a1654d57314ca61f7d6136..8c3eb3898be080c96303456a928645b296292610 100644 (file)
@@ -3,19 +3,20 @@ participant Client
 participant Server
 
 Client -> Client : R=rand(64bit)
-Client -> Client : CPrivKey=rand(256bit)
-Client -> Server : R, enc(PSK, R, CPubKey)
-Server -> Server : SPrivKey=rand(256bit)
-Server -> Server : K=DH(SPrivKey, CPubKey)
+Client -> Client : CDHPriv=rand(256bit)
+Client -> Server : R, enc(H(DSAPub), R, CDHPub)
+Server -> Server : SDHPriv=rand(256bit)
+Server -> Server : K=H(DH(SDHPriv, CDHPub))
 Server -> Server : RS=rand(64bit)
 Server -> Server : SS=rand(256bit)
-Server -> Client : enc(PSK, R+1, SPubKey); enc(K, R, RS+SS)
-Client -> Client : K=DH(CPrivKey, SPubKey)
+Server -> Client : enc(H(DSAPub), R+1, SDHPub); enc(K, R, RS+SS)
+Client -> Client : K=H(DH(CDHPriv, SDHPub))
 Client -> Client : RC=rand(64bit); SC=rand(256bit)
-Client -> Server : enc(K, R+1, RS+RC+SC)
+Client -> Server : enc(K, R+1, RS+RC+SC+Sign(DSAPriv, K))
 Server -> Server : compare(RS)
+Server -> Server : Verify(DSAPub, Sign(DSAPriv, K), K)
 Server -> Server : MasterKey=SS XOR SC
-Server -> Client : enc(K, 0x00, RC)
+Server -> Client : enc(K, R+2, RC)
 Client -> Client : compare(RC)
 Client -> Client : MasterKey=SS XOR SC
 @enduml
diff --git a/doc/identity.texi b/doc/identity.texi
new file mode 100644 (file)
index 0000000..3288d2e
--- /dev/null
@@ -0,0 +1,6 @@
+@node Identity
+@section Identity
+
+Client's identity is 128-bit string. It is not secret, so can be
+transmitted and stored in the clear. However handshake applies PRP on it
+to make DPI and deanonymization much harder to success.
index 5aa954311a92ab2755fdeb1acbc1752af6b923fd..13d215919fbcbc194eba6f804edc6c1ffcc60fd1 100644 (file)
@@ -2,11 +2,23 @@
 @unnumbered Installation
 
 GoVPN is written on @url{http://golang.org/, Go programming language},
-with @code{golang.org/x/crypto} libraries dependencies.
 @url{https://www.gnu.org/software/make/, GNU Make} is recommended for
 convenient building. @url{https://www.gnu.org/software/texinfo/, Texinfo}
 is used for building documentation.
 
+Required libraries, dependencies (they are installed automatically when
+using @emph{Make}):
+
+@multitable @columnfractions .40 .20 .40
+@headitem Library @tab Platform @tab Licence
+@item @code{golang.org/x/crypto/poly1305} @tab All @tab BSD 3-Clause
+@item @code{golang.org/x/crypto/salsa20} @tab All @tab BSD 3-Clause
+@item @code{golang.org/x/crypto/xtea} @tab All @tab BSD 3-Clause
+@item @code{golang.org/x/crypto/pbkdf2} @tab All @tab BSD 3-Clause
+@item @code{github.com/agl/ed25519} @tab All @tab BSD 3-Clause
+@item @code{github.com/bigeagle/water} @tab GNU/Linux @tab BSD 3-Clause
+@end multitable
+
 @include download.texi
 
 You @strong{have to} verify downloaded archives integrity and check
@@ -17,12 +29,12 @@ download signature provided with the tarball.
 
 For the very first time you have to import signing public keys. They
 are provided below, but be sure that you are reading them from the
-trusted source. Alternatively check this page from other sources (Tor's
-hidden service for example) and look for the mailing list announcements.
+trusted source. Alternatively check this page from
+@ref{Contacts, other sources} and look for the mailing list announcements.
 
 For example you can get tarball, set proper @code{$GOPATH} and run
 @code{make} (that will install all necessary libraries and build
-client/server binaries) like this:
+@emph{govpn-client}, @emph{govpn-server}, @emph{govpn-verifier} binaries:
 
 @example
 % mkdir -p govpn/src
diff --git a/doc/keywords.texi b/doc/keywords.texi
new file mode 100644 (file)
index 0000000..39a20e3
--- /dev/null
@@ -0,0 +1,12 @@
+Some keywords describing this project: encryption, authentication, key
+exchange, EKE, Diffie-Hellman, DH, DH-EKE, Augmented EKE, A-EKE,
+security, encrypted key exchange, 128-bit margin, DPI, censorship,
+resistance, free software, GPLv3+, reviewability, easy, simple,
+Curve25519, Ed25519, SHA-512, Salsa20, Poly1305, AEAD, XTEA, PBKDF2,
+PRP, signatures, asymmetric cryptography, zero-knowledge password proof,
+PAKE, password, passphrase, password authenticated key exchange, perfect
+forward secrecy, PFS, MAC, nonce, verifier, rehandshake, heartbeat,
+replay attack, MiTM, length hiding, timestamps hiding, noise, constant
+traffic, constant packet rate, CPR, TAP, TAP, VPN, GNU, Linux, FreeBSD,
+IPv6, dictionary attack, mutual authentication, simultaneous clients,
+JSON, HTTP-server, statistics, PRNG, traffic analysis, Go, golang.
diff --git a/doc/mtu.texi b/doc/mtu.texi
new file mode 100644 (file)
index 0000000..e8c5c22
--- /dev/null
@@ -0,0 +1,12 @@
+@node MTU
+@section MTU
+
+MTU command line argument is maximum allowable size of outgoing GoVPN's
+packets. It varies and depends on your environment, so probably has to
+be tuned. By default MTU equals to 1452 bytes: 40 bytes per IPv6 and 8
+bytes per UDP.
+
+Underlying TAP interface has lower MTU value -- 42 bytes smaller: 26
+bytes overhead on transport message and 14 bytes for Ethernet frame.
+Client and server will print what MTU value should be used on TAP
+interface.
diff --git a/doc/news.texi b/doc/news.texi
new file mode 100644 (file)
index 0000000..75ec725
--- /dev/null
@@ -0,0 +1,106 @@
+@node News
+@unnumbered News
+
+@table @strong
+@item Release 3.0
+@itemize @bullet
+@item
+EKE protocol is replaced by Augmented-EKE and static symmetric (both
+sides have it) pre-shared key replaced with server-side verifier. This
+requires, 64 more bytes in handshake traffic, Ed25519 dependency with
+corresponding sign/verify computations, PBKDF2 dependency and its
+usage on the client side during handshake.
+
+A-EKE with PBKDF2-based verifiers is resistant to dictionary attacks,
+can use human memorable passphrases instead of static keys and
+server-side verifiers can not be used for authentication (compromised
+server does not leak client's authentication keys/passphrases).
+
+@item
+Changed transport message structure: added payload packet's length.
+This will increase transport overhead for two bytes, but heartbeat
+packets became smaller
+
+@item
+Ability to hide underlying packets lengths by appending noise, junk
+data during transmission. Each packet can be fill up-ed to it's
+maximal MTU size.
+
+@item
+Ability to hide underlying packets appearance rate, by generating
+Constant Packet Rate traffic. This includes noise generation too.
+@item
+Per-peer @code{-timeout}, @code{-noncediff}, @code{-noise} and
+@code{-cpr} configuration options for server.
+@end itemize
+
+@item Release 2.4
+@itemize @bullet
+@item
+Added ability to optionally run built-in HTTP-server responding with
+JSON of all known connected peers information. Real-time client's
+statistics.
+
+@item
+Documentation is explicitly licensed under GNU FDL 1.3+.
+@end itemize
+
+@item Release 2.3
+@itemize @bullet
+@item
+Handshake packets became indistinguishable from the random.
+Now all GoVPN's traffic is the noise for men in the middle.
+
+@item
+Handshake messages are smaller (16% traffic reduce).
+
+@item
+Adversary now can not create malicious fake handshake packets that
+will force server to generate private DH key, preventing entropy
+consuming and resource heavy computations.
+@end itemize
+
+@item Release 2.2
+@itemize @bullet
+@item Fixed several possible channel deadlocks.
+@end itemize
+
+@item Release 2.1
+@itemize @bullet
+@item Fixed Linux-related building.
+@end itemize
+
+@item Release 2.0
+@itemize @bullet
+@item Added clients identification.
+@item Simultaneous several clients support by server.
+@item Per-client up/down scripts.
+@end itemize
+
+@item Release 1.5
+@itemize @bullet
+@item Nonce obfuscation/encryption.
+@end itemize
+
+@item Release 1.4
+@itemize @bullet
+@item Performance optimizations.
+@end itemize
+
+@item Release 1.3
+@itemize @bullet
+@item Heartbeat feature.
+@item Rehandshake feature.
+@item up- and down- optinal scripts.
+@end itemize
+
+@item Release 1.1
+@itemize @bullet
+@item FreeBSD support.
+@end itemize
+
+@item Release 1.0
+@itemize @bullet
+@item Initial stable release.
+@end itemize
+@end table
diff --git a/doc/noise.texi b/doc/noise.texi
new file mode 100644 (file)
index 0000000..06ee1de
--- /dev/null
@@ -0,0 +1,13 @@
+@node Noise
+@section Noise
+
+So-called noise is used to hide underlying payload packets lengths.
+Without it GoVPN provides confidentiality and authenticity of messages,
+but not their timestamps of appearance and sizes.
+
+You may turn on @code{-noise} option, that forces to fill up all
+outgoing packets to their maximum (MTU) size. As it can be applied only
+to outgoing traffic, you should enable it on both sides in most cases.
+
+Pay attention that this can dramatically @strong{increase} your traffic!
+It is turned off by default.
diff --git a/doc/noncediff.texi b/doc/noncediff.texi
new file mode 100644 (file)
index 0000000..7ce3aa2
--- /dev/null
@@ -0,0 +1,17 @@
+@node Nonce difference
+@section Nonce difference
+
+GoVPN prevents replay attacks by remembering the last used nonce in
+messages from the remote peer. All incoming messages must have higher
+nonce number (technically it is counter), otherwise they are dropped.
+
+Because of UDP nature that does not guarantee packet ordering during
+transmission, GoVPN will drop valid non-replayed UDP packets. That leads
+to performance decrease.
+
+In most cases there is no need in so strict nonce boundaries and
+@code{-noncediff} command line option allows to create the window of
+allowable nonce differences. This is trade-off between highest security
+and possible performance degradation. For example @code{-noncediff 128}
+works rather well (no packet drops) with 1 Gbps link with two switches.
+By default no nonce differences are allowed (highest security).
index 7c5bcc087d53e24f94f5deebf4a9a8d958f81a7e..f6b81ac771075f02233896e49d58619ac6f45b16 100644 (file)
@@ -1,35 +1,49 @@
 @node Overview
 @unnumbered Overview
 
-GoVPN is simple secure virtual private network daemon. It uses
-@url{https://en.wikipedia.org/wiki/Encrypted_key_exchange, Diffie-Hellman Encrypted Key Exchange}
-(DH-EKE) for mutual zero-knowledge peers authentication and
-authenticated encrypted data transport. It is written entirely on
-@url{http://golang.org/, Go programming language}.
+GoVPN is simple secure virtual private network daemon, written entirely
+on @url{http://golang.org/, Go programming language}.
 
-All packets captured on network interface are encrypted, authenticated
-and sent to remote server, that writes them to his interface, and vice
-versa. Client and server use pre-shared authentication key (PSK) and
-128-bit identification key. There are heartbeat packets used to prevent
-session termination because of peers inactivity.
+Reviewability, high 128-bit security margin and
+@url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}
+censorship resistance in mind in free software solution are the main
+goals for that daemon. Most modern widespread protocols and their
+implementations in software are too complex to be reviewed, analyzed and
+modified.
 
-Handshake is used to mutually authenticate peers, exchange common secret
-per-session encryption key and check UDP transport availability.
-
-Because of UDP and authentication overhead: each packet grows in size
-during transmission, so you have to lower you maximum transmission unit
-(MTU) on virtual network interface.
+State off art cryptography technologies includes:
+@url{http://cr.yp.to/snuffle.html, Salsa20} stream encryption,
+@url{http://143.53.36.235:8080/tea.htm, XTEA} PRP,
+@url{http://cr.yp.to/mac.html, Poly1305} message authentication,
+@url{https://en.wikipedia.org/wiki/PBKDF2} password-based key derivation
+function based on @url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}
+hash function,
+@url{https://en.wikipedia.org/wiki/Encrypted_key_exchange,
+Diffie-Hellman Augmented Encrypted Key Exchange}
+(DH-A-EKE) powered by @url{http://cr.yp.to/ecdh.html, Curve25519} and
+@url{http://ed25519.cr.yp.to/, Ed25519} signatures.
+Strong
+@url{https://en.wikipedia.org/wiki/Zero-knowledge_password_proof, zero-knowledge}
+mutual authentication with key exchange stage is invulnerable
+to man-in-the-middle attacks.
+@url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy}
+property guarantee that compromising of long-term authentication
+pre-shared key can not lead to previously captured traffic decrypting.
+Compromising of peers password file on server side won't allow attacker
+to masquerade as the client, because of asymmetric @strong{verifiers}
+usage, resistant to dictionary attacks. Rehandshaking ensures session
+keys rotation. MAC authentication with one-time keys protects against
+@url{https://en.wikipedia.org/wiki/Replay_attack, replay attacks}.
 
-High security is the goal for that daemon. It uses fast cryptography
-algorithms with 128bit security margin, strong mutual zero-knowledge
-authentication and perfect-forward secrecy property. An attacker can not
-know anything about payload (except it's size and time) from captured
-traffic, even if pre-shared key is compromised. Rehandshake is performed
-by client every 4 GiB of transfered data.
+Server can work with several clients simultaneously. Each client is
+@strong{identified} by 128-bit key, that does not leak during handshake
+and each client stays @strong{anonymous} for MiTM and DPI. All settings
+are applied per-peer separately.
 
-Each client also has it's own identification key and server works with
-all of them independently. Identification key is not secret, but it is
-encrypted (obfuscated) during transmission.
+Optional ability to hide payload packets lengths by appending
+@strong{noise} to them during transmission. Ability to generate constant
+packet rate traffic (@strong{CPR}) that will hide even the fact of
+packets appearance, their timestamps.
 
 The only platform specific requirement is TAP network interface support.
 API to that kind of device is different, OS dependent and non portable.
@@ -40,7 +54,7 @@ popular Microsoft Windows or Apple OS X support.
 @itemize @bullet
 @item
 Copylefted free software: licensed under
-@url{http://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}
+@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}
 @item
 Works with @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP}
 network interfaces on top of UDP entirely
@@ -48,23 +62,20 @@ network interfaces on top of UDP entirely
 @url{https://www.gnu.org/, GNU}/Linux and
 @url{http://www.freebsd.org/, FreeBSD} support
 @item IPv6 compatible
-@item Encrypted and authenticated transport
+@item Encrypted and authenticated payload transport
 @item Relatively fast handshake
-@item
-@url{https://en.wikipedia.org/wiki/Replay_attack, Replay attack} protection
-@item
-@url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy}
-(if long-term pre-shared keys are compromised, no captured traffic can
-be decrypted anyway)
-@item
-Mutual two-side authentication (noone will send real network interface
-data unless the other side is authenticated)
-@item
-@url{https://en.wikipedia.org/wiki/Zero-knowledge_password_proof, Zero knowledge}
-authentication (pre-shared key is not transmitted in any form between
-the peers, not even it's hash value)
+@item Password-authenticated key exchange
+@item Server-side password verifiers are secure against dictionary attacks
+@item Attacker can not masquerade a client even with password files compromising
+@item Replay attack protection
+@item Perfect forward secrecy property
+@item Mutual two-side authentication
+@item Zero knowledge authentication
 @item Built-in rehandshake and heartbeat features
 @item Several simultaneous clients support
+@item Per-client configuration options
+@item Hiding of payload packets length with noise
+@item Hiding of payload packets timestamps with constant packet rate traffic
 @item Optional built-in HTTP-server for retrieving information about
 known connected peers in @url{http://json.org/, JSON} format
 @end itemize
diff --git a/doc/pake.texi b/doc/pake.texi
new file mode 100644 (file)
index 0000000..535165e
--- /dev/null
@@ -0,0 +1,30 @@
+@node PAKE
+@section PAKE
+
+Previously we used pre-shared high-entropy long-term static key for
+client-server authentication. Is is secure, but not convenient for some
+user use-cases:
+
+@itemize @bullet
+@item Compromising of password files on either server or client side
+allows attacker to masquerade himself a client.
+@item To prevent compromising of keys on the client side, one needs some
+kind of passphrase protected secure storage (like either PGP with
+decryption to the memory, or full-disk encryption).
+@end itemize
+
+Overall security on the client side is concentrated in passphrase
+(high-entropy password), so it is convenient to use it in GoVPN
+directly, without static on-disk keys. That is why we use password
+authenticated key agreement.
+
+We use "passphrase" term instead of "password". Technically there may be
+no difference between them. But as a rule passphrases are @strong{long}
+strings with low entropy characters. Because of low entropy characters,
+they are memorable. Because of their quantity, they acts as a high
+entropy source.
+
+Passphrases are entered directly by the human on the client side. Server
+side stores previously shared so-called @ref{Verifier}. Verifier contains
+dictionary attack resistant password derivative. Attacker can not use it
+to act as a client.
index bae37b3ec4e5d02e167d8488264aed1f468c6dd6..93b179e427f6829246d6a45e4b70bb0cb0f8068e 100644 (file)
@@ -17,3 +17,8 @@ Also you should @strong{never} use one key for multiple clients. Salsa20
 encryption is randomized in each session, but it depends again on PRNG.
 If it fails, produces equal values at least once, then all you traffic
 related to that key could be decrypted.
+
+We use password (passphrase) authentication, so overall security fully
+depends on it's strength. So you should use long, high-entropy
+passphrases. Also remember to keep passphrase on temporary file as
+described in @ref{Verifier}.
diff --git a/doc/server.texi b/doc/server.texi
new file mode 100644 (file)
index 0000000..c5f2eb2
--- /dev/null
@@ -0,0 +1,76 @@
+@node Server part
+@section Server part
+
+Except for common @code{-mtu}, @code{-stats} options server has the
+following ones:
+
+@table @code
+@item -bind
+Address (@code{host:port} format) we must bind to.
+@item -peers
+Path to the directory containing peers information, database.
+@end table
+
+Peers directory must contain subdirectories with the names of client's
+identities in hexadecimal notation. Each subdirectory has the following
+files:
+
+@table @code
+
+@item verifier
+@strong{Required}. Contains corresponding verifier used to authenticate
+the client in hexadecimal notation. See @ref{Verifier} for how
+to create it.
+
+@item up.sh
+@strong{Required}. up-script executes each time connection with the
+client is established. It's @emph{stdout} output must contain TAP
+interface name on the first string. This script can be simple
+@code{echo tap10}, or maybe more advanced like this:
+    @example
+    #!/bin/sh
+    $tap=$(ifconfig tap create)
+    ifconfig $tap inet6 fc00::1/96 mtu 1412 up
+    echo $tap
+    @end example
+
+@item down.sh
+Optional. Same as @code{up.sh} above, but executes when connection is
+lost.
+
+@item name
+Optional. Contains human readable username. Used to beauty output of
+@ref{Stats}.
+
+@item timeout
+Optional. Contains @ref{Timeout} setting (decimal notation) in seconds.
+Otherwise default minute timeout will be used.
+
+@item noncediff
+Optional. Contains allowable @ref{Nonce difference} setting (decimal
+notation).
+
+@item noise
+Optional. Contains either "1" (enable @ref{Noise} adding), or "0".
+
+@item cpr
+Optional. Contains @ref{CPR} setting (decimal notation) in KiB/sec.
+
+@end table
+
+Each minute server refreshes peers directory contents and adds newly
+appeared identities, deletes an obsolete ones.
+
+You can use convenient @code{utils/newclient.sh} script for new client
+creation:
+
+@example
+% ./utils/newclient.sh Alice
+Place verifier to peers/9b40701bdaf522f2b291cb039490312/verifier
+@end example
+
+@code{9b40701bdaf522f2b291cb039490312} is client's identification.
+@code{peers/9b40701bdaf522f2b291cb039490312/name} contains @emph{Alice},
+@code{peers/9b40701bdaf522f2b291cb039490312/verifier} contains dummy
+verifier and @code{peers/9b40701bdaf522f2b291cb039490312/up.sh} contains
+currently dummy empty up-script.
diff --git a/doc/stats.texi b/doc/stats.texi
new file mode 100644 (file)
index 0000000..f6284e0
--- /dev/null
@@ -0,0 +1,36 @@
+@node Stats
+@section Stats
+
+Both client and server has ability to show statistics about known
+connected peers. You retrieve them by downloading JSON from built-in
+background HTTP-server. You can enable it by specifying
+@code{-stats host:port} argument.
+
+Actually it is not full-fledged HTTP-server: it just accepts connection,
+reads from it (does not parse anything) and writes dummy headers with
+JSON document.
+
+@verbatim
+% govpn-server [...] -stats "[::1]:5678"
+% curl http://localhost:5678/ | jq .
+[
+  {
+    "HeartbeatSent": 1,
+    "HeartbeatRecv": 2,
+    "FramesDup": 0,
+    "FramesUnauth": 0,
+    "Addr": {
+      "Zone": "igb1",
+      "Port": 12989,
+      "IP": "fe80::221:ccff:feb9:ba7f"
+    },
+    "Id": "stargrave",
+    "LastPing": "2015-04-30T22:05:49.426616166+03:00",
+    "LastSent": "2015-04-30T22:05:49.426704138+03:00",
+    "BytesIn": 1392774,
+    "BytesOut": 17228877,
+    "FramesIn": 12412,
+    "FramesOut": 16588
+  }
+]
+@end verbatim
diff --git a/doc/thanks.texi b/doc/thanks.texi
new file mode 100644 (file)
index 0000000..6922370
--- /dev/null
@@ -0,0 +1,19 @@
+@node Thanks
+@unnumbered Thanks
+
+@itemize @bullet
+@item
+@url{https://www.schneier.com/books/applied_cryptography/, Applied Cryptography}
+@copyright{} 1996 Bruce Schneier
+@item
+@url{http://tnlandforms.us/cns05/speke.pdf, Strong Password-Only Authenticated Key Exchange}
+@copyright{} 1996 David P. Jablon
+@item
+@url{https://www.cs.columbia.edu/~smb/papers/aeke.pdf, Augmented Encrypted Key Exchange}:
+a Password-Based Protocol Secure Against Dictionary Attacks and Password
+File Compromise @copyright{} Steven M. Belloving, Michael Merrit
+@item @url{http://cr.yp.to/ecdh.html, A state-of-the-art Diffie-Hellman function}
+@item @url{http://cr.yp.to/snuffle.html, Snuffle 2005: the Salsa20 encryption function}
+@item @url{http://cr.yp.to/mac.html, A state-of-the-art message-authentication code}
+@item @url{http://ed25519.cr.yp.to/, Ed25519: high-speed high-security signatures}
+@end itemize
diff --git a/doc/timeout.texi b/doc/timeout.texi
new file mode 100644 (file)
index 0000000..e750177
--- /dev/null
@@ -0,0 +1,14 @@
+@node Timeout
+@section Timeout
+
+Because of stateless UDP nature there is no way to reliably know if
+remote peer is alive. That is why timeouts are necessary. If no packets
+are sent during timeout period, then remote peer is considered to be
+dead. Timeout option should be synchronized both for server and client.
+
+If there were no packets at all during fourth part of timeout, then
+special heartbeat packet is sent. So VPN connection should be alive all
+the time, even if there is no traffic in corresponding TAP interfaces.
+@strong{Beware}: this consumes traffic.
+
+Stale peers and handshake states are cleaned up every timeout period.
index d0cb75642564dbee51a693c1943fd9bbe9657840..b76286138f63af78bb9f43bc02ec5d774d1e5716 100644 (file)
@@ -2,8 +2,8 @@
 @section Transport protocol
 
 @verbatim
-ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA) +
-    AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA))
+ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) +
+    AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE))
 @end verbatim
 
 All transport and handshake messages are indistinguishable from
@@ -24,6 +24,10 @@ Encrypted @code{SERIAL} is used as a nonce for @code{DATA} encryption:
 encryption key is different during each handshake, so (key, nonce) pair
 is always used only once. @code{ENC} is Salsa20 cipher, with established
 session @code{KEY} and encrypted @code{SERIAL} used as a nonce.
+@code{DATA_SIZE} is @emph{uint16} storing length of the @code{DATA}.
+
+@code{NOISE} is optional. It is just some junk data, intended to fill up
+packet to MTU size. This is useful for concealing payload packets length.
 
 @code{AUTH} is Poly1305 authentication function. First 256 bits of
 Salsa20 output are used as a one-time key for @code{AUTH}. Next 256 bits
index 193e9456979fb238b2f584598fa21e0ec4827101..3080ce1f1ff3974939c64a568b061802936337cc 100644 (file)
 @node User manual
 @unnumbered User manual
 
-Announcements about updates and new releases can be found in
-@ref{Reporting bugs}.
+Announcements about updates and new releases can be found in @ref{Contacts}.
 
 GoVPN is split into two pieces: client and server. Each of them work on
-top of UDP and TAP virtual network interfaces. Client and server have
-several common configuration command line options:
-
-@table @asis
-@item Timeout
-Because of stateless UDP nature there is no way to know if
-remote peer is dead, but after some timeout. Client and server
-heartbeats each other every third part of heartbeat. Also this timeout
-is the time when server purge his obsolete handshake and peers states.
-@item Allowable nonce difference
-To prevent replay attacks we just remember latest received nonce number
-from the remote peer and drop those who has lower ones. Because UDP
-packets can be reordered: that behaviour can lead to dropping of not
-replayed ones. This option gives ability to create some window of
-allowable difference. That opens the door for replay attacks for narrow
-time interval.
-@item MTU
-Maximum transmission unit, maximum frame size that is acceptable on TAP
-interface.
-@end table
-
-Client needs to know his identification, path to the authentication key,
-remote server's address, TAP interface name, and optional path to up and
-down scripts, that will be executed after connection is either initiated
-or terminated.
-
-Server needs to know only the address to listen on and path to directory
-containing peers information. This directory must contain subdirectories
-with the names equal to client's identifications. Each of them must have
-@strong{key} file with corresponding authentication key, @strong{up.sh}
-script that has to print interface's name on the first output line.
-Optionally there can be @code{down.sh} that will be executed when client
-disconnects, and @code{name} file containing human readable client's name.
-
-Each of them have ability to show statistics about known connected
-peers. If you specify @emph{host:port} in @code{-stats} argument, then
-it will run HTTP server on it, responding with JSON documents.
+top of UDP and TAP virtual network interfaces. GoVPN is just a
+tunnelling of Ethernet frames, nothing less, nothing more. All you
+IP-related network management is not touched by VPN at all. You can
+automate it using up and down shell scripts.
 
 @menu
+* Identity::
+* PAKE:: Password Authenticated Key Agreement
+* Timeout::
+* Nonce difference::
+* MTU:: Maximum Transmission Unit
+* Stats::
+* Noise::
+* CPR:: Constant Packet Rate
+* Verifier::
+* Client part::
+* Server part::
 * Example usage::
 @end menu
 
-@node Example usage
-@section Example usage
-
-Let's assume that there is some insecure link between your computer and
-WiFi-reachable gateway. You have got preconfigured @code{wlan0} network
-interface with 192.168.0/24 network. You want to create virtual
-encrypted and authenticated 172.16.0/24 network and use it as a default
-transport. MTU for that wlan0 is 1500 bytes. GoVPN will say that maximum
-MTU for the link is 1476, however it does not take in account TAP's
-Ethernet frame header length, that in my case is 14 bytes long (1476 - 14).
-
-Do not forget about setting @code{GOMAXPROC} environment variable for
-using more than one CPU.
-
-At first you have to generate client's authentication key and client's
-unique identification. There is @code{utils/newclient.sh} script for
-convenience.
-
-@example
-% ./utils/newclient.sh Alice
-9b40701bdaf522f2b291cb039490312
-@end example
-
-@code{9b40701bdaf522f2b291cb039490312} is client's identification.
-@code{peers/9b40701bdaf522f2b291cb039490312/name} contains @emph{Alice},
-@code{peers/9b40701bdaf522f2b291cb039490312/key} contains authentication key and
-@code{peers/9b40701bdaf522f2b291cb039490312/up.sh} contains currently
-dummy empty up-script.
-
-GNU/Linux IPv4 client-server example:
-
-@example
-server% echo "echo tap10" >> peers/CLIENTID/up.sh
-server% ip addr add 192.168.0.1/24 dev wlan0
-server% tunctl -t tap10
-server% ip link set mtu 1462 dev tap10
-server% ip addr add 172.16.0.1/24 dev tap10
-server% ip link set up dev tap10
-server% GOMAXPROC=4 govpn-server -bind 192.168.0.1:1194
-@end example
-
-@example
-client% umask 066
-client% echo MYLONG64HEXKEY > key.txt
-client% ip addr add 192.168.0.2/24 dev wlan0
-client% tunctl -t tap10
-client% ip link set mtu 1462 dev tap10
-client% ip addr add 172.16.0.2/24 dev tap10
-client% ip link set up dev tap10
-client% ip route add default via 172.16.0.1
-client% export GOMAXPROC=4
-client% while :; do
-    govpn-client -key key.txt -id CLIENTID -iface tap10 -remote 192.168.0.1:1194
-done
-@end example
-
-FreeBSD IPv6 client-server example, with stats enabled on the server
-(localhost's 5678 port):
-
-@example
-server% cat > peers/CLIENTID/up.sh <<EOF
-#!/bin/sh
-$tap=$(ifconfig tap create)
-ifconfig $tap inet6 fc00::1/96 mtu 1462 up
-echo $tap
-EOF
-server% ifconfig em0 inet6 fe80::1/64
-server% GOMAXPROC=4 govpn-server -bind fe80::1%em0 -stats [::1]:5678
-@end example
-
-@example
-client% ifconfig me0 inet6 -ifdisabled auto_linklocal
-client% ifconfig tap10
-client% ifconfig tap10 inet6 fc00::2/96 mtu 1462 up
-client% route -6 add default fc00::1
-client% export GOMAXPROC=4
-client% while :; do
-    govpn-client -key key.txt -id CLIENTID -iface tap10 -remote [fe80::1%me0]:1194
-done
-@end example
-
-Example up-script:
-
-@example
-client% cat > up.sh <<EOF
-#!/bin/sh
-dhclient $1
-rtsol $1
-EOF
-client% chmod +x up.sh
-client% govpn -id CLIENTID -key key.txt -iface tap10 -remote [fe80::1%me0]:1194 -up ./up.sh
-@end example
-
-Client will exit if won't finish handshake during @code{-timeout}.
-If no packets are received from remote side during timeout, then daemon
-will stop sending packets to the client and client will exit. In all
-cases you have to rehandshake again.
+@include identity.texi
+@include pake.texi
+@include timeout.texi
+@include noncediff.texi
+@include mtu.texi
+@include stats.texi
+@include noise.texi
+@include cpr.texi
+@include verifier.texi
+@include client.texi
+@include server.texi
+@include example.texi
diff --git a/doc/verifier.texi b/doc/verifier.texi
new file mode 100644 (file)
index 0000000..6e97b7e
--- /dev/null
@@ -0,0 +1,29 @@
+@node Verifier
+@section Verifier
+
+Verifier is created using @code{govpn-verifier} utility. But currently
+Go does not provide native instruments to read passwords without echoing
+them to stdout. You can use @code{utils/storekey.sh} script to read them
+silently.
+
+@example
+% utils/storekey.sh mypass.txt
+Enter passphrase:[hello world]
+% govpn-verifier -id 9da9bf91112d0e4483c135b12d5b48de -key mypass.txt
+210e3878542828901a3af9b4aa00b004de530410eef5c1ba2ffb6d04504371b2
+@end example
+
+Store "210...1b2" string on the server's side in corresponding
+@code{verifier} file.
+
+You can check passphrase against verifier by specifying @code{-verifier}
+option with the path to verifier file:
+
+@example
+% govpn-verifier -id 9da9bf91112d0e4483c135b12d5b48de -key mypass.txt -verifier verifier
+true
+@end example
+
+Plaintext passphrases @strong{must} be stored on volatile memory, for
+example either in memory disk, or on encrypted filesystem with
+restrictive permissions to the file.
diff --git a/doc/verifierstruct.texi b/doc/verifierstruct.texi
new file mode 100644 (file)
index 0000000..3b969dc
--- /dev/null
@@ -0,0 +1,16 @@
+@node Verifier structure
+@section Verifier structure
+
+Verifier is a derivative of the password. It is resistant to
+dictionary attacks and can not be used for authentication (only
+it's verifying).
+
+@verbatim
+SOURCE = PBKDF2(SALT=PeerId, PASSWORD, 1<<16, SHA512)
+PUB, PRIV = Ed25519.Generate(SOURCE)
+@end verbatim
+
+Verifier is public key of Ed25519 generated from the PBKDF2 of the
+passphrase in hexadecimal encoding. @code{PeerId} is used as a 128-bit
+salt. Server stores and knows only verifier. Client can compute the
+whole keypair every time he makes handshake.
index 86e6083482a770e7e8b91d52ec5313598fac6352..0fee82c77d0e70db8d23d8febf4aaa8021f92394 100644 (file)
@@ -24,36 +24,47 @@ import (
        "encoding/binary"
        "log"
        "net"
-       "path"
        "time"
 
+       "github.com/agl/ed25519"
        "golang.org/x/crypto/curve25519"
        "golang.org/x/crypto/salsa20"
        "golang.org/x/crypto/salsa20/salsa"
        "golang.org/x/crypto/xtea"
 )
 
+const (
+       RSize = 8
+       SSize = 32
+)
+
 type Handshake struct {
        addr     *net.UDPAddr
        LastPing time.Time
-       Id       PeerId
-       rNonce   *[8]byte
-       dhPriv   *[32]byte      // own private DH key
-       key      *[KeySize]byte // handshake encryption key
-       rServer  *[8]byte       // random string for authentication
-       rClient  *[8]byte
-       sServer  *[32]byte // secret string for main key calculation
-       sClient  *[32]byte
+       Conf     *PeerConf
+       dsaPubH  *[ed25519.PublicKeySize]byte
+       key      *[32]byte
+       rNonce   *[RSize]byte
+       dhPriv   *[32]byte    // own private DH key
+       rServer  *[RSize]byte // random string for authentication
+       rClient  *[RSize]byte
+       sServer  *[SSize]byte // secret string for main key calculation
+       sClient  *[SSize]byte
 }
 
-func keyFromSecrets(server, client []byte) *[KeySize]byte {
-       k := new([32]byte)
-       for i := 0; i < 32; i++ {
+func keyFromSecrets(server, client []byte) *[SSize]byte {
+       k := new([SSize]byte)
+       for i := 0; i < SSize; i++ {
                k[i] = server[i] ^ client[i]
        }
        return k
 }
 
+// Apply HSalsa20 function for data. Used to hash public keys.
+func HApply(data *[32]byte) {
+       salsa.HSalsa20(data, new([16]byte), data, &salsa.Sigma)
+}
+
 // Zero handshake's memory state
 func (h *Handshake) Zero() {
        if h.rNonce != nil {
@@ -65,6 +76,9 @@ func (h *Handshake) Zero() {
        if h.key != nil {
                sliceZero(h.key[:])
        }
+       if h.dsaPubH != nil {
+               sliceZero(h.dsaPubH[:])
+       }
        if h.rServer != nil {
                sliceZero(h.rServer[:])
        }
@@ -79,10 +93,10 @@ func (h *Handshake) Zero() {
        }
 }
 
-func (h *Handshake) rNonceNext() []byte {
-       nonce := make([]byte, 8)
+func (h *Handshake) rNonceNext(count uint64) []byte {
+       nonce := make([]byte, RSize)
        nonceCurrent, _ := binary.Uvarint(h.rNonce[:])
-       binary.PutUvarint(nonce, nonceCurrent+1)
+       binary.PutUvarint(nonce, nonceCurrent+count)
        return nonce
 }
 
@@ -97,16 +111,20 @@ func dhPrivGen() *[32]byte {
 func dhKeyGen(priv, pub *[32]byte) *[32]byte {
        key := new([32]byte)
        curve25519.ScalarMult(key, priv, pub)
-       salsa.HSalsa20(key, new([16]byte), key, &salsa.Sigma)
+       HApply(key)
        return key
 }
 
 // Create new handshake state.
-func HandshakeNew(addr *net.UDPAddr) *Handshake {
+func HandshakeNew(addr *net.UDPAddr, conf *PeerConf) *Handshake {
        state := Handshake{
                addr:     addr,
                LastPing: time.Now(),
+               Conf:     conf,
        }
+       state.dsaPubH = new([ed25519.PublicKeySize]byte)
+       copy(state.dsaPubH[:], state.Conf.DSAPub[:])
+       HApply(state.dsaPubH)
        return &state
 }
 
@@ -121,26 +139,25 @@ func idTag(id *PeerId, data []byte) []byte {
        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,
-// our own identification and an encryption key. First handshake packet
-// will be sent immediately.
-func HandshakeStart(conn *net.UDPConn, addr *net.UDPAddr, id *PeerId, key *[32]byte) *Handshake {
-       state := HandshakeNew(addr)
+// 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, our own peer configuration.
+// First handshake packet will be sent immediately.
+func HandshakeStart(conf *PeerConf, conn *net.UDPConn, addr *net.UDPAddr) *Handshake {
+       state := HandshakeNew(addr, conf)
 
        state.dhPriv = dhPrivGen()
        dhPub := new([32]byte)
        curve25519.ScalarBaseMult(dhPub, state.dhPriv)
 
-       state.rNonce = new([8]byte)
+       state.rNonce = new([RSize]byte)
        if _, err := rand.Read(state.rNonce[:]); err != nil {
                panic("Can not read random for handshake nonce")
        }
        enc := make([]byte, 32)
-       salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], key)
+       salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], state.dsaPubH)
        data := append(state.rNonce[:], enc...)
-       data = append(data, idTag(id, state.rNonce[:])...)
+       data = append(data, idTag(state.Conf.Id, state.rNonce[:])...)
        if _, err := conn.WriteTo(data, addr); err != nil {
                panic(err)
        }
@@ -149,72 +166,82 @@ 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.
-// Client identity, our outgoing conn connection and
-// received data are required.
+// 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(id *PeerId, conn *net.UDPConn, data []byte) *Peer {
-       // R + ENC(PSK, dh_client_pub) + IDtag
+func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
+       // R + ENC(H(DSAPub), R, CDHPub) + IDtag
        if len(data) == 48 && h.rNonce == nil {
-               key := KeyRead(path.Join(PeersPath, id.String(), "key"))
-               h.Id = *id
-
                // Generate private DH key
                h.dhPriv = dhPrivGen()
                dhPub := new([32]byte)
                curve25519.ScalarBaseMult(dhPub, h.dhPriv)
 
+               h.rNonce = new([RSize]byte)
+               copy(h.rNonce[:], data[:RSize])
+
                // Decrypt remote public key and compute shared key
                dec := new([32]byte)
-               salsa20.XORKeyStream(dec[:], data[8:8+32], data[:8], key)
+               salsa20.XORKeyStream(dec[:], data[RSize:RSize+32], h.rNonce[:], h.dsaPubH)
                h.key = dhKeyGen(h.dhPriv, dec)
 
-               // Compute nonce and encrypt our public key
-               h.rNonce = new([8]byte)
-               copy(h.rNonce[:], data[:8])
-
                encPub := make([]byte, 32)
-               salsa20.XORKeyStream(encPub, dhPub[:], h.rNonceNext(), key)
+               salsa20.XORKeyStream(encPub, dhPub[:], h.rNonceNext(1), h.dsaPubH)
 
                // Generate R* and encrypt them
-               h.rServer = new([8]byte)
+               h.rServer = new([RSize]byte)
                if _, err := rand.Read(h.rServer[:]); err != nil {
                        panic("Can not read random for handshake random key")
                }
-               h.sServer = new([32]byte)
+               h.sServer = new([SSize]byte)
                if _, err := rand.Read(h.sServer[:]); err != nil {
                        panic("Can not read random for handshake shared key")
                }
-               encRs := make([]byte, 8+32)
+               encRs := make([]byte, RSize+SSize)
                salsa20.XORKeyStream(encRs, append(h.rServer[:], h.sServer[:]...), h.rNonce[:], h.key)
 
                // Send that to client
                if _, err := conn.WriteTo(
-                       append(encPub, append(encRs, idTag(id, encPub)...)...), h.addr); err != nil {
+                       append(encPub, append(encRs, idTag(h.Conf.Id, encPub)...)...), h.addr); err != nil {
                        panic(err)
                }
                h.LastPing = time.Now()
        } else
-       // ENC(K, RS + RC + SC) + IDtag
-       if len(data) == 56 && h.rClient == nil {
+       // ENC(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag
+       if len(data) == 120 && h.rClient == nil {
                // Decrypted Rs compare rServer
-               decRs := make([]byte, 8+8+32)
-               salsa20.XORKeyStream(decRs, data[:8+8+32], h.rNonceNext(), h.key)
-               if subtle.ConstantTimeCompare(decRs[:8], h.rServer[:]) != 1 {
+               dec := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
+               salsa20.XORKeyStream(
+                       dec,
+                       data[:RSize+RSize+SSize+ed25519.SignatureSize],
+                       h.rNonceNext(1),
+                       h.key,
+               )
+               if subtle.ConstantTimeCompare(dec[:RSize], h.rServer[:]) != 1 {
                        log.Println("Invalid server's random number with", h.addr)
                        return nil
                }
+               sign := new([ed25519.SignatureSize]byte)
+               copy(sign[:], dec[RSize+RSize+SSize:])
+               if !ed25519.Verify(h.Conf.DSAPub, h.key[:], sign) {
+                       log.Println("Invalid signature from", h.addr)
+                       return nil
+               }
 
                // 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, idTag(id, enc)...), h.addr); err != nil {
+               enc := make([]byte, RSize)
+               salsa20.XORKeyStream(enc, dec[RSize:RSize+RSize], h.rNonceNext(2), h.key)
+               if _, err := conn.WriteTo(append(enc, idTag(h.Conf.Id, enc)...), h.addr); err != nil {
                        panic(err)
                }
 
                // Switch peer
-               peer := newPeer(h.addr, h.Id, 0, keyFromSecrets(h.sServer[:], decRs[8+8:]))
+               peer := newPeer(
+                       h.addr,
+                       h.Conf,
+                       0,
+                       keyFromSecrets(h.sServer[:], dec[RSize+RSize:RSize+RSize+SSize]))
                h.LastPing = time.Now()
                return peer
        } else {
@@ -230,9 +257,9 @@ func (h *Handshake) Server(id *PeerId, conn *net.UDPConn, data []byte) *Peer {
 // 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(id *PeerId, conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer {
+func (h *Handshake) Client(conn *net.UDPConn, data []byte) *Peer {
        switch len(data) {
-       case 80: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + IDtag
+       case 80: // ENC(H(DSAPub), R+1, SDHPub) + ENC(K, R, RS + SS) + IDtag
                if h.key != nil {
                        log.Println("Invalid handshake stage from", h.addr)
                        return nil
@@ -240,52 +267,55 @@ func (h *Handshake) Client(id *PeerId, conn *net.UDPConn, key *[KeySize]byte, da
 
                // Decrypt remote public key and compute shared key
                dec := new([32]byte)
-               salsa20.XORKeyStream(dec[:], data[:32], h.rNonceNext(), key)
+               salsa20.XORKeyStream(dec[:], data[:32], h.rNonceNext(1), h.dsaPubH)
                h.key = dhKeyGen(h.dhPriv, dec)
 
                // Decrypt Rs
-               decRs := make([]byte, 8+32)
-               salsa20.XORKeyStream(decRs, data[32:32+8+32], h.rNonce[:], h.key)
-               h.rServer = new([8]byte)
-               copy(h.rServer[:], decRs[:8])
-               h.sServer = new([32]byte)
-               copy(h.sServer[:], decRs[8:])
-
-               // Generate R* and encrypt them
-               h.rClient = new([8]byte)
+               decRs := make([]byte, RSize+SSize)
+               salsa20.XORKeyStream(decRs, data[SSize:32+RSize+SSize], h.rNonce[:], h.key)
+               h.rServer = new([RSize]byte)
+               copy(h.rServer[:], decRs[:RSize])
+               h.sServer = new([SSize]byte)
+               copy(h.sServer[:], decRs[RSize:])
+
+               // Generate R* and signature and encrypt them
+               h.rClient = new([RSize]byte)
                if _, err := rand.Read(h.rClient[:]); err != nil {
                        panic("Can not read random for handshake random key")
                }
-               h.sClient = new([32]byte)
+               h.sClient = new([SSize]byte)
                if _, err := rand.Read(h.sClient[:]); err != nil {
                        panic("Can not read random for handshake shared key")
                }
-               encRs := make([]byte, 8+8+32)
-               salsa20.XORKeyStream(encRs,
+               sign := ed25519.Sign(h.Conf.DSAPriv, h.key[:])
+
+               enc := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
+               salsa20.XORKeyStream(enc,
                        append(h.rServer[:],
-                               append(h.rClient[:], h.sClient[:]...)...), h.rNonceNext(), h.key)
+                               append(h.rClient[:],
+                                       append(h.sClient[:], sign[:]...)...)...), h.rNonceNext(1), h.key)
 
                // Send that to server
-               if _, err := conn.WriteTo(append(encRs, idTag(id, encRs)...), h.addr); err != nil {
+               if _, err := conn.WriteTo(append(enc, idTag(h.Conf.Id, enc)...), h.addr); err != nil {
                        panic(err)
                }
                h.LastPing = time.Now()
-       case 16: // ENC(K, RC) + IDtag
+       case 16: // ENC(K, R+2, RC) + IDtag
                if h.key == nil {
                        log.Println("Invalid handshake stage from", h.addr)
                        return nil
                }
 
                // Decrypt rClient
-               dec := make([]byte, 8)
-               salsa20.XORKeyStream(dec, data[:8], make([]byte, 8), h.key)
+               dec := make([]byte, RSize)
+               salsa20.XORKeyStream(dec, data[:RSize], h.rNonceNext(2), h.key)
                if subtle.ConstantTimeCompare(dec, h.rClient[:]) != 1 {
                        log.Println("Invalid client's random number with", h.addr)
                        return nil
                }
 
                // Switch peer
-               peer := newPeer(h.addr, h.Id, 1, keyFromSecrets(h.sServer[:], h.sClient[:]))
+               peer := newPeer(h.addr, h.Conf, 1, keyFromSecrets(h.sServer[:], h.sClient[:]))
                h.LastPing = time.Now()
                return peer
        default:
index 6c16da08a2a3c1cc124f0b0995b99142e24aad59..4363c58d39fd9bd001b8aaae73cab6c8e2b93e81 100644 (file)
@@ -25,10 +25,12 @@ import (
        "log"
        "os"
        "path"
+       "strconv"
        "strings"
        "sync"
        "time"
 
+       "github.com/agl/ed25519"
        "golang.org/x/crypto/xtea"
 )
 
@@ -53,12 +55,25 @@ func (id PeerId) MarshalJSON() ([]byte, error) {
        return []byte(`"` + result + `"`), nil
 }
 
+type PeerConf struct {
+       Id          *PeerId
+       Timeout     time.Duration
+       Noncediff   int
+       NoiseEnable bool
+       CPR         int
+       // This is passphrase verifier
+       DSAPub *[ed25519.PublicKeySize]byte
+       // This field exists only in dummy configuration on client's side
+       DSAPriv *[ed25519.PrivateKeySize]byte
+}
+
 type cipherCache map[PeerId]*xtea.Cipher
 
 var (
        PeersPath       string
        IDsCache        cipherCache
        cipherCacheLock sync.RWMutex
+       dummyConf       *PeerConf
 )
 
 // Initialize (pre-cache) available peers info.
@@ -73,15 +88,15 @@ func PeersInit(path string) {
        }()
 }
 
-// Initialize dummy cache for client-side usage. It will consist only
-// of single key.
-func PeersInitDummy(id *PeerId) {
+// Initialize dummy cache for client-side usage.
+func PeersInitDummy(id *PeerId, conf *PeerConf) {
        IDsCache = make(map[PeerId]*xtea.Cipher)
        cipher, err := xtea.NewCipher(id[:])
        if err != nil {
                panic(err)
        }
        IDsCache[*id] = cipher
+       dummyConf = conf
 }
 
 // Refresh IDsCache: remove disappeared keys, add missing ones with
@@ -147,6 +162,62 @@ func (cc cipherCache) Find(data []byte) *PeerId {
        return nil
 }
 
+func readIntFromFile(path string) (int, error) {
+       data, err := ioutil.ReadFile(path)
+       if err != nil {
+               return 0, err
+       }
+       val, err := strconv.Atoi(strings.TrimRight(string(data), "\n"))
+       if err != nil {
+               return 0, err
+       }
+       return val, nil
+}
+
+// Get peer related configuration.
+func (id *PeerId) Conf() *PeerConf {
+       if dummyConf != nil {
+               return dummyConf
+       }
+       conf := PeerConf{Id: id, Noncediff: 1, NoiseEnable: false, CPR: 0}
+       peerPath := path.Join(PeersPath, id.String())
+
+       verPath := path.Join(peerPath, "verifier")
+       keyData, err := ioutil.ReadFile(verPath)
+       if err != nil {
+               log.Println("Unable to read verifier:", verPath)
+               return nil
+       }
+       if len(keyData) < ed25519.PublicKeySize*2 {
+               log.Println("Verifier must be 64 hex characters long:", verPath)
+               return nil
+       }
+       keyDecoded, err := hex.DecodeString(string(keyData[:ed25519.PublicKeySize*2]))
+       if err != nil {
+               log.Println("Unable to decode the key:", err.Error(), verPath)
+               return nil
+       }
+       conf.DSAPub = new([ed25519.PublicKeySize]byte)
+       copy(conf.DSAPub[:], keyDecoded)
+
+       timeout := TimeoutDefault
+       if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil {
+               timeout = val
+       }
+       conf.Timeout = time.Second * time.Duration(timeout)
+
+       if val, err := readIntFromFile(path.Join(peerPath, "noncediff")); err == nil {
+               conf.Noncediff = val
+       }
+       if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
+               conf.NoiseEnable = true
+       }
+       if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil {
+               conf.CPR = val
+       }
+       return &conf
+}
+
 // Decode identification string.
 // It must be 32 hexadecimal characters long.
 // If it is not the valid one, then return nil.
index 3dec45c65c5f51e90273fb55cddc21e4e6084325..e5db1dabde20f91e8e9b7237e50b25a14be8918b 100644 (file)
--- a/makefile
+++ b/makefile
@@ -1,17 +1,25 @@
-.PHONY: govpn-client govpn-server
+.PHONY: govpn-client govpn-server govpn-verifier
 
 LDFLAGS=-X govpn.Version $(shell cat VERSION)
 
-all: govpn-client govpn-server
+all: govpn-client govpn-server govpn-verifier
 
 dependencies:
        [ "$(shell uname)" = FreeBSD ] || go get github.com/bigeagle/water
        go get golang.org/x/crypto/poly1305
        go get golang.org/x/crypto/salsa20
        go get golang.org/x/crypto/xtea
+       go get golang.org/x/crypto/pbkdf2
+       go get github.com/agl/ed25519
 
 govpn-client: dependencies
        go build -ldflags "$(LDFLAGS)" govpn/cmd/govpn-client
 
 govpn-server: dependencies
        go build -ldflags "$(LDFLAGS)" govpn/cmd/govpn-server
+
+govpn-verifier: dependencies
+       go build -ldflags "$(LDFLAGS)" govpn/cmd/govpn-verifier
+
+bench: dependencies
+       GOMAXPROC=2 go test -bench .
diff --git a/tap.go b/tap.go
index 4fee83a28c3ea920cb619da22a1a9ddc984d71e7..a80795534a70adf431b183c5d339e2ca61c51706 100644 (file)
--- a/tap.go
+++ b/tap.go
@@ -20,11 +20,14 @@ package govpn
 
 import (
        "io"
-       "log"
 
        "golang.org/x/crypto/poly1305"
 )
 
+const (
+       EtherSize = 14
+)
+
 type TAP struct {
        Name   string
        dev    io.ReadWriter
@@ -34,8 +37,14 @@ type TAP struct {
        synced bool
 }
 
+// Return maximal acceptable TAP interface MTU. This is daemon's MTU
+// minus nonce, MAC, packet size mark and Ethernet header sizes.
+func TAPMaxMTU() int {
+       return MTU - poly1305.TagSize - NonceSize - PktSizeSize - EtherSize
+}
+
 func NewTAP(ifaceName string) (*TAP, error) {
-       maxIfacePktSize := MTU - poly1305.TagSize - NonceSize
+       maxIfacePktSize := TAPMaxMTU() + EtherSize
        tapRaw, err := newTAPer(ifaceName)
        if err != nil {
                return nil, err
@@ -63,8 +72,6 @@ func NewTAP(ifaceName string) (*TAP, error) {
        return &tap, nil
 }
 
-func (t *TAP) Write(data []byte) {
-       if _, err := t.dev.Write(data); err != nil {
-               log.Println("Error writing to iface: ", err)
-       }
+func (t *TAP) Write(data []byte) (n int, err error) {
+       return t.dev.Write(data)
 }
index 3c284f20990c51fc05c6da91c46ce4db3f7f4dcd..ddcfbf90dc28f21904f3230de0c6f9e5cea0adb4 100644 (file)
@@ -19,8 +19,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package govpn
 
 import (
-       "crypto/subtle"
        "encoding/binary"
+       "io"
        "log"
        "net"
        "time"
@@ -32,12 +32,14 @@ import (
 
 const (
        NonceSize = 8
-       KeySize   = 32
        // S20BS is Salsa20's internal blocksize in bytes
-       S20BS         = 64
-       HeartbeatSize = 12
+       S20BS = 64
        // Maximal amount of bytes transfered with single key (4 GiB)
        MaxBytesPerKey int64 = 1 << 32
+       // Size of packet's size mark in bytes
+       PktSizeSize = 2
+       // Heartbeat rate, relative to Timeout
+       TimeoutHeartbeat = 4
 )
 
 type UDPPkt struct {
@@ -46,35 +48,55 @@ type UDPPkt struct {
 }
 
 type Peer struct {
-       Addr          *net.UDPAddr
-       Id            PeerId
-       Key           *[KeySize]byte `json:"-"`
-       NonceOur      uint64         `json:"-"`
-       NonceRecv     uint64         `json:"-"`
-       NonceCipher   *xtea.Cipher   `json:"-"`
+       Addr *net.UDPAddr
+       Id   *PeerId
+
+       // Traffic behaviour
+       NoiseEnable bool
+       CPR         int
+       CPRCycle    time.Duration `json:"-"`
+
+       // Cryptography related
+       Key         *[SSize]byte `json:"-"`
+       Noncediff   int
+       NonceOur    uint64       `json:"-"`
+       NonceRecv   uint64       `json:"-"`
+       NonceCipher *xtea.Cipher `json:"-"`
+
+       // Timers
+       Timeout       time.Duration `json:"-"`
+       Established   time.Time
        LastPing      time.Time
        LastSent      time.Time
-       buf           []byte
-       tag           *[poly1305.TagSize]byte
-       keyAuth       *[KeySize]byte
-       nonceRecv     uint64
-       frame         []byte
-       nonce         []byte
-       BytesIn       int64
-       BytesOut      int64
-       FramesIn      int
-       FramesOut     int
-       FramesUnauth  int
-       FramesDup     int
-       HeartbeatRecv int
-       HeartbeatSent int
+       willSentCycle time.Time
+
+       // This variables are initialized only once to relief GC
+       buf       []byte
+       tag       *[poly1305.TagSize]byte
+       keyAuth   *[32]byte
+       nonceRecv uint64
+       frame     []byte
+       nonce     []byte
+       pktSize   uint64
+
+       // Statistics
+       BytesIn         int64
+       BytesOut        int64
+       BytesPayloadIn  int64
+       BytesPayloadOut int64
+       FramesIn        int
+       FramesOut       int
+       FramesUnauth    int
+       FramesDup       int
+       HeartbeatRecv   int
+       HeartbeatSent   int
 }
 
 func (p *Peer) String() string {
        return p.Id.String() + ":" + p.Addr.String()
 }
 
-// Zero peer's memory state
+// Zero peer's memory state.
 func (p *Peer) Zero() {
        sliceZero(p.Key[:])
        sliceZero(p.tag[:])
@@ -85,27 +107,17 @@ func (p *Peer) Zero() {
 }
 
 var (
-       HeartbeatMark   = []byte("\x00\x00\x00HEARTBEAT")
-       Emptiness       = make([]byte, KeySize)
-       taps            = make(map[string]*TAP)
-       heartbeatPeriod *time.Duration
+       Emptiness = make([]byte, 1<<14)
+       taps      = make(map[string]*TAP)
 )
 
-func heartbeatPeriodGet() time.Duration {
-       if heartbeatPeriod == nil {
-               period := time.Second * time.Duration(Timeout/4)
-               heartbeatPeriod = &period
-       }
-       return *heartbeatPeriod
-}
-
 // Create TAP listening goroutine.
 // This function takes required TAP interface name, opens it and allocates
 // a buffer where all frame data will be written, channel where information
 // about number of read bytes is sent to, synchronization channel (external
 // processes tell that read buffer can be used again) and possible channel
 // opening error.
-func TAPListen(ifaceName string) (*TAP, chan []byte, chan struct{}, chan struct{}, error) {
+func TAPListen(ifaceName string, timeout time.Duration, cpr int) (*TAP, chan []byte, chan struct{}, chan struct{}, error) {
        var tap *TAP
        var err error
        tap, exists := taps[ifaceName]
@@ -122,7 +134,13 @@ func TAPListen(ifaceName string) (*TAP, chan []byte, chan struct{}, chan struct{
        sinkSkip := make(chan struct{})
 
        go func() {
-               heartbeat := time.Tick(heartbeatPeriodGet())
+               cprCycle := cprCycleCalculate(cpr)
+               if cprCycle != time.Duration(0) {
+                       timeout = cprCycle
+               } else {
+                       timeout = timeout / TimeoutHeartbeat
+               }
+               heartbeat := time.Tick(timeout)
                var pkt []byte
        ListenCycle:
                for {
@@ -166,9 +184,9 @@ func TAPListen(ifaceName string) (*TAP, chan []byte, chan struct{}, chan struct{
 // all UDP packet data will be saved, channel where information about
 // remote address and number of written bytes are stored, and a channel
 // used to tell that buffer is ready to be overwritten.
-func ConnListen(conn *net.UDPConn) (chan *UDPPkt, []byte, chan struct{}) {
+func ConnListen(conn *net.UDPConn) (chan UDPPkt, []byte, chan struct{}) {
        buf := make([]byte, MTU)
-       sink := make(chan *UDPPkt)
+       sink := make(chan UDPPkt)
        sinkReady := make(chan struct{})
        go func(conn *net.UDPConn) {
                var n int
@@ -180,21 +198,21 @@ func ConnListen(conn *net.UDPConn) (chan *UDPPkt, []byte, chan struct{}) {
                        n, addr, err = conn.ReadFromUDP(buf)
                        if err != nil {
                                // This is needed for ticking the timeouts counter outside
-                               sink <- nil
+                               sink <- UDPPkt{nil, 0}
                                continue
                        }
-                       sink <- &UDPPkt{addr, n}
+                       sink <- UDPPkt{addr, n}
                }
        }(conn)
        sinkReady <- struct{}{}
        return sink, buf, sinkReady
 }
 
-func newNonceCipher(key *[KeySize]byte) *xtea.Cipher {
+func newNonceCipher(key *[32]byte) *xtea.Cipher {
        nonceKey := make([]byte, 16)
        salsa20.XORKeyStream(
                nonceKey,
-               make([]byte, KeySize),
+               make([]byte, 32),
                make([]byte, xtea.BlockSize),
                key,
        )
@@ -205,18 +223,41 @@ func newNonceCipher(key *[KeySize]byte) *xtea.Cipher {
        return ciph
 }
 
-func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer {
+func cprCycleCalculate(rate int) time.Duration {
+       if rate == 0 {
+               return time.Duration(0)
+       }
+       return time.Second / time.Duration(rate*(1<<10)/MTU)
+}
+
+func newPeer(addr *net.UDPAddr, conf *PeerConf, nonce int, key *[SSize]byte) *Peer {
+       now := time.Now()
+       timeout := conf.Timeout
+       cprCycle := cprCycleCalculate(conf.CPR)
+       noiseEnable := conf.NoiseEnable
+       if conf.CPR > 0 {
+               noiseEnable = true
+               timeout = cprCycle
+       } else {
+               timeout = timeout / TimeoutHeartbeat
+       }
        peer := Peer{
                Addr:        addr,
-               LastPing:    time.Now(),
-               Id:          id,
-               NonceOur:    uint64(Noncediff + nonce),
-               NonceRecv:   uint64(Noncediff + 0),
+               Timeout:     timeout,
+               Established: now,
+               LastPing:    now,
+               Id:          conf.Id,
+               NoiseEnable: noiseEnable,
+               CPR:         conf.CPR,
+               CPRCycle:    cprCycle,
+               Noncediff:   conf.Noncediff,
+               NonceOur:    uint64(conf.Noncediff + nonce),
+               NonceRecv:   uint64(conf.Noncediff + 0),
                Key:         key,
                NonceCipher: newNonceCipher(key),
                buf:         make([]byte, MTU+S20BS),
                tag:         new([poly1305.TagSize]byte),
-               keyAuth:     new([KeySize]byte),
+               keyAuth:     new([SSize]byte),
                nonce:       make([]byte, NonceSize),
        }
        return &peer
@@ -227,9 +268,9 @@ func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer
 // ConnListen'es synchronization channel used to tell him that he is
 // free to receive new packets. Authenticated and decrypted packets
 // will be written to the interface immediately (except heartbeat ones).
-func (p *Peer) UDPProcess(udpPkt []byte, tap *TAP, ready chan struct{}) bool {
+func (p *Peer) UDPProcess(udpPkt []byte, tap io.Writer, ready chan struct{}) bool {
        size := len(udpPkt)
-       copy(p.buf[:KeySize], Emptiness)
+       copy(p.buf, Emptiness)
        copy(p.tag[:], udpPkt[size-poly1305.TagSize:])
        copy(p.buf[S20BS:], udpPkt[NonceSize:size-poly1305.TagSize])
        salsa20.XORKeyStream(
@@ -238,7 +279,7 @@ func (p *Peer) UDPProcess(udpPkt []byte, tap *TAP, ready chan struct{}) bool {
                udpPkt[:NonceSize],
                p.Key,
        )
-       copy(p.keyAuth[:], p.buf[:KeySize])
+       copy(p.keyAuth[:], p.buf[:SSize])
        if !poly1305.Verify(p.tag, udpPkt[:size-poly1305.TagSize], p.keyAuth) {
                ready <- struct{}{}
                p.FramesUnauth++
@@ -246,60 +287,78 @@ func (p *Peer) UDPProcess(udpPkt []byte, tap *TAP, ready chan struct{}) bool {
        }
        p.NonceCipher.Decrypt(p.buf, udpPkt[:NonceSize])
        p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize])
-       if int(p.NonceRecv)-Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-Noncediff {
+       if int(p.NonceRecv)-p.Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-p.Noncediff {
                ready <- struct{}{}
                p.FramesDup++
                return false
        }
        ready <- struct{}{}
+       p.FramesIn++
+       p.BytesIn += int64(size)
        p.LastPing = time.Now()
        p.NonceRecv = p.nonceRecv
-       p.frame = p.buf[S20BS : S20BS+size-NonceSize-poly1305.TagSize]
-       p.BytesIn += int64(len(p.frame))
-       p.FramesIn++
-       if subtle.ConstantTimeCompare(p.frame[:HeartbeatSize], HeartbeatMark) == 1 {
+       p.pktSize, _ = binary.Uvarint(p.buf[S20BS : S20BS+PktSizeSize])
+       if p.pktSize == 0 {
                p.HeartbeatRecv++
                return true
        }
+       p.frame = p.buf[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSize]
+       p.BytesPayloadIn += int64(p.pktSize)
        tap.Write(p.frame)
        return true
 }
 
+type WriteToer interface {
+       WriteTo([]byte, net.Addr) (int, error)
+}
+
 // Process incoming Ethernet packet.
 // ethPkt is received data, conn is our outgoing connection.
 // ready channel is TAPListen's synchronization channel used to tell him
 // that he is free to receive new packets. Encrypted and authenticated
 // packets will be sent to remote Peer side immediately.
-func (p *Peer) EthProcess(ethPkt []byte, conn *net.UDPConn, ready chan struct{}) {
+func (p *Peer) EthProcess(ethPkt []byte, conn WriteToer, ready chan struct{}) {
        now := time.Now()
        size := len(ethPkt)
        // If this heartbeat is necessary
-       if size == 0 && !p.LastSent.Add(heartbeatPeriodGet()).Before(now) {
+       if size == 0 && !p.LastSent.Add(p.Timeout).Before(now) {
                return
        }
-       copy(p.buf[:KeySize], Emptiness)
+       copy(p.buf, Emptiness)
        if size > 0 {
-               copy(p.buf[S20BS:], ethPkt)
+               copy(p.buf[S20BS+PktSizeSize:], ethPkt)
                ready <- struct{}{}
+               binary.PutUvarint(p.buf[S20BS:S20BS+PktSizeSize], uint64(size))
+               p.BytesPayloadOut += int64(size)
        } else {
                p.HeartbeatSent++
-               copy(p.buf[S20BS:], HeartbeatMark)
-               size = HeartbeatSize
        }
 
-       p.NonceOur = p.NonceOur + 2
+       p.NonceOur += 2
        copy(p.nonce, Emptiness)
        binary.PutUvarint(p.nonce, p.NonceOur)
        p.NonceCipher.Encrypt(p.nonce, p.nonce)
 
        salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key)
        copy(p.buf[S20BS-NonceSize:S20BS], p.nonce)
-       copy(p.keyAuth[:], p.buf[:KeySize])
-       p.frame = p.buf[S20BS-NonceSize : S20BS+size]
+       copy(p.keyAuth[:], p.buf[:SSize])
+       if p.NoiseEnable {
+               p.frame = p.buf[S20BS-NonceSize : S20BS+MTU-NonceSize-poly1305.TagSize]
+       } else {
+               p.frame = p.buf[S20BS-NonceSize : S20BS+PktSizeSize+size]
+       }
        poly1305.Sum(p.tag, p.frame, p.keyAuth)
 
-       p.BytesOut += int64(len(p.frame))
+       p.BytesOut += int64(len(p.frame) + poly1305.TagSize)
        p.FramesOut++
+
+       if p.CPRCycle != time.Duration(0) {
+               p.willSentCycle = p.LastSent.Add(p.CPRCycle)
+               if p.willSentCycle.After(now) {
+                       time.Sleep(p.willSentCycle.Sub(now))
+                       now = p.willSentCycle
+               }
+       }
        p.LastSent = now
        if _, err := conn.WriteTo(append(p.frame, p.tag[:]...), p.Addr); err != nil {
                log.Println("Error sending UDP", err)
diff --git a/transport_test.go b/transport_test.go
new file mode 100644 (file)
index 0000000..0dc54f9
--- /dev/null
@@ -0,0 +1,69 @@
+package govpn
+
+import (
+       "net"
+       "testing"
+       "time"
+)
+
+var (
+       peer       *Peer
+       plaintext  []byte
+       ready      chan struct{}
+       dummy      = &Dummy{}
+       ciphertext []byte
+       addr       *net.UDPAddr
+       peerId     *PeerId
+       conf       *PeerConf
+)
+
+func init() {
+       MTU = 1500
+       addr, _ = net.ResolveUDPAddr("udp", "[::1]:1")
+       peerId = IDDecode("ffffffffffffffffffffffffffffffff")
+       conf = &PeerConf{
+               Id:          peerId,
+               Timeout:     time.Second * time.Duration(TimeoutDefault),
+               Noncediff:   1,
+               NoiseEnable: false,
+               CPR:         0,
+       }
+       peer = newPeer(addr, conf, 128, new([SSize]byte))
+       plaintext = make([]byte, 789)
+       ready = make(chan struct{})
+       go func() {
+               for {
+                       <-ready
+               }
+       }()
+}
+
+type Dummy struct{}
+
+func (d *Dummy) WriteTo(b []byte, addr net.Addr) (int, error) {
+       ciphertext = b
+       return len(b), nil
+}
+
+func (d *Dummy) Write(p []byte) (n int, err error) {
+       return len(p), nil
+}
+
+func BenchmarkEnc(b *testing.B) {
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               peer.NonceOur = 128
+               peer.EthProcess(plaintext, dummy, ready)
+       }
+}
+
+func BenchmarkDec(b *testing.B) {
+       peer.EthProcess(plaintext, dummy, ready)
+       peer = newPeer(addr, conf, 128, new([SSize]byte))
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               if !peer.UDPProcess(ciphertext, dummy, ready) {
+                       b.Fail()
+               }
+       }
+}
index 68469b66aa63047f9019937506dc9d2087ba3c7a..ab86a7fdb23dde0009948ccba3190ad063387927 100755 (executable)
@@ -9,8 +9,8 @@ getrand()
 [ -n "$1" ] || {
     cat <<EOF
 Example script for creating new user peer for GoVPN.
-It just creates directory with random peer ID, random key,
-saves username in it and creates dummy up.sh executable script.
+It just creates directory with random peer ID, dummy verifier,
+dummy up.sh executable script and saves username in it.
 
 Usage: $0 <username>
 EOF
@@ -21,8 +21,8 @@ username=$1
 peerid=$(getrand 16)
 umask 077
 mkdir -p peers/$peerid
-getrand 32 > peers/$peerid/key
+echo '0000000000000000000000000000000000000000000000000000000000000000' > peers/$peerid/verifier
 echo $username > peers/$peerid/name
 echo '#!/bin/sh' > peers/$peerid/up.sh
 chmod 700 peers/$peerid/up.sh
-echo $peerid
+echo Place verifier to peers/$peerid/verifier
diff --git a/utils/storekey.sh b/utils/storekey.sh
new file mode 100755 (executable)
index 0000000..299883d
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh -e
+
+[ -n "$1" ] || {
+    cat <<EOF
+Read passphrase from stdin and store it in file.
+
+Usage: $0 <keyfilename>
+EOF
+    exit 1
+}
+
+echo -n Enter passphrase:
+stty -echo
+read passphrase
+stty echo
+umask 077
+cat > $1 <<EOF
+$passphrase
+EOF
diff --git a/verifier.go b/verifier.go
new file mode 100644 (file)
index 0000000..f86cdcc
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package govpn
+
+import (
+       "bytes"
+       "crypto/sha512"
+       "io/ioutil"
+       "strings"
+
+       "github.com/agl/ed25519"
+       "golang.org/x/crypto/pbkdf2"
+)
+
+const (
+       PBKDF2Iters = 1 << 16
+)
+
+// Create verifier from supplied password for given PeerId.
+func NewVerifier(id *PeerId, password string) (*[ed25519.PublicKeySize]byte, *[ed25519.PrivateKeySize]byte) {
+       r := pbkdf2.Key(
+               []byte(password),
+               id[:],
+               PBKDF2Iters,
+               ed25519.PrivateKeySize,
+               sha512.New,
+       )
+       defer sliceZero(r)
+       src := bytes.NewBuffer(r)
+       pub, priv, err := ed25519.GenerateKey(src)
+       if err != nil {
+               panic("Unable to generate Ed25519 keypair" + err.Error())
+       }
+       return pub, priv
+}
+
+// Read string from the file, trimming newline. Panics if error occured.
+func StringFromFile(path string) string {
+       s, err := ioutil.ReadFile(path)
+       if err != nil {
+               panic("Can not read string from" + path)
+       }
+       return strings.TrimRight(string(s), "\n")
+}