From cae1bf7a68f5be04942cdb96f91e5eca44232f5c Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sun, 3 May 2015 14:10:02 +0300 Subject: [PATCH] Use A-EKE instead of EKE. Doc refactoring. Preparing for 3.0 release Signed-off-by: Sergey Matveev --- INSTALL | 6 +- NEWS | 52 +-------- README | 18 ++-- THANKS | 6 +- TODO | 2 - VERSION | 2 +- cmd/govpn-client/main.go | 24 +++-- cmd/govpn-server/main.go | 11 +- cmd/govpn-verifier/main.go | 53 +++++++++ common.go | 21 ---- doc/client.texi | 7 +- doc/{bugs.texi => contacts.texi} | 6 +- doc/developer.texi | 12 ++- doc/download.texi | 6 +- doc/example.texi | 7 +- doc/govpn.texi | 30 +++--- doc/handshake.texi | 100 ++++++++++++----- doc/handshake.txt | 17 +-- doc/identity.texi | 6 ++ doc/installation.texi | 20 +++- doc/keywords.texi | 12 +++ doc/news.texi | 106 ++++++++++++++++++ doc/noise.texi | 4 +- doc/overview.texi | 24 +++-- doc/pake.texi | 30 ++++++ doc/precautions.texi | 5 + doc/server.texi | 17 +-- doc/thanks.texi | 19 ++++ doc/user.texi | 23 ++-- doc/verifier.texi | 29 +++++ doc/verifierstruct.texi | 16 +++ handshake.go | 178 ++++++++++++++++++------------- identify.go | 29 ++++- makefile | 9 +- tap.go | 2 + transport.go | 24 ++--- transport_test.go | 13 ++- utils/newclient.sh | 8 +- utils/storekey.sh | 19 ++++ verifier.go | 60 +++++++++++ 40 files changed, 731 insertions(+), 302 deletions(-) mode change 100644 => 120000 INSTALL mode change 100644 => 120000 NEWS mode change 100644 => 120000 THANKS create mode 100644 cmd/govpn-verifier/main.go rename doc/{bugs.texi => contacts.texi} (88%) create mode 100644 doc/identity.texi create mode 100644 doc/keywords.texi create mode 100644 doc/news.texi create mode 100644 doc/pake.texi create mode 100644 doc/thanks.texi create mode 100644 doc/verifier.texi create mode 100644 doc/verifierstruct.texi create mode 100755 utils/storekey.sh create mode 100644 verifier.go diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 0f2864b..0000000 --- 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 index 0000000..09eda89 --- /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 index 4bf946c..0000000 --- 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 index 0000000..8be52e4 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +doc/news.texi \ No newline at end of file diff --git a/README b/README index e044713..7fdd2e8 100644 --- a/README +++ b/README @@ -1,13 +1,13 @@ GoVPN is simple secure free software virtual private network daemon, -aimed to be reviewable, secure, DPI-resistant. It is written on Go. +aimed to be reviewable, secure, DPI-resistant, written on Go. -It uses fast Diffie-Hellman Encrypted Key Exchange (DH-EKE) handshake -for mutual zero-knowledge peers authentication. Encrypted authenticated -data transport. Hiding payload messages length with noise, hiding -messages appearance fact with constant packet rate (CPR) traffic. -Perfect forward secrecy (PFS), replay attack protection, heartbeating, -rehandshaking, pre-shared authentication keys, IPv4/IPv6-compatible, -JSON real-time statistics. GNU/Linux and FreeBSD support. +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/ @@ -22,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 index c317254..0000000 --- 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 index 0000000..7862103 --- /dev/null +++ b/THANKS @@ -0,0 +1 @@ +doc/thanks.texi \ No newline at end of file diff --git a/TODO b/TODO index e1d3d3f..a2043ca 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,2 @@ * Increase performance -* Probably implement alternative Secure Remote Password protocol to allow - human memorized passphrases to be used * Randomize ports usage diff --git a/VERSION b/VERSION index 6b4950e..9f55b2c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.4 +3.0 diff --git a/cmd/govpn-client/main.go b/cmd/govpn-client/main.go index 30ba8e9..f49edf8 100644 --- a/cmd/govpn-client/main.go +++ b/cmd/govpn-client/main.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Simple secure free software virtual private network daemon. +// Simple secure free software virtual private network daemon client. package main import ( @@ -34,7 +34,7 @@ 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") @@ -54,17 +54,21 @@ func main() { govpn.MTU = *mtu id := govpn.IDDecode(*IDRaw) - govpn.PeersInitDummy(id, govpn.PeerConf{ + 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, - }) - key := govpn.KeyRead(*keyPath) - if id == nil { - panic("ID is not specified") + DSAPub: pub, + DSAPriv: priv, } + govpn.PeersInitDummy(id, conf) bind, err := net.ResolveUDPAddr("udp", "0.0.0.0:0") if err != nil { @@ -112,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 { @@ -155,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) diff --git a/cmd/govpn-server/main.go b/cmd/govpn-server/main.go index 6697f2b..8aa26ec 100644 --- a/cmd/govpn-server/main.go +++ b/cmd/govpn-server/main.go @@ -113,6 +113,7 @@ func main() { var udpPktData []byte var ethEvent EthEvent var peerId *govpn.PeerId + var peerConf *govpn.PeerConf var handshakeProcessForce bool ethSink := make(chan EthEvent) @@ -208,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 index 0000000..e7cc0e9 --- /dev/null +++ b/cmd/govpn-verifier/main.go @@ -0,0 +1,53 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +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 . +*/ + +// 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) + } +} diff --git a/common.go b/common.go index b87133a..fbafd56 100644 --- a/common.go +++ b/common.go @@ -19,8 +19,6 @@ along with this program. If not, see . package govpn import ( - "encoding/hex" - "io/ioutil" "log" "os" "os/exec" @@ -54,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 index 3cb8927..dfee4f1 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -1,7 +1,7 @@ @node Client part @section Client part -Except for common @code{-mtu}, @code{-stats}, options client has the +Except for common @code{-mtu}, @code{-stats} options client has the following ones: @table @code @@ -13,10 +13,11 @@ Address (@code{host:port} format) of remote server we need to connect to. TAP interface name. @item -id -Our client's identification (hexadecimal string). +Our client's @ref{Identity} (hexadecimal string). @item -key -Path to the file with the PSK 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. diff --git a/doc/bugs.texi b/doc/contacts.texi similarity index 88% rename from doc/bugs.texi rename to doc/contacts.texi index b81e6b5..9acdbf0 100644 --- a/doc/bugs.texi +++ b/doc/contacts.texi @@ -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/developer.texi b/doc/developer.texi index b218c35..3fd23ed 100644 --- a/doc/developer.texi +++ b/doc/developer.texi @@ -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 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 diff --git a/doc/download.texi b/doc/download.texi index 157d471..9c6193b 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -25,9 +25,11 @@ You can obtain releases source code prepared tarballs from the links below: @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 index 384d16a..c203d43 100644 --- a/doc/example.texi +++ b/doc/example.texi @@ -17,6 +17,11 @@ is 1432. 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 @@ -31,7 +36,7 @@ server% GOMAXPROC=4 govpn-server -bind 192.168.0.1:1194 -mtu 1472 @example client% umask 066 -client% echo MYLONG64HEXKEY > key.txt +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 diff --git a/doc/govpn.texi b/doc/govpn.texi index 44aa54d..fe842b3 100644 --- a/doc/govpn.texi +++ b/doc/govpn.texi @@ -21,9 +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, aimed to be reviewable, secure, DPI-resistant, -written entirely on Go. +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:: @@ -32,26 +39,18 @@ written entirely on Go. * 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 @@ -59,4 +58,5 @@ written entirely on Go. @insertcopying @verbatiminclude fdl.txt +@include thanks.texi @bye diff --git a/doc/handshake.texi b/doc/handshake.texi index 533fef9..6f1e37e 100644 --- a/doc/handshake.texi +++ b/doc/handshake.texi @@ -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. diff --git a/doc/handshake.txt b/doc/handshake.txt index b36d9f3..8c3eb38 100644 --- a/doc/handshake.txt +++ b/doc/handshake.txt @@ -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 index 0000000..3288d2e --- /dev/null +++ b/doc/identity.texi @@ -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. diff --git a/doc/installation.texi b/doc/installation.texi index 5aa9543..13d2159 100644 --- a/doc/installation.texi +++ b/doc/installation.texi @@ -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 index 0000000..39a20e3 --- /dev/null +++ b/doc/keywords.texi @@ -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/news.texi b/doc/news.texi new file mode 100644 index 0000000..75ec725 --- /dev/null +++ b/doc/news.texi @@ -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 index d376310..06ee1de 100644 --- a/doc/noise.texi +++ b/doc/noise.texi @@ -9,5 +9,5 @@ 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 increase your traffic! It is -turned off by default. +Pay attention that this can dramatically @strong{increase} your traffic! +It is turned off by default. diff --git a/doc/overview.texi b/doc/overview.texi index 1886f03..f6b81ac 100644 --- a/doc/overview.texi +++ b/doc/overview.texi @@ -11,12 +11,17 @@ goals for that daemon. Most modern widespread protocols and their implementations in software are too complex to be reviewed, analyzed and modified. -State off art cryptography technologies include: +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/Encrypted_key_exchange, Diffie-Hellman Encrypted Key Exchange} -(DH-EKE) powered by @url{http://cr.yp.to/ecdh.html, Curve25519}. +@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 @@ -24,8 +29,10 @@ 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. -Rehandshaking ensures session keys rotation. MAC authentication with -one-time keys protects against +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}. Server can work with several clients simultaneously. Each client is @@ -36,7 +43,7 @@ are applied per-peer separately. 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. +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. @@ -57,6 +64,9 @@ network interfaces on top of UDP entirely @item IPv6 compatible @item Encrypted and authenticated payload transport @item Relatively fast handshake +@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 @@ -65,7 +75,7 @@ network interfaces on top of UDP entirely @item Several simultaneous clients support @item Per-client configuration options @item Hiding of payload packets length with noise -@item Hiding of payload packets appearance with constant packet rate traffic +@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 index 0000000..535165e --- /dev/null +++ b/doc/pake.texi @@ -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. diff --git a/doc/precautions.texi b/doc/precautions.texi index bae37b3..93b179e 100644 --- a/doc/precautions.texi +++ b/doc/precautions.texi @@ -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 index 4fd074c..c5f2eb2 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -1,7 +1,7 @@ @node Server part @section Server part -Except for common @code{-mtu}, @code{-stats}, options server has the +Except for common @code{-mtu}, @code{-stats} options server has the following ones: @table @code @@ -17,9 +17,10 @@ files: @table @code -@item key -@strong{Required}. Contains corresponding authentication PSK key in -hexadecimal notation. +@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 @@ -65,11 +66,11 @@ creation: @example % ./utils/newclient.sh Alice -9b40701bdaf522f2b291cb039490312 +Place verifier to peers/9b40701bdaf522f2b291cb039490312/verifier @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. +@code{peers/9b40701bdaf522f2b291cb039490312/verifier} contains dummy +verifier and @code{peers/9b40701bdaf522f2b291cb039490312/up.sh} contains +currently dummy empty up-script. diff --git a/doc/thanks.texi b/doc/thanks.texi new file mode 100644 index 0000000..6922370 --- /dev/null +++ b/doc/thanks.texi @@ -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/user.texi b/doc/user.texi index a77fd8a..3080ce1 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -1,41 +1,38 @@ @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. GoVPN is just a tunnelling of Ethernet frames, nothing less, nothing more. All you -IP-level network management is not touched by VPN at all. You can +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:: +* MTU:: Maximum Transmission Unit * Stats:: * Noise:: -* CPR:: +* CPR:: Constant Packet Rate +* Verifier:: * Client part:: * Server part:: * Example usage:: @end menu +@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 index 0000000..6e97b7e --- /dev/null +++ b/doc/verifier.texi @@ -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 index 0000000..3b969dc --- /dev/null +++ b/doc/verifierstruct.texi @@ -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. diff --git a/handshake.go b/handshake.go index 86e6083..0fee82c 100644 --- a/handshake.go +++ b/handshake.go @@ -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: diff --git a/identify.go b/identify.go index d6a324f..4363c58 100644 --- a/identify.go +++ b/identify.go @@ -30,6 +30,7 @@ import ( "sync" "time" + "github.com/agl/ed25519" "golang.org/x/crypto/xtea" ) @@ -60,6 +61,10 @@ type PeerConf struct { 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 @@ -84,14 +89,14 @@ func PeersInit(path string) { } // Initialize dummy cache for client-side usage. -func PeersInitDummy(id *PeerId, conf PeerConf) { +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 + dummyConf = conf } // Refresh IDsCache: remove disappeared keys, add missing ones with @@ -170,13 +175,31 @@ func readIntFromFile(path string) (int, error) { } // Get peer related configuration. -func (id *PeerId) ConfGet() *PeerConf { +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 diff --git a/makefile b/makefile index 77a12ad..e5db1da 100644 --- a/makefile +++ b/makefile @@ -1,14 +1,16 @@ -.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 @@ -16,5 +18,8 @@ govpn-client: dependencies 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 8a19df3..a807955 100644 --- a/tap.go +++ b/tap.go @@ -37,6 +37,8 @@ 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 } diff --git a/transport.go b/transport.go index f13c503..ddcfbf9 100644 --- a/transport.go +++ b/transport.go @@ -32,7 +32,6 @@ import ( const ( NonceSize = 8 - KeySize = 32 // S20BS is Salsa20's internal blocksize in bytes S20BS = 64 // Maximal amount of bytes transfered with single key (4 GiB) @@ -50,7 +49,7 @@ type UDPPkt struct { type Peer struct { Addr *net.UDPAddr - Id PeerId + Id *PeerId // Traffic behaviour NoiseEnable bool @@ -58,7 +57,7 @@ type Peer struct { CPRCycle time.Duration `json:"-"` // Cryptography related - Key *[KeySize]byte `json:"-"` + Key *[SSize]byte `json:"-"` Noncediff int NonceOur uint64 `json:"-"` NonceRecv uint64 `json:"-"` @@ -74,7 +73,7 @@ type Peer struct { // This variables are initialized only once to relief GC buf []byte tag *[poly1305.TagSize]byte - keyAuth *[KeySize]byte + keyAuth *[32]byte nonceRecv uint64 frame []byte nonce []byte @@ -97,7 +96,7 @@ 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[:]) @@ -209,11 +208,11 @@ func ConnListen(conn *net.UDPConn) (chan UDPPkt, []byte, chan 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, ) @@ -231,9 +230,8 @@ func cprCycleCalculate(rate int) time.Duration { return time.Second / time.Duration(rate*(1<<10)/MTU) } -func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer { +func newPeer(addr *net.UDPAddr, conf *PeerConf, nonce int, key *[SSize]byte) *Peer { now := time.Now() - conf := id.ConfGet() timeout := conf.Timeout cprCycle := cprCycleCalculate(conf.CPR) noiseEnable := conf.NoiseEnable @@ -248,7 +246,7 @@ func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer Timeout: timeout, Established: now, LastPing: now, - Id: id, + Id: conf.Id, NoiseEnable: noiseEnable, CPR: conf.CPR, CPRCycle: cprCycle, @@ -259,7 +257,7 @@ func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer 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 @@ -281,7 +279,7 @@ func (p *Peer) UDPProcess(udpPkt []byte, tap io.Writer, ready chan struct{}) boo 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++ @@ -343,7 +341,7 @@ func (p *Peer) EthProcess(ethPkt []byte, conn WriteToer, ready chan struct{}) { salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key) copy(p.buf[S20BS-NonceSize:S20BS], p.nonce) - copy(p.keyAuth[:], p.buf[:KeySize]) + copy(p.keyAuth[:], p.buf[:SSize]) if p.NoiseEnable { p.frame = p.buf[S20BS-NonceSize : S20BS+MTU-NonceSize-poly1305.TagSize] } else { diff --git a/transport_test.go b/transport_test.go index 2a56750..0dc54f9 100644 --- a/transport_test.go +++ b/transport_test.go @@ -3,6 +3,7 @@ package govpn import ( "net" "testing" + "time" ) var ( @@ -13,13 +14,21 @@ var ( ciphertext []byte addr *net.UDPAddr peerId *PeerId + conf *PeerConf ) func init() { MTU = 1500 addr, _ = net.ResolveUDPAddr("udp", "[::1]:1") peerId = IDDecode("ffffffffffffffffffffffffffffffff") - peer = newPeer(addr, *peerId, 128, new([KeySize]byte)) + 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() { @@ -50,7 +59,7 @@ func BenchmarkEnc(b *testing.B) { func BenchmarkDec(b *testing.B) { peer.EthProcess(plaintext, dummy, ready) - peer = newPeer(addr, *peerId, 128, new([KeySize]byte)) + peer = newPeer(addr, conf, 128, new([SSize]byte)) b.ResetTimer() for i := 0; i < b.N; i++ { if !peer.UDPProcess(ciphertext, dummy, ready) { diff --git a/utils/newclient.sh b/utils/newclient.sh index 68469b6..ab86a7f 100755 --- a/utils/newclient.sh +++ b/utils/newclient.sh @@ -9,8 +9,8 @@ getrand() [ -n "$1" ] || { cat < 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 index 0000000..299883d --- /dev/null +++ b/utils/storekey.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e + +[ -n "$1" ] || { + cat < +EOF + exit 1 +} + +echo -n Enter passphrase: +stty -echo +read passphrase +stty echo +umask 077 +cat > $1 < + +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 . +*/ + +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") +} -- 2.44.0