NEWS | 9 +++++++++ README | 23 ++++++++++++++--------- cmd/govpn-client/main.go | 14 +++++++++++--- cmd/govpn-server/main.go | 31 ++++++++++++++++++++++++++----- common.go | 23 +++++++++++++++-------- doc/download.texi | 7 +++++-- doc/govpn.texi | 105 +++++++++++++++++++++++++++++++++++++---------------- doc/handshake.txt | 2 +- doc/makefile | 5 +++-- handshake.go | 105 +++++++++++++++++++++++++---------------------------- identify.go | 47 ++++++++++++++++++++++++++++++++++++++--------- makedist.sh => utils/makedist.sh | 0 makefile | 2 +- transport.go | 18 ++++++++++++++---- utils/newclient.sh | 26 ++++++++++++++++++++++++++ diff --git a/NEWS b/NEWS index 4500296c5f9358219e153fb49057ef65b2be6b26..8734b16d93f2e029c6e3b765e0b132eb4ac7ebda 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,12 @@ +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. diff --git a/README b/README index bdb348941c9ca5ab1d62b86b5cfe3f13ae6c00e8..d58111d23e3dbd03d86a0e587bfae02d4b885412 100644 --- a/README +++ b/README @@ -1,16 +1,21 @@ -GoVPN is simple secure free software virtual private network daemon. It -uses Diffie-Hellman Encrypted Key Exchange (DH-EKE) for mutual -zero-knowledge peers authentication and authenticated encrypted data -transport. +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 keys (PSK), perfect +forward secrecy (PFS). GNU/Linux and FreeBSD support. Home page: http://www.cypherpunks.ru/govpn/ +also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/ -Send bug reports, questions and patches to govpn-devel@lists.cypherpunks.ru -mailing list. Visit https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel -for subscription and archive access information. +Send bug reports, questions and patches to +govpn-devel@lists.cypherpunks.ru mailing list. Either visit +https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel for +subscription and archive access information, or send email with the +subject "subscribe" to govpn-devel-request@lists.cypherpunks.ru. -Development Git source code repository currently is located on: -https://github.com/stargrave/govpn +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. diff --git a/cmd/govpn-client/main.go b/cmd/govpn-client/main.go index f4a29eb82d2aea2a23d3fdb5eaad906cf41e677d..119a03d3ddbfe0152e8b877e5478563abd926e07 100644 --- a/cmd/govpn-client/main.go +++ b/cmd/govpn-client/main.go @@ -52,6 +52,7 @@ 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") @@ -86,13 +87,14 @@ termSignal := make(chan os.Signal, 1) signal.Notify(termSignal, os.Interrupt, os.Kill) - log.Println("Client version", govpn.Version) + log.Println(govpn.VersionGet()) log.Println("Starting handshake") handshake := govpn.HandshakeStart(conn, remote, id, key) MainCycle: for { if peer != nil && peer.Bytes > govpn.MaxBytesPerKey { + peer.Zero() peer = nil handshake = govpn.HandshakeStart(conn, remote, id, key) log.Println("Rehandshaking") @@ -119,19 +121,25 @@ continue } udpPktData = udpBuf[:udpPkt.Size] - if govpn.IsValidHandshakePkt(udpPktData) { + if peer == nil { if udpPkt.Addr.String() != remote.String() { udpReady <- struct{}{} log.Println("Unknown handshake message") continue } - if p := handshake.Client(conn, key, udpPktData); p != nil { + if govpn.IDsCache.Find(udpPktData) == nil { + log.Println("Invalid identity in handshake packet") + udpReady <- struct{}{} + continue + } + if p := handshake.Client(id, conn, key, udpPktData); p != nil { log.Println("Handshake completed") if firstUpCall { go govpn.ScriptCall(*upPath, *ifaceName) firstUpCall = false } peer = p + handshake.Zero() handshake = nil } udpReady <- struct{}{} diff --git a/cmd/govpn-server/main.go b/cmd/govpn-server/main.go index 4fa5c96f8baa184c350e4362178b196dc14f1009..3d952ea9e0d4e5a62cdb69fad9dba3c115124b74 100644 --- a/cmd/govpn-server/main.go +++ b/cmd/govpn-server/main.go @@ -114,9 +114,11 @@ var peerReady PeerReadyEvent var udpPkt *govpn.UDPPkt var udpPktData []byte var ethEvent EthEvent + var peerId *govpn.PeerId + var handshakeProcessForce bool ethSink := make(chan EthEvent) - log.Println("Server version", govpn.Version) + log.Println(govpn.VersionGet()) log.Println("Server started") MainCycle: @@ -129,6 +131,7 @@ now := time.Now() for addr, hs := range states { if hs.LastPing.Add(timeout).Before(now) { log.Println("Deleting handshake state", addr) + hs.Zero() delete(states, addr) } } @@ -143,6 +146,7 @@ "down.sh", ) go govpn.ScriptCall(downPath, state.tap.Name) state.terminate <- struct{}{} + state.peer.Zero() } } case peerReady = <-peerReadySink: @@ -152,6 +156,7 @@ continue } delete(peers, addr) state.terminate <- struct{}{} + state.peer.Zero() break } addr = peerReady.peer.Addr.String() @@ -160,6 +165,7 @@ if state == nil { continue } peers[addr] = state + states[addr].Zero() delete(states, addr) log.Println("Registered interface", peerReady.iface, "with peer", peer) go func(state *PeerState) { @@ -183,13 +189,21 @@ continue } udpPktData = udpBuf[:udpPkt.Size] addr = udpPkt.Addr.String() - if govpn.IsValidHandshakePkt(udpPktData) { + handshakeProcessForce = false + HandshakeProcess: + if _, exists = peers[addr]; handshakeProcessForce || !exists { + peerId = govpn.IDsCache.Find(udpPktData) + if peerId == nil { + log.Println("Unknown identity from", addr) + udpReady <- struct{}{} + continue + } state, exists = states[addr] if !exists { state = govpn.HandshakeNew(udpPkt.Addr) states[addr] = state } - peer = state.Server(conn, udpPktData) + peer = state.Server(peerId, conn, udpPktData) if peer != nil { log.Println("Peer handshake finished", peer) if _, exists = peers[addr]; exists { @@ -212,7 +226,9 @@ peerReadySink <- PeerReadyEvent{peer, ifaceName} }() } } - udpReady <- struct{}{} + if !handshakeProcessForce { + udpReady <- struct{}{} + } continue } peerState, exists = peers[addr] @@ -220,7 +236,12 @@ if !exists { udpReady <- struct{}{} continue } - peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady) + // If it fails during processing, then try to work with it + // as with handshake packet + if !peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady) { + handshakeProcessForce = true + goto HandshakeProcess + } } } } diff --git a/common.go b/common.go index b42673c98925f32be3b63948f7eaf30eccb186e9..7eb40600c9fc0a868af4de56b26727a83b228736 100644 --- a/common.go +++ b/common.go @@ -19,12 +19,12 @@ package govpn import ( - "bytes" "encoding/hex" "io/ioutil" "log" "os" "os/exec" + "runtime" ) var ( @@ -45,15 +45,11 @@ } if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { return nil, err } - cmd := exec.Command(path, ifaceName) - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - result := out.Bytes() + out, err := exec.Command(path, ifaceName).CombinedOutput() if err != nil { - log.Println("Script error", path, err, string(result)) + log.Println("Script error", path, err, string(out)) } - return result, err + return out, err } // Read authentication key from the file. @@ -74,3 +70,14 @@ key := new([KeySize]byte) copy(key[:], keyDecoded) return key } + +// Zero each byte +func sliceZero(data []byte) { + for i := 0; i < len(data); i++ { + data[i] = '\x00' + } +} + +func VersionGet() string { + return "GoVPN version " + Version + " built with " + runtime.Version() +} diff --git a/doc/download.texi b/doc/download.texi index e0ae00d035748093430cc91ed148a72436adb406..d646b894a3786926bb255bc54045a782b15f6339 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -2,14 +2,17 @@ You can obtain it's source code either by cloning development branches from Git repository: @code{git clone https://github.com/stargrave/govpn.git}, or by downloading prepared tarballs below. -@multitable {XXXXX} {XXXX KiB} {link sign} -@headitem Version @tab Size @tab Tarball +@multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx} +@headitem Version @tab Size @tab Tarball @tab SHA256 checksum @item 1.5 @tab 19 KiB @tab @url{download/govpn-1.5.tar.xz, link} @url{download/govpn-1.5.tar.xz.sig, sign} +@tab @code{715b07d4d1ea4396c3e37014ca65ec3768818423521f3c12e7200b6edca48c31} @item 2.0 @tab 31 KiB @tab @url{download/govpn-2.0.tar.xz, link} @url{download/govpn-2.0.tar.xz.sig, sign} +@tab @code{d43be1248d6a46ba8ca75be2fdab5e3d8b0660fb9df9b6d87cfa3973722b42be} @item 2.2 @tab 32 KiB @tab @url{download/govpn-2.2.tar.xz, link} @url{download/govpn-2.2.tar.xz.sig, sign} +@tab @code{5745278bce8b9a3bd7ec1636507bbce8c17ba1d79f1568e2f3681b7a90bbe6e1} @end multitable Sourceforge.net also provides mirror for the files above: diff --git a/doc/govpn.texi b/doc/govpn.texi index 3bdf72faddc3ccb254c9dfed48cad765bcec2600..3f782098a47e829b2d85546cb154a98dda9a8c6c 100644 --- a/doc/govpn.texi +++ b/doc/govpn.texi @@ -26,7 +26,8 @@ @menu * Overview:: * News:: -* Getting and building source code:: +* Installation:: +* Precautions:: * User manual:: * Developer manual:: * Reporting bugs:: @@ -69,6 +70,12 @@ 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. +The only platform specific requirement is TAP network interface support. +API to that kind of device is different, OS dependent and non portable. +So only a few operating systems is officially supported. Author has no +proprietary software to work with, so currently there is lack of either +popular Microsoft Windows or Apple OS X support. + @itemize @bullet @item Works with @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP} @@ -101,8 +108,8 @@ @unnumbered News @verbatiminclude ../NEWS -@node Getting and building source code -@unnumbered Getting and building source code +@node Installation +@unnumbered Installation GoVPN is written on Go programming language, But @url{https://www.gnu.org/software/make/, Make} program is recommended @@ -132,7 +139,7 @@ clone the repository or decompress tarball and set path like this: @example % mkdir -p govpn/src -% git clone https://github.com/stargrave/govpn govpn/src/govpn +% git clone https://github.com/stargrave/govpn.git govpn/src/govpn or % tar xfC govpn-1.5.tar.xz govpn/src && mv govpn/src/govpn-1.5 govpn/src/govpn % export GOPATH=$(pwd)/govpn:$GOPATH @@ -150,6 +157,26 @@ @end example @include pubkey.texi +@node Precautions +@unnumbered Precautions + +The very important precaution is the @strong{cryptographically good} +pseudo random number generator. GoVPN uses native operating system PRNG +as entropy source. You have no way to check it's quality in closed +source code operating systems, so it is recommended not to use them if +you really needs security. Moreover it is possible that those OS leaks +information about possible PRNG states. And at least Apple OS X and +Microsoft Windows are already known to have weak CSPRNGs. + +GoVPN could use it's own PRNG implementation like +@url{https://www.schneier.com/fortuna.html, Fortuna}, but it is +much easier to use the right OS, to use free software. + +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. + @node User manual @unnumbered User manual @@ -203,14 +230,25 @@ 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 +peers/9b40701bdaf522f2b291cb039490312/Alice +@end example + +@code{9b40701bdaf522f2b291cb039490312} is client's identification. +@code{Alice} is just an empty file that can help to search them like +this: @verb{|find peers -name Alice|}. @code{key} file inside peer's +directory contains authentication key. + GNU/Linux IPv4 client-server example: @example -server% mkdir -p peers/CLIENTID -server% umask 066 -server% echo MYLONG64HEXKEY > peers/CLIENTID/key server% echo "#!/bin/sh" > peers/CLIENTID/up.sh -server% echo "echo tap10" > peers/CLIENTID/up.sh +server% echo "echo tap10" >> peers/CLIENTID/up.sh server% chmod 500 peers/CLIENTID/up.sh server% ip addr add 192.168.0.1/24 dev wlan0 server% tunctl -t tap10 @@ -238,10 +276,6 @@ FreeBSD IPv6 client-server example: @example -server% mkdir -p peers/CLIENTID -server% umask 066 -server% echo MYLONG64HEXKEY > peers/CLIENTID/key -server% echo "#!/bin/sh" > server% cat > peers/CLIENTID/up.sh < Server|} [65 bytes] +@verb{|R + enc(PSK, R, CPubKey) + IDtag -> Server|} [48 bytes] @item server remembers clients address, decrypt @code{CPubKey}, generates @code{SPrivKey}/@code{SPubKey}, computes common shared key @code{K} @@ -364,19 +405,19 @@ (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 @item -@verb{|enc(PSK, SPubKey) + enc(K, RS + SS) + NULLs -> Client|} [88 bytes] +@verb{|enc(PSK, R+1, SPubKey) + enc(K, R, RS + SS) + IDtag -> Client|} [80 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}, @item -@verb{|enc(K, RS + RC + SC) + NULLs -> Server|} [64 bytes] +@verb{|enc(K, R+1, RS + RC + SC) + IDtag -> Server|} [56 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} @item -@verb{|ENC(K, RC) + NULLs -> Client|} [24 bytes] +@verb{|ENC(K, 0, RC) + IDtag -> Client|} [16 bytes] @item server switches to the new client @item @@ -384,23 +425,25 @@ client decrypts @code{RC} and compares with it's own generated one, computes final main encryption key @code{S} @end enumerate -Where PSK is 256bit pre-shared key, @code{NULLs} are 16 null-bytes. -@code{R*} are required for handshake randomization and two-way -authentication. K key is used only during handshake. @code{NULLs} are -required to differentiate common transport protocol messages from -handshake ones. DH public keys can be trivially derived from private -ones. +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. @node Reporting bugs @unnumbered Reporting bugs Please send all your bug requests, patches and related questions to @email{govpn-devel@@lists.cypherpunks.ru} mailing list. -Visit @url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel} -for information about subscription options and archived messages access. +Either visit @url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel} +for information about subscription options and archived messages access, or +send email with the subject @code{subscribe} to +@email{govpn-devel-request@@lists.cypherpunks.ru}. -Development Git source code repository currently is located on: -@url{https://github.com/stargrave/govpn}. +Official website is @url{http://www.cypherpunks.ru/govpn/}, also available +as @url{https://www.torproject.org/, Tor} hidden service: +@url{http://vabu56j2ep2rwv3b.onion/govpn/}. +Development Git source code repository currently is located here: +@url{https://github.com/stargrave/govpn.git}. @node Copying conditions @unnumbered Copying conditions diff --git a/doc/handshake.txt b/doc/handshake.txt index d946833876e7a2d052b1367100fe3a2655551eef..b36d9f3e8b5500d2d0a1654d57314ca61f7d6136 100644 --- a/doc/handshake.txt +++ b/doc/handshake.txt @@ -4,7 +4,7 @@ participant Server Client -> Client : R=rand(64bit) Client -> Client : CPrivKey=rand(256bit) -Client -> Server : R, enc(PSK, R, CPubKey), xtea(ID, R) +Client -> Server : R, enc(PSK, R, CPubKey) Server -> Server : SPrivKey=rand(256bit) Server -> Server : K=DH(SPrivKey, CPubKey) Server -> Server : RS=rand(64bit) diff --git a/doc/makefile b/doc/makefile index aa886a932dfcb37ce89bdfb89c8b0d3219b45f85..c55b888bae3ce11e256c3b5dbd69aadcb5850908 100644 --- a/doc/makefile +++ b/doc/makefile @@ -1,10 +1,11 @@ all: govpn.info govpn.html -govpn.info: govpn.texi handshake.utxt +govpn.info: *.texi handshake.utxt makeinfo govpn.texi handshake.utxt: handshake.txt plantuml -tutxt handshake.txt -govpn.html: govpn.texi handshake.utxt +govpn.html: *.texi handshake.utxt + rm -f govpn.html/*.html makeinfo --html -o govpn.html govpn.texi diff --git a/handshake.go b/handshake.go index c458f5d41e90d25b6961d9d043b2d1ece7dbde87..86e6083482a770e7e8b91d52ec5313598fac6352 100644 --- a/handshake.go +++ b/handshake.go @@ -28,7 +28,6 @@ "path" "time" "golang.org/x/crypto/curve25519" - "golang.org/x/crypto/poly1305" "golang.org/x/crypto/salsa20" "golang.org/x/crypto/salsa20/salsa" "golang.org/x/crypto/xtea" @@ -55,18 +54,29 @@ } return k } -// Check if it is valid handshake-related message. -// Minimal size and last 16 zero bytes. -func IsValidHandshakePkt(pkt []byte) bool { - if len(pkt) < 24 { - return false +// Zero handshake's memory state +func (h *Handshake) Zero() { + if h.rNonce != nil { + sliceZero(h.rNonce[:]) + } + if h.dhPriv != nil { + sliceZero(h.dhPriv[:]) } - for i := len(pkt) - poly1305.TagSize; i < len(pkt); i++ { - if pkt[i] != '\x00' { - return false - } + if h.key != nil { + sliceZero(h.key[:]) + } + if h.rServer != nil { + sliceZero(h.rServer[:]) + } + if h.rClient != nil { + sliceZero(h.rClient[:]) + } + if h.sServer != nil { + sliceZero(h.sServer[:]) + } + if h.sClient != nil { + sliceZero(h.sClient[:]) } - return true } func (h *Handshake) rNonceNext() []byte { @@ -100,6 +110,17 @@ } return &state } +// Generate ID tag from client identification and data. +func idTag(id *PeerId, data []byte) []byte { + ciph, err := xtea.NewCipher(id[:]) + if err != nil { + panic(err) + } + enc := make([]byte, xtea.BlockSize) + ciph.Encrypt(enc, data[:xtea.BlockSize]) + 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, @@ -118,19 +139,8 @@ panic("Can not read random for handshake nonce") } enc := make([]byte, 32) salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], key) - - ciph, err := xtea.NewCipher(id[:]) - if err != nil { - panic(err) - } - rEnc := make([]byte, xtea.BlockSize) - ciph.Encrypt(rEnc, state.rNonce[:]) - data := append(state.rNonce[:], enc...) - data = append(data, rEnc...) - data = append(data, '\x00') - data = append(data, make([]byte, poly1305.TagSize)...) - + data = append(data, idTag(id, state.rNonce[:])...) if _, err := conn.WriteTo(data, addr); err != nil { panic(err) } @@ -139,24 +149,14 @@ } // Process handshake message on the server side. // This function is intended to be called on server's side. -// Our outgoing conn connection and received data are required. +// Client identity, 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(conn *net.UDPConn, data []byte) *Peer { - switch len(data) { - case 65: // R + ENC(PSK, dh_client_pub) + xtea(ID, R) + NULL + NULLs - if h.rNonce != nil { - log.Println("Invalid handshake stage from", h.addr) - return nil - } - - // Try to determine client's ID - id := IDsCache.Find(data[:8], data[8+32:8+32+8]) - if id == nil { - log.Println("Unknown identity from", h.addr) - return nil - } +func (h *Handshake) Server(id *PeerId, conn *net.UDPConn, data []byte) *Peer { + // R + ENC(PSK, dh_client_pub) + IDtag + if len(data) == 48 && h.rNonce == nil { key := KeyRead(path.Join(PeersPath, id.String(), "key")) h.Id = *id @@ -191,17 +191,13 @@ salsa20.XORKeyStream(encRs, append(h.rServer[:], h.sServer[:]...), h.rNonce[:], h.key) // Send that to client if _, err := conn.WriteTo( - append(encPub, - append(encRs, make([]byte, poly1305.TagSize)...)...), h.addr); err != nil { + append(encPub, append(encRs, idTag(id, encPub)...)...), h.addr); err != nil { panic(err) } h.LastPing = time.Now() - case 64: // ENC(K, RS + RC + SC) + NULLs - if (h.rNonce == nil) || (h.rClient != nil) { - log.Println("Invalid handshake stage from", h.addr) - return nil - } - + } else + // ENC(K, RS + RC + SC) + IDtag + if len(data) == 56 && h.rClient == nil { // Decrypted Rs compare rServer decRs := make([]byte, 8+8+32) salsa20.XORKeyStream(decRs, data[:8+8+32], h.rNonceNext(), h.key) @@ -213,7 +209,7 @@ // 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, make([]byte, poly1305.TagSize)...), h.addr); err != nil { + if _, err := conn.WriteTo(append(enc, idTag(id, enc)...), h.addr); err != nil { panic(err) } @@ -221,7 +217,7 @@ // Switch peer peer := newPeer(h.addr, h.Id, 0, keyFromSecrets(h.sServer[:], decRs[8+8:])) h.LastPing = time.Now() return peer - default: + } else { log.Println("Invalid handshake message from", h.addr) } return nil @@ -229,15 +225,14 @@ } // Process handshake message on the client side. // This function is intended to be called on client's side. -// Our outgoing conn connection, authentication key and received data -// are required. Client does not work with identities, as he is the -// only one, so key is a requirement. +// Our outgoing conn connection, authentication +// key 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) Client(conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer { +func (h *Handshake) Client(id *PeerId, conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer { switch len(data) { - case 88: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + NULLs + case 80: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + IDtag if h.key != nil { log.Println("Invalid handshake stage from", h.addr) return nil @@ -271,11 +266,11 @@ append(h.rServer[:], append(h.rClient[:], h.sClient[:]...)...), h.rNonceNext(), h.key) // Send that to server - if _, err := conn.WriteTo(append(encRs, make([]byte, poly1305.TagSize)...), h.addr); err != nil { + if _, err := conn.WriteTo(append(encRs, idTag(id, encRs)...), h.addr); err != nil { panic(err) } h.LastPing = time.Now() - case 24: // ENC(K, RC) + NULLs + case 16: // ENC(K, RC) + IDtag if h.key == nil { log.Println("Invalid handshake stage from", h.addr) return nil diff --git a/identify.go b/identify.go index 8547e6081ef659c022c1f7f113ef222148491915..e4baea14c5f89b45bc0b8dcaeeabaa4a63ac8c07 100644 --- a/identify.go +++ b/identify.go @@ -23,12 +23,15 @@ "crypto/subtle" "encoding/hex" "log" "os" + "sync" + "time" "golang.org/x/crypto/xtea" ) const ( - IDSize = 128 / 8 + IDSize = 128 / 8 + RefreshRate = 60 * time.Second ) type PeerId [IDSize]byte @@ -40,15 +43,32 @@ type cipherCache map[PeerId]*xtea.Cipher var ( - PeersPath string - IDsCache cipherCache + PeersPath string + IDsCache cipherCache + cipherCacheLock sync.RWMutex ) // Initialize (pre-cache) available peers info. func PeersInit(path string) { PeersPath = path IDsCache = make(map[PeerId]*xtea.Cipher) - IDsCache.refresh() + go func() { + for { + IDsCache.refresh() + time.Sleep(RefreshRate) + } + }() +} + +// Initialize dummy cache for client-side usage. It will consist only +// of single key. +func PeersInitDummy(id *PeerId) { + IDsCache = make(map[PeerId]*xtea.Cipher) + cipher, err := xtea.NewCipher(id[:]) + if err != nil { + panic(err) + } + IDsCache[*id] = cipher } // Refresh IDsCache: remove disappeared keys, add missing ones with @@ -70,6 +90,8 @@ continue } available[*id] = true } + + cipherCacheLock.Lock() // Cleanup deleted ones from cache for k, _ := range cc { if _, exists := available[k]; !exists { @@ -88,20 +110,27 @@ } cc[peerId] = cipher } } + cipherCacheLock.Unlock() } // Try to find peer's identity (that equals to an encryption key) -// by providing cipher and plain texts. -func (cc cipherCache) Find(plaintext, ciphertext []byte) *PeerId { - cc.refresh() +// by taking first blocksize sized bytes from data at the beginning +// as plaintext and last bytes as cyphertext. +func (cc cipherCache) Find(data []byte) *PeerId { + if len(data) < xtea.BlockSize*2 { + return nil + } buf := make([]byte, xtea.BlockSize) + cipherCacheLock.RLock() for pid, cipher := range cc { - cipher.Decrypt(buf, ciphertext) - if subtle.ConstantTimeCompare(buf, plaintext) == 1 { + cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:]) + if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 { ppid := PeerId(pid) + cipherCacheLock.RUnlock() return &ppid } } + cipherCacheLock.RUnlock() return nil } diff --git a/makedist.sh b/utils/makedist.sh rename from makedist.sh rename to utils/makedist.sh diff --git a/makefile b/makefile index 59f148ee059bce4cdc7fa51580febd630f065e7f..d0cab0d8dbd9e12741c375d283bdefe7ec1fed56 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ .PHONY: govpn-client govpn-server -VERSION=2.2 +VERSION=2.3 LDFLAGS=-X govpn.Version $(VERSION) all: govpn-client govpn-server diff --git a/transport.go b/transport.go index d72fcc15ea140c74584514dd380fbc3fe6dea607..08144de4675aed598d6f183d772a8861cfb05248 100644 --- a/transport.go +++ b/transport.go @@ -37,7 +37,7 @@ // S20BS is Salsa20's internal blocksize in bytes S20BS = 64 HeartbeatSize = 12 // Maximal amount of bytes transfered with single key (4 GiB) - MaxBytesPerKey = 4294967296 + MaxBytesPerKey int64 = 1 << 32 ) type UDPPkt struct { @@ -58,13 +58,23 @@ buf []byte tag *[poly1305.TagSize]byte keyAuth *[KeySize]byte nonceRecv uint64 - Bytes int + Bytes int64 frame []byte nonce []byte } func (p *Peer) String() string { return p.Id.String() + ":" + p.Addr.String() +} + +// Zero peer's memory state +func (p *Peer) Zero() { + sliceZero(p.Key[:]) + sliceZero(p.tag[:]) + sliceZero(p.keyAuth[:]) + sliceZero(p.buf) + sliceZero(p.frame) + sliceZero(p.nonce) } var ( @@ -232,7 +242,7 @@ ready <- struct{}{} p.LastPing = time.Now() p.NonceRecv = p.nonceRecv p.frame = p.buf[S20BS : S20BS+size-NonceSize-poly1305.TagSize] - p.Bytes += len(p.frame) + p.Bytes += int64(len(p.frame)) if subtle.ConstantTimeCompare(p.frame[:HeartbeatSize], HeartbeatMark) == 1 { return true } @@ -272,7 +282,7 @@ copy(p.keyAuth[:], p.buf[:KeySize]) p.frame = p.buf[S20BS-NonceSize : S20BS+size] poly1305.Sum(p.tag, p.frame, p.keyAuth) - p.Bytes += len(p.frame) + p.Bytes += int64(len(p.frame)) 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/utils/newclient.sh b/utils/newclient.sh new file mode 100755 index 0000000000000000000000000000000000000000..620aac71538a6240294a4c7adc24f2577c5994c6 --- /dev/null +++ b/utils/newclient.sh @@ -0,0 +1,26 @@ +#!/bin/sh -e + +getrand() +{ + local size=$1 + dd if=/dev/random bs=$size count=1 2>/dev/null | hexdump -ve '"%02x"' +} + +[ -n "$1" ] || { + cat < +EOF + exit 1 +} + +username=$1 +peerid=$(getrand 16) +umask 077 +mkdir -p peers/$peerid +getrand 32 > peers/$peerid/key +touch peers/$peerid/$1 +echo peers/$peerid/$1