From 71d699f1c4aa8e2a8537e2d63222faec0a6ea6ca Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 5 Mar 2015 18:52:02 +0300 Subject: [PATCH] Texinfo documentation, client ID, simultaneous clients Signed-off-by: Sergey Matveev --- INSTALL | 5 + NEWS | 27 +++ README | 278 ++------------------------ THANKS | 5 + TODO | 6 + cmd/govpn-client/main.go | 148 ++++++++++++++ cmd/govpn-server/main.go | 226 +++++++++++++++++++++ common.go | 72 +++++++ doc/.gitignore | 3 + doc/download.texi | 11 ++ doc/govpn.texi | 410 +++++++++++++++++++++++++++++++++++++++ doc/handshake.txt | 21 ++ doc/makefile | 10 + govpn.go | 344 +------------------------------- handshake.go | 155 ++++++++------- identify.go | 123 ++++++++++++ makedist.sh | 18 ++ makefile | 17 ++ tap.go | 68 +++++++ tap_freebsd.go | 13 +- tap_linux.go | 14 +- transport.go | 274 ++++++++++++++++++++++++++ 22 files changed, 1558 insertions(+), 690 deletions(-) create mode 100644 INSTALL create mode 100644 NEWS create mode 100644 THANKS create mode 100644 TODO create mode 100644 cmd/govpn-client/main.go create mode 100644 cmd/govpn-server/main.go create mode 100644 common.go create mode 100644 doc/.gitignore create mode 100644 doc/download.texi create mode 100644 doc/govpn.texi create mode 100644 doc/handshake.txt create mode 100644 doc/makefile create mode 100644 identify.go create mode 100755 makedist.sh create mode 100644 makefile create mode 100644 tap.go create mode 100644 transport.go diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..c1f1dc3 --- /dev/null +++ b/INSTALL @@ -0,0 +1,5 @@ +GoVPN is a program written on Go programming language. If you have set +up $GOPATH environment, then simple "make all" should build govpn-client +and govpn-server executable binaries. + +For usage documentation see either doc/govpn.info or doc/govpn.texi. diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..bf44317 --- /dev/null +++ b/NEWS @@ -0,0 +1,27 @@ +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/README b/README index dfef9cb..bdb3489 100644 --- a/README +++ b/README @@ -1,271 +1,17 @@ - GoVPN - ===== -SYNOPSIS +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 virtual private network daemon. -It uses DH-EKE for mutual zero-knowledge authentication and -authenticated encrypted transport. It runs under GNU/Linux and FreeBSD. +Home page: http://www.cypherpunks.ru/govpn/ -FEATURES +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. -* GNU/Linux and FreeBSD support -* IPv6 compatible -* Encrypted and authenticated transport -* Relatively fast handshake -* Replay attack protection -* Perfect forward secrecy (if long-term pre-shared keys are compromised, - no captured traffic can be decrypted anyway) -* Mutual two-side authentication (noone will send real network interface - data unless the other side is authenticated) -* Zero knowledge authentication (pre-shared key is not transmitted in - any form between the peers, not even it's hash value) -* Built-in rehandshake and heartbeat features +Development Git source code repository currently is located on: +https://github.com/stargrave/govpn -DESCRIPTION +GoVPN is free software: see the file COPYING for copying conditions. -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). -Because of stateless UDP nature, after some timeout of inactivity peers -forget about each other and have to retry handshake process again, -therefore background heartbeat process will be ran. - -Handshake is used to mutually authenticate peers, exchange common secret -per-session encryption key and checks 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 network interface. - -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 from captured traffic, even if pre-shared key is -compromised. Rehandshake is performed by client every 4 GiB of -transfered data. - -Also you can provide up and down scripts that will be executed after -either connection is initiated (up-script in background), or is went -down. The first argument for them is an interface name. - -CONSOLE OUTPUT LEGEND - -B -- bad or timeouted UDP packet (maybe network is inactive) -T -- bad tag on packet (MiTM, unordered packet) -R -- invalid sequence number (MiTM, unordered packet) -[HS?] -- unknown handshake message -w -- successful write to remote peer -r -- successful read from remote peer -[HS1], [HS2], [HS3], [HS4] -- handshake packet stage -[rS?] -- invalid server's random authentication number received (MiTM, bad PSK) -[rC?] -- invalid client's random authentication number received (MiTM, bad PSK) -[S?] -- invalid handshake stage is trying to perform (MiTM, duplicate packet) -[OK] -- handshake's stage passed - -EXAMPLE USAGE - -Let's assume that there is some insecure link between your computer and -WiFi-reachable gateway. You have got preconfigured 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). - - common% umask 066 - common% echo MYLONG64HEXKEY > key.txt - -GNU/Linux IPv4 client-server example: - - 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% govpn -key key.txt -iface tap10 -bind 192.168.0.1:1194 - - 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% while :; do govpn -key key.txt -iface tap10 -remote 192.168.0.1:1194; done - -FreeBSD IPv6 client-server example: - - server% ifconfig em0 inet6 fe80::1/64 - server% ifconfig tap10 create - server% ifconfig tap10 inet6 fc00::1/96 mtu 1462 up - server% govpn -key key.txt -face tap10 -bind fe80::1%em0 - - 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% while :; do govpn -key key.txt -iface tap10 -remote [fe80::1%me0]:1194; done - -Example up-script: - - client% cat > up.sh < - │ │ - │ │────┐ - │ │ │ SPrivKey=rand(256bit) - │ │<───┘ - │ │ - │ │────┐ - │ │ │ K=DH(SPrivKey, CPubKey) - │ │<───┘ - │ │ - │ │────┐ - │ │ │ RS=rand(64bit); SS=rand(256bit) - │ │<───┘ - │ │ - │ enc(PSK, R+1, SPubKey); enc(K, R, RS+SS)│ - │ <──────────────────────────────────────── - │ │ - │────┐ │ - │ │ K=DH(CPrivKey, SPubKey) │ - │<───┘ │ - │ │ - │────┐ │ - │ │ RC=rand(64bit); SC=rand(256bit) │ - │<───┘ │ - │ │ - │ enc(K, R+1, RS+RC+SC) │ - │ ────────────────────────────────────────> - │ │ - │ │────┐ - │ │ │ compare(RS) - │ │<───┘ - │ │ - │ │────┐ - │ │ │ MasterKey=SS XOR SC - │ │<───┘ - │ │ - │ enc(K, 0x00, RC) │ - │ <──────────────────────────────────────── - │ │ - │────┐ │ - │ │ compare(RC) │ - │<───┘ │ - │ │ - │────┐ │ - │ │ MasterKey=SS XOR SC │ - │<───┘ │ - ┌──┴───┐ ┌──┴───┐ - │Client│ │Server│ - └──────┘ └──────┘ - -* client generates CPubKey, random 64bit R that is used as a nonce - for encryption -* R + enc(PSK, R, CPubKey) + NULLs -> Server [56 bytes] -* server remembers clients address, decrypt CPubKey, generates - SPrivKey/SPubKey, computes common shared key K (based on - CPubKey and SPrivKey), generates 64bit random number RS and - 256bit random SS. PSK-encryption uses incremented R (from previous - message) for nonce -* enc(PSK, SPubKey) + enc(K, RS + SS) + NULLs -> Client [88 bytes] -* client decrypt SPubKey, computes K, decrypts RS, SS with key K, - remembers SS, generates 64bit random number RC and 256bit random SC, -* enc(K, RS + RC + SC) + NULLs -> Server [64 bytes] -* server decrypt RS, RC, SC with key K, compares RS with it's own one - send before, computes final main encryption key S = SS XOR SC -* ENC(K, RC) + NULLs -> Client [24 bytes] -* server switches to the new client -* client decrypts RC and compares with it's own generated one, computes - final main encryption key S - -Where PSK is 256bit pre-shared key, NULLs are 16 null-bytes. R* are -required for handshake randomization and two-way authentication. K key -is used only during handshake. NULLs are required to differentiate -common transport protocol messages from handshake ones. DH public keys -can be trivially derived from private ones. - - -RELATED DOCUMENTS - -* http://cr.yp.to/ecdh.html -* http://cr.yp.to/snuffle.html -* http://cr.yp.to/mac.html -* http://grouper.ieee.org/groups/1363/passwdPK/contributions/jablon.pdf -* Applied Cryptography (C) 1996 Bruce Schneier - -TODO - -* Move decryption and encryption processes into goroutines -* Add identity management (client can send it's identification, server has - on-disk id↔key plaintext database) -* Implement alternative Secure Remote Password protocol (it is much slower, - technically has more code, but human memorized passwords can be used - instead of keys) - -LICENCE - -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 -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. +For futher information please read either doc/govpn.info or doc/govpn.texi. diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..c317254 --- /dev/null +++ b/THANKS @@ -0,0 +1,5 @@ +* 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/TODO b/TODO new file mode 100644 index 0000000..63d6ef3 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +* 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/cmd/govpn-client/main.go b/cmd/govpn-client/main.go new file mode 100644 index 0000000..e358d43 --- /dev/null +++ b/cmd/govpn-client/main.go @@ -0,0 +1,148 @@ +/* +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 . +*/ + +// Simple secure free software virtual private network daemon. +package main + +import ( + "flag" + "log" + "net" + "os" + "os/signal" + + "govpn" +) + +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") + upPath = flag.String("up", "", "Path to up-script") + downPath = flag.String("down", "", "Path to down-script") + mtu = flag.Int("mtu", 1500, "MTU") + nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference") + timeoutP = flag.Int("timeout", 60, "Timeout seconds") +) + +func main() { + flag.Parse() + timeout := *timeoutP + var err error + log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) + + govpn.MTU = *mtu + govpn.Timeout = timeout + govpn.Noncediff = *nonceDiff + + id := govpn.IDDecode(*IDRaw) + key := govpn.KeyRead(*keyPath) + if id == nil { + panic("ID is not specified") + } + + bind, err := net.ResolveUDPAddr("udp", "0.0.0.0:0") + if err != nil { + panic(err) + } + conn, err := net.ListenUDP("udp", bind) + if err != nil { + panic(err) + } + remote, err := net.ResolveUDPAddr("udp", *remoteAddr) + if err != nil { + panic(err) + } + + tap, ethSink, ethReady, _, err := govpn.TAPListen(*ifaceName) + if err != nil { + panic(err) + } + udpSink, udpBuf, udpReady := govpn.ConnListen(conn) + + timeouts := 0 + firstUpCall := true + var peer *govpn.Peer + var ethPkt []byte + var udpPkt *govpn.UDPPkt + var udpPktData []byte + + termSignal := make(chan os.Signal, 1) + signal.Notify(termSignal, os.Interrupt, os.Kill) + + log.Println("Client version", govpn.Version) + log.Println("Starting handshake") + handshake := govpn.HandshakeStart(conn, remote, id, key) + +MainCycle: + for { + if peer != nil && peer.Bytes > govpn.MaxBytesPerKey { + peer = nil + handshake = govpn.HandshakeStart(conn, remote, id, key) + log.Println("Rehandshaking") + } + select { + case <-termSignal: + break MainCycle + case ethPkt = <-ethSink: + if peer == nil { + ethReady <- struct{}{} + continue + } + peer.EthProcess(ethPkt, conn, ethReady) + case udpPkt = <-udpSink: + timeouts++ + if timeouts >= timeout { + break MainCycle + } + if udpPkt == nil { + udpReady <- struct{}{} + continue + } + + udpPktData = udpBuf[:udpPkt.Size] + if govpn.IsValidHandshakePkt(udpPktData) { + if udpPkt.Addr.String() != remote.String() { + udpReady <- struct{}{} + log.Println("Unknown handshake message") + continue + } + if p := handshake.Client(conn, key, udpPktData); p != nil { + log.Println("Handshake completed") + if firstUpCall { + go govpn.ScriptCall(*upPath, *ifaceName) + firstUpCall = false + } + peer = p + handshake = nil + } + udpReady <- struct{}{} + continue + } + if peer == nil { + udpReady <- struct{}{} + continue + } + if peer.UDPProcess(udpPktData, tap, udpReady) { + timeouts = 0 + } + } + } + govpn.ScriptCall(*downPath, *ifaceName) +} diff --git a/cmd/govpn-server/main.go b/cmd/govpn-server/main.go new file mode 100644 index 0000000..f628244 --- /dev/null +++ b/cmd/govpn-server/main.go @@ -0,0 +1,226 @@ +/* +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 . +*/ + +// Simple secure free software virtual private network daemon. +package main + +import ( + "bytes" + "flag" + "log" + "net" + "os" + "os/signal" + "path" + "time" + + "govpn" +) + +var ( + bindAddr = flag.String("bind", "[::]:1194", "Bind to address") + peersPath = flag.String("peers", "peers", "Path to peers keys directory") + mtu = flag.Int("mtu", 1500, "MTU") + nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference") + timeoutP = flag.Int("timeout", 60, "Timeout seconds") +) + +type PeerReadyEvent struct { + peer *govpn.Peer + iface string +} + +type PeerState struct { + peer *govpn.Peer + tap *govpn.TAP + sink chan []byte + ready chan struct{} + terminate chan struct{} +} + +func NewPeerState(peer *govpn.Peer, iface string) *PeerState { + tap, sink, ready, terminate, err := govpn.TAPListen(iface) + if err != nil { + log.Println("Unable to create Eth", err) + return nil + } + state := PeerState{ + peer: peer, + tap: tap, + sink: sink, + ready: ready, + terminate: terminate, + } + return &state +} + +type EthEvent struct { + peer *govpn.Peer + data []byte + ready chan struct{} +} + +func main() { + flag.Parse() + timeout := time.Second * time.Duration(*timeoutP) + 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) + if err != nil { + panic(err) + } + conn, err := net.ListenUDP("udp", bind) + if err != nil { + panic(err) + } + udpSink, udpBuf, udpReady := govpn.ConnListen(conn) + + termSignal := make(chan os.Signal, 1) + signal.Notify(termSignal, os.Interrupt, os.Kill) + + hsHeartbeat := time.Tick(timeout) + go func() { <-hsHeartbeat }() + + var addr string + var state *govpn.Handshake + var peerState *PeerState + var peer *govpn.Peer + var exists bool + states := make(map[string]*govpn.Handshake) + peers := make(map[string]*PeerState) + peerReadySink := make(chan PeerReadyEvent) + var peerReady PeerReadyEvent + var udpPkt *govpn.UDPPkt + var udpPktData []byte + var ethEvent EthEvent + ethSink := make(chan EthEvent) + + log.Println("Server version", govpn.Version) + log.Println("Server started") + +MainCycle: + for { + select { + case <-termSignal: + break MainCycle + case <-hsHeartbeat: + now := time.Now() + for addr, hs := range states { + if hs.LastPing.Add(timeout).Before(now) { + log.Println("Deleting handshake state", addr) + delete(states, addr) + } + } + for addr, state := range peers { + if state.peer.LastPing.Add(timeout).Before(now) { + log.Println("Deleting peer", state.peer) + delete(peers, addr) + downPath := path.Join( + govpn.PeersPath, + state.peer.Id.String(), + "down.sh", + ) + go govpn.ScriptCall(downPath, state.tap.Name) + state.terminate <- struct{}{} + } + } + case peerReady = <-peerReadySink: + for addr, state := range peers { + if state.tap.Name != peerReady.iface { + continue + } + delete(peers, addr) + state.terminate <- struct{}{} + break + } + addr = peerReady.peer.Addr.String() + state := NewPeerState(peerReady.peer, peerReady.iface) + if state == nil { + continue + } + peers[addr] = state + delete(states, addr) + log.Println("Registered interface", peerReady.iface, "with peer", peer) + go func(state *PeerState) { + for data := range state.sink { + ethSink <- EthEvent{ + peer: state.peer, + data: data, + ready: state.ready, + } + } + }(state) + case ethEvent = <-ethSink: + if _, exists := peers[ethEvent.peer.Addr.String()]; !exists { + continue + } + ethEvent.peer.EthProcess(ethEvent.data, conn, ethEvent.ready) + case udpPkt = <-udpSink: + if udpPkt == nil { + udpReady <- struct{}{} + continue + } + udpPktData = udpBuf[:udpPkt.Size] + addr = udpPkt.Addr.String() + if govpn.IsValidHandshakePkt(udpPktData) { + state, exists = states[addr] + if !exists { + state = govpn.HandshakeNew(udpPkt.Addr) + states[addr] = state + } + peer = state.Server(conn, udpPktData) + if peer != nil { + log.Println("Peer handshake finished", peer) + if _, exists = peers[addr]; exists { + go func() { + peerReadySink <- PeerReadyEvent{peer, peers[addr].tap.Name} + }() + } else { + go func() { + upPath := path.Join(govpn.PeersPath, peer.Id.String(), "up.sh") + result, err := govpn.ScriptCall(upPath, "") + if err != nil { + return + } + sepIndex := bytes.Index(result, []byte{'\n'}) + if sepIndex < 0 { + sepIndex = len(result) + } + ifaceName := string(result[:sepIndex]) + peerReadySink <- PeerReadyEvent{peer, ifaceName} + }() + } + } + udpReady <- struct{}{} + continue + } + peerState, exists = peers[addr] + if !exists { + udpReady <- struct{}{} + continue + } + peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady) + } + } +} diff --git a/common.go b/common.go new file mode 100644 index 0000000..b38f52b --- /dev/null +++ b/common.go @@ -0,0 +1,72 @@ +/* +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 . +*/ + +package govpn + +import ( + "bytes" + "encoding/hex" + "io/ioutil" + "log" + "os/exec" +) + +var ( + MTU int + Timeout int + Noncediff int + Version string +) + +// Call external program/script. +// You have to specify path to it and (inteface name as a rule) something +// that will be the first argument when calling it. Function will return +// it's output and possible error. +func ScriptCall(path, ifaceName string) ([]byte, error) { + if path == "" { + return nil, nil + } + cmd := exec.Command(path, ifaceName) + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + result := out.Bytes() + if err != nil { + log.Println("Script error", path, err, string(result)) + } + return result, 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 +} diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..37a161d --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,3 @@ +handshake.utxt +govpn.info +govpn.html diff --git a/doc/download.texi b/doc/download.texi new file mode 100644 index 0000000..3a72cdc --- /dev/null +++ b/doc/download.texi @@ -0,0 +1,11 @@ +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 +@item 1.5 @tab 19 KiB +@tab @url{download/govpn-1.5.tar.xz, link} @url{download/govpn-1.5.tar.xz.sig, sign} +@item 2.0 @tab 31 KiB +@tab @url{download/govpn-2.0.tar.xz, link} @url{download/govpn-2.0.tar.xz.sig, sign} +@end multitable diff --git a/doc/govpn.texi b/doc/govpn.texi new file mode 100644 index 0000000..e3aed5a --- /dev/null +++ b/doc/govpn.texi @@ -0,0 +1,410 @@ +\input texinfo +@setfilename govpn.info +@documentencoding UTF-8 +@settitle GoVPN + +@copying +@quotation +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 +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. +@end quotation +@end copying + +@ifnottex +@node Top +@top GoVPN + +This manual is for GoVPN -- simple secure free software +virtual private network (VPN) daemon. +@end ifnottex + +@menu +* Overview:: +* News:: +* Getting source code:: +* User manual:: +* Developer manual:: +* Reporting bugs:: +* Copying conditions:: +@end menu + +@node Overview +@unnumbered Overview + +GoVPN is simple secure virtual private network daemon. It uses +Diffie-Hellman Encrypted Key Exchange (DH-EKE) for mutual zero-knowledge +peers authentication and authenticated encrypted data transport. + +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. + +Because of stateless UDP nature, after some timeout of inactivity peers +forget about each other and have to retry handshake process again, +therefore background heartbeat process will be ran. + +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. + +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. + +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. + +@itemize @bullet +@item GNU/Linux and FreeBSD support +@item IPv6 compatible +@item Encrypted and authenticated transport +@item Relatively fast handshake +@item Replay attack protection +@item +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 +Zero knowledge authentication (pre-shared key is not transmitted in +any form between the peers, not even it's hash value) +@item Built-in rehandshake and heartbeat features +@item Several simultaneous clients support +@end itemize + +@node News +@unnumbered News + +@verbatiminclude ../NEWS + +@node Getting source code +@unnumbered Getting source code + +GoVPN is written on Go programming language and depends on +@code{golang.org/x/crypto} libraries. + +@include download.texi + +You @strong{have to} verify downloaded archives integrity and check +their signature to be sure that you have got trusted, untampered +software. For integrity and authentication of downloaded binaries +@url{https://www.gnupg.org/, The GNU Privacy Guard} is used. You must +download signature provided with the tarball and run for example: + +@example +gpg --verify govpn-1.5.tar.xz.sig govpn-1.5.tar.xz +@end example + +For the very first time you must also 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 and +look for the mailing list announcements. + +@verbatim +pub rsa2048/FFE2F4A1 2015-03-10 +uid [ultimate] Sergey Matveev (GoVPN release signing key) +sub rsa2048/8A6C750A 2015-03-10 + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFT/H6cBCADTf/oqoTTBAA/CCQuYtzg8vrXxyjXj9yy4lTWqMSwgLXMm8br/ +kG0Jnk63oP3hggI3hm2mpuiNwpwrJiORLBZCe8JgZW71zG4LfhVpQeWd7fu8WxDx +0uUZWByz5KcK8c/kNWNDpSkMmmqdE/8v0YDFbsz5U+ytp/Kki/gj3BCeIX3jYOL1 +fxczkv2okoU+aGYXt9z50VzheLUSRLzkkX8yNSpszqfB0LEEmUk8HO2fSS/bXwaY +ZXX5//suH8V5hwq8vB8dHHCquZW6blyzcTa2KGIh6g2CmpypIQp/i5QAbzOCHKTM +A1F7A1r0kYF2WfZOrycCfjUx3GA5B7sytuA3ABEBAAG0RFNlcmdleSBNYXR2ZWV2 +IChHb1ZQTiByZWxlYXNlIHNpZ25pbmcga2V5KSA8c3RhcmdyYXZlQHN0YXJncmF2 +ZS5vcmc+iQE8BBMBCAAmBQJU/x+nAhsDCAsKCQgHBAMCBxUKCQgLAwIFFgIBAwAC +HgECF4AACgkQ8vWQRf/i9KEZ/AgAqYF/RRNwwhgLgFqTLfw3ha0FeiSso7H9ITDo +cdJ/domLHaFvmwFIDQQKV8Zd1Rnj6xTCs2bq2O5hYMLrFZg85A9i5tLwkgFc9J5G ++8K3K/dh9Y4pArbM+craO+xydrwLyg1zlXCezthWbL0iXO/CuGiuBBCZJqRJ9HV4 +cZr4TRA3Znm5nt96rRsR86XqOgr0iOEDtYKfKW/IzDqOEgXUN5o2bUwuQawe9Y8d +CngXzJcfb2eJ/TqSP9CxVWscjz4sAmD3/ECrHSjX7xsusIs46F2+VMlEXFuST52r +zamfiGKlol8XvimUjKhlMWjqfdcJ0+jvFftsa7HXQUwRoQ1vJYheBBARCAAGBQJU +/x/VAAoJEK4agQnkmFfvqn8A/ReK2ZZrnI9s0rzTsF1jrTZ1o5YowuINOzVMmLbE +aYuGAP4iGwPgwVbANu4dWaP2N03oL4xFtmdaeNn3sB9ZqJOOyrkBDQRU/x+nAQgA +uYBRyJVwhlE2SRIEmMggwr4gq1JBM2Ge5O46usf+YPUjCJKWoAj+MpQoq7r+oA/s +E/6kGvWgngwV9prCdNkvcdwEWbb+n9PcMc2ZuIGRV3iOKYlYEBFV0bfM9zEV2jar +1YQ+J/48UX7R00cYJuXel7Dy77V9eNd+Ukyowm93fggFlBDBGBjVbNtfIorHNYjB +01CCu3i/8yxrMyFRvMKyAVEGp3obgmlam4DNkNIhFMv3du0tFnDFBsZf7N0kbLWI +xEEJoc/jxaezDytQpUr3RhlMsLV6N/jjIZuy36QO1sbFeOe2to0E7ixaFzNCWsqY +cxUfnJ3wi7hOiOwE2PF3tQARAQABiQEfBBgBCAAJBQJU/x+nAhsMAAoJEPL1kEX/ +4vShrVcIAKLUwMn7WgK6thmwPjdwP5V/jTlsWLWk2O/LEN4W/R0mw2hRsgRG/8Sz +qlAP6vfl7ERaWuyL+fp72rKnGTGU9CEvn6PKmaG7bi4tGEvWXscNc10r0leIAP63 +pkQOa6Nyx2axJlJdSuTsYetd1ZgNpHNng+lxSUBlkPMOhPd/P/Ok7DShZjd2jhQ1 +jUbjWn+P7ARGEvgdd5utNjy/RaSwrLG8NXj3I+XuksG0/TPeG0zu9NOPzWZq9sCc +5VbDNJTYtsMFs1etHE95Efmx6yUquQyB+g/HgvkH/LzthBawVVHxZNzzHgc6KN5w +E0itJPXMaQL+juUfiNM0i2R1O8nJo14= +=LJzj +-----END PGP PUBLIC KEY BLOCK----- +@end verbatim + +@node User manual +@unnumbered User manual + +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 remembers +latest received nonce number from the remote peer and drops those who +has lower ones. Because UDP packets can be reordered during: that +behaviour can lead to dropping of not replayed ones. This options gives +ability to create some window of allows difference. That opens the door +for replay attacks for narrow time interval. +@item MTU +Maximum transmission unit. +@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 +key file with corresponding authentication key, up.sh script that has to +print interface's name on the first line and optional down.sh. + +@menu +* 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). + +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% chmod 500 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% govpn -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% while :; do + govpn -key key.txt -id CLIENTID -iface tap10 -remote 192.168.0.1:1194 +done +@end example + +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 < up.sh < Server|} [65 bytes] +@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 +@item +@verb{|enc(PSK, SPubKey) + enc(K, RS + SS) + NULLs -> Client|} [88 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] +@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] +@item +server switches to the new client +@item +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. + +@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. + +Development Git source code repository currently is located on: +@url{https://github.com/stargrave/govpn}. + +@node Copying conditions +@unnumbered Copying conditions + +@insertcopying + +@bye diff --git a/doc/handshake.txt b/doc/handshake.txt new file mode 100644 index 0000000..d946833 --- /dev/null +++ b/doc/handshake.txt @@ -0,0 +1,21 @@ +@startuml +participant Client +participant Server + +Client -> Client : R=rand(64bit) +Client -> Client : CPrivKey=rand(256bit) +Client -> Server : R, enc(PSK, R, CPubKey), xtea(ID, R) +Server -> Server : SPrivKey=rand(256bit) +Server -> Server : K=DH(SPrivKey, CPubKey) +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) +Client -> Client : RC=rand(64bit); SC=rand(256bit) +Client -> Server : enc(K, R+1, RS+RC+SC) +Server -> Server : compare(RS) +Server -> Server : MasterKey=SS XOR SC +Server -> Client : enc(K, 0x00, RC) +Client -> Client : compare(RC) +Client -> Client : MasterKey=SS XOR SC +@enduml diff --git a/doc/makefile b/doc/makefile new file mode 100644 index 0000000..aa886a9 --- /dev/null +++ b/doc/makefile @@ -0,0 +1,10 @@ +all: govpn.info govpn.html + +govpn.info: govpn.texi handshake.utxt + makeinfo govpn.texi + +handshake.utxt: handshake.txt + plantuml -tutxt handshake.txt + +govpn.html: govpn.texi handshake.utxt + makeinfo --html -o govpn.html govpn.texi diff --git a/govpn.go b/govpn.go index 6f662c6..9a7b114 100644 --- a/govpn.go +++ b/govpn.go @@ -1,5 +1,5 @@ /* -govpn -- simple secure virtual private network daemon +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 @@ -16,343 +16,5 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Simple secure virtual private network daemon -package main - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "flag" - "fmt" - "io" - "io/ioutil" - "log" - "net" - "os" - "os/exec" - "os/signal" - "time" - - "golang.org/x/crypto/poly1305" - "golang.org/x/crypto/salsa20" - "golang.org/x/crypto/xtea" -) - -var ( - remoteAddr = flag.String("remote", "", "Remote server address") - bindAddr = flag.String("bind", "", "Bind to address") - ifaceName = flag.String("iface", "tap0", "TAP network interface") - keyPath = flag.String("key", "", "Path to authentication key file") - upPath = flag.String("up", "", "Path to up-script") - downPath = flag.String("down", "", "Path to down-script") - mtu = flag.Int("mtu", 1500, "MTU") - nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference") - timeoutP = flag.Int("timeout", 60, "Timeout seconds") - verboseP = flag.Bool("v", false, "Increase verbosity") -) - -const ( - NonceSize = 8 - KeySize = 32 - // S20BS is Salsa20's internal blocksize in bytes - S20BS = 64 - HeartBeatSize = 12 - HeartBeatMark = "\x00\x00\x00HEARTBEAT" - // Maximal amount of bytes transfered with single key (4 GiB) - MaxBytesPerKey = 4294967296 -) - -type TAP interface { - io.Reader - io.Writer -} - -type Peer struct { - addr *net.UDPAddr - key *[KeySize]byte // encryption key - nonceOur uint64 // nonce for our messages - nonceRecv uint64 // latest received nonce from remote peer - nonceCipher *xtea.Cipher // nonce cipher -} - -type UDPPkt struct { - addr *net.UDPAddr - size int -} - -func ScriptCall(path *string) { - if *path == "" { - return - } - cmd := exec.Command(*path, *ifaceName) - var out bytes.Buffer - cmd.Stdout = &out - if err := cmd.Run(); err != nil { - fmt.Println(time.Now(), "script error: ", err.Error(), string(out.Bytes())) - } -} - -func main() { - flag.Parse() - timeout := *timeoutP - verbose := *verboseP - noncediff := uint64(*nonceDiff) - var err error - log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) - - // Key decoding - keyData, err := ioutil.ReadFile(*keyPath) - 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) - keyDecoded = nil - keyData = nil - - // Interface listening - maxIfacePktSize := *mtu - poly1305.TagSize - NonceSize - log.Println("Max MTU", maxIfacePktSize, "on interface", *ifaceName) - iface := NewTAP(*ifaceName) - ethBuf := make([]byte, maxIfacePktSize) - ethSink := make(chan int) - ethSinkReady := make(chan bool) - go func() { - var n int - var err error - for { - <-ethSinkReady - n, err = iface.Read(ethBuf) - if err != nil { - panic(err) - } - ethSink <- n - } - }() - ethSinkReady <- true - - // Network address parsing - if (len(*bindAddr) > 1 && len(*remoteAddr) > 1) || - (len(*bindAddr) == 0 && len(*remoteAddr) == 0) { - panic("Either -bind or -remote must be specified only") - } - var conn *net.UDPConn - var remote *net.UDPAddr - serverMode := false - bindTo := "0.0.0.0:0" - - if len(*bindAddr) > 1 { - bindTo = *bindAddr - serverMode = true - } - - bind, err := net.ResolveUDPAddr("udp", bindTo) - if err != nil { - panic(err) - } - conn, err = net.ListenUDP("udp", bind) - if err != nil { - panic(err) - } - - if len(*remoteAddr) > 1 { - remote, err = net.ResolveUDPAddr("udp", *remoteAddr) - if err != nil { - panic(err) - } - } - - udpBuf := make([]byte, *mtu) - udpSink := make(chan *UDPPkt) - udpSinkReady := make(chan bool) - go func(conn *net.UDPConn) { - var n int - var addr *net.UDPAddr - var err error - for { - <-udpSinkReady - conn.SetReadDeadline(time.Now().Add(time.Second)) - n, addr, err = conn.ReadFromUDP(udpBuf) - if err != nil { - if verbose { - fmt.Print("B") - } - udpSink <- nil - } else { - udpSink <- &UDPPkt{addr, n} - } - } - }(conn) - udpSinkReady <- true - - // Process packets - var udpPkt *UDPPkt - var udpPktData []byte - var ethPktSize int - var frame []byte - var dataToSend []byte - var addr string - var peer *Peer - var p *Peer - var nonceRecv uint64 - - timeouts := 0 - bytes := 0 - states := make(map[string]*Handshake) - nonce := make([]byte, NonceSize) - keyAuth := new([KeySize]byte) - tag := new([poly1305.TagSize]byte) - buf := make([]byte, *mtu+S20BS) - emptyKey := make([]byte, KeySize) - - if !serverMode { - states[remote.String()] = HandshakeStart(conn, remote, key) - } - - heartbeat := time.Tick(time.Second * time.Duration(timeout/3)) - go func() { <-heartbeat }() - heartbeatMark := []byte(HeartBeatMark) - - termSignal := make(chan os.Signal, 1) - signal.Notify(termSignal, os.Interrupt, os.Kill) - - finished := false - for { - if finished { - break - } - if !serverMode && bytes > MaxBytesPerKey { - states[remote.String()] = HandshakeStart(conn, remote, key) - bytes = 0 - } - select { - case <-termSignal: - finished = true - case <-heartbeat: - go func() { ethSink <- -1 }() - case udpPkt = <-udpSink: - timeouts++ - if !serverMode && timeouts >= timeout { - finished = true - } - if udpPkt == nil { - udpSinkReady <- true - continue - } - udpPktData = udpBuf[:udpPkt.size] - if isValidHandshakePkt(udpPktData) { - addr = udpPkt.addr.String() - state, exists := states[addr] - if serverMode { - if !exists { - state = &Handshake{addr: udpPkt.addr} - states[addr] = state - } - p = state.Server(noncediff, conn, key, udpPktData) - } else { - if !exists { - fmt.Print("[HS?]") - udpSinkReady <- true - continue - } - p = state.Client(noncediff, conn, key, udpPktData) - } - if p != nil { - fmt.Print("[HS-OK]") - if peer == nil { - go ScriptCall(upPath) - } - peer = p - delete(states, addr) - } - udpSinkReady <- true - continue - } - if peer == nil { - udpSinkReady <- true - continue - } - copy(buf[:KeySize], emptyKey) - copy(tag[:], udpPktData[udpPkt.size-poly1305.TagSize:]) - copy(buf[S20BS:], udpPktData[NonceSize:udpPkt.size-poly1305.TagSize]) - salsa20.XORKeyStream( - buf[:S20BS+udpPkt.size-poly1305.TagSize], - buf[:S20BS+udpPkt.size-poly1305.TagSize], - udpPktData[:NonceSize], - peer.key, - ) - copy(keyAuth[:], buf[:KeySize]) - if !poly1305.Verify(tag, udpPktData[:udpPkt.size-poly1305.TagSize], keyAuth) { - udpSinkReady <- true - fmt.Print("T") - continue - } - peer.nonceCipher.Decrypt(buf, udpPktData[:NonceSize]) - nonceRecv, _ = binary.Uvarint(buf[:NonceSize]) - if nonceRecv < peer.nonceRecv-noncediff { - fmt.Print("R") - udpSinkReady <- true - continue - } - udpSinkReady <- true - peer.nonceRecv = nonceRecv - timeouts = 0 - frame = buf[S20BS : S20BS+udpPkt.size-NonceSize-poly1305.TagSize] - bytes += len(frame) - if string(frame[0:HeartBeatSize]) == HeartBeatMark { - continue - } - if _, err = iface.Write(frame); err != nil { - log.Println("Error writing to iface: ", err) - } - if verbose { - fmt.Print("r") - } - case ethPktSize = <-ethSink: - if ethPktSize > maxIfacePktSize { - panic("Too large packet on interface") - } - if peer == nil { - ethSinkReady <- true - continue - } - - peer.nonceOur = peer.nonceOur + 2 - for i := 0; i < NonceSize; i++ { - nonce[i] = '\x00' - } - binary.PutUvarint(nonce, peer.nonceOur) - peer.nonceCipher.Encrypt(nonce, nonce) - - copy(buf[:KeySize], emptyKey) - if ethPktSize > -1 { - copy(buf[S20BS:], ethBuf[:ethPktSize]) - ethSinkReady <- true - } else { - copy(buf[S20BS:], heartbeatMark) - ethPktSize = HeartBeatSize - } - salsa20.XORKeyStream(buf, buf, nonce, peer.key) - copy(buf[S20BS-NonceSize:S20BS], nonce) - copy(keyAuth[:], buf[:KeySize]) - dataToSend = buf[S20BS-NonceSize : S20BS+ethPktSize] - poly1305.Sum(tag, dataToSend, keyAuth) - bytes += len(dataToSend) - if _, err = conn.WriteTo(append(dataToSend, tag[:]...), peer.addr); err != nil { - log.Println("Error sending UDP", err) - } - if verbose { - fmt.Print("w") - } - } - } - ScriptCall(downPath) -} +// Simple secure free software virtual private network daemon +package govpn diff --git a/handshake.go b/handshake.go index 9faf9ec..c458f5d 100644 --- a/handshake.go +++ b/handshake.go @@ -1,5 +1,5 @@ /* -govpn -- Simple secure virtual private network daemon +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 @@ -16,14 +16,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package govpn import ( "crypto/rand" "crypto/subtle" "encoding/binary" - "fmt" + "log" "net" + "path" "time" "golang.org/x/crypto/curve25519" @@ -35,17 +36,18 @@ import ( type Handshake struct { addr *net.UDPAddr - lastPing time.Time + LastPing time.Time + Id PeerId rNonce *[8]byte - dhPriv *[32]byte // own private DH key - key *[32]byte // handshake encryption key - rServer *[8]byte // random string for authentication + 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 } -func KeyFromSecrets(server, client []byte) *[32]byte { +func keyFromSecrets(server, client []byte) *[KeySize]byte { k := new([32]byte) for i := 0; i < 32; i++ { k[i] = server[i] ^ client[i] @@ -53,19 +55,9 @@ func KeyFromSecrets(server, client []byte) *[32]byte { return k } -func NewNonceCipher(key *[32]byte) *xtea.Cipher { - nonceKey := make([]byte, 16) - salsa20.XORKeyStream(nonceKey, make([]byte, 32), make([]byte, 8), key) - ciph, err := xtea.NewCipher(nonceKey) - if err != nil { - panic(err) - } - return ciph -} - -// Check if it is valid handshake-related message -// Minimal size and last 16 zero bytes -func isValidHandshakePkt(pkt []byte) bool { +// 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 } @@ -99,10 +91,22 @@ func dhKeyGen(priv, pub *[32]byte) *[32]byte { return key } -func HandshakeStart(conn *net.UDPConn, addr *net.UDPAddr, key *[32]byte) *Handshake { - state := Handshake{} - state.addr = addr - state.lastPing = time.Now() +// Create new handshake state. +func HandshakeNew(addr *net.UDPAddr) *Handshake { + state := Handshake{ + addr: addr, + LastPing: time.Now(), + } + return &state +} + +// 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) state.dhPriv = dhPrivGen() dhPub := new([32]byte) @@ -115,23 +119,47 @@ func HandshakeStart(conn *net.UDPConn, addr *net.UDPAddr, key *[32]byte) *Handsh enc := make([]byte, 32) salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], key) - if _, err := conn.WriteTo( - append(state.rNonce[:], - append(enc, make([]byte, poly1305.TagSize)...)...), addr); err != nil { + 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)...) + + if _, err := conn.WriteTo(data, addr); err != nil { panic(err) } - return &state + return state } -func (h *Handshake) Server(noncediff uint64, conn *net.UDPConn, key *[32]byte, data []byte) *Peer { +// 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. +// 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 56: // R + ENC(PSK, dh_client_pub) + NULLs - fmt.Print("[HS1]") + case 65: // R + ENC(PSK, dh_client_pub) + xtea(ID, R) + NULL + NULLs if h.rNonce != nil { - fmt.Print("[S?]") + 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 + } + key := KeyRead(path.Join(PeersPath, id.String(), "key")) + h.Id = *id + // Generate private DH key h.dhPriv = dhPrivGen() dhPub := new([32]byte) @@ -167,19 +195,18 @@ func (h *Handshake) Server(noncediff uint64, conn *net.UDPConn, key *[32]byte, d append(encRs, make([]byte, poly1305.TagSize)...)...), h.addr); err != nil { panic(err) } - fmt.Print("[OK]") + h.LastPing = time.Now() case 64: // ENC(K, RS + RC + SC) + NULLs - fmt.Print("[HS3]") if (h.rNonce == nil) || (h.rClient != nil) { - fmt.Print("[S?]") + log.Println("Invalid handshake stage from", h.addr) return nil } - // Decrypt Rs compare rServer + // Decrypted Rs compare rServer decRs := make([]byte, 8+8+32) salsa20.XORKeyStream(decRs, data[:8+8+32], h.rNonceNext(), h.key) - if res := subtle.ConstantTimeCompare(decRs[:8], h.rServer[:]); res != 1 { - fmt.Print("[rS?]") + if subtle.ConstantTimeCompare(decRs[:8], h.rServer[:]) != 1 { + log.Println("Invalid server's random number with", h.addr) return nil } @@ -191,27 +218,28 @@ func (h *Handshake) Server(noncediff uint64, conn *net.UDPConn, key *[32]byte, d } // Switch peer - peer := Peer{ - addr: h.addr, - nonceOur: noncediff + 0, - nonceRecv: noncediff + 0, - key: KeyFromSecrets(h.sServer[:], decRs[8+8:]), - } - peer.nonceCipher = NewNonceCipher(peer.key) - fmt.Print("[OK]") - return &peer + peer := newPeer(h.addr, h.Id, 0, keyFromSecrets(h.sServer[:], decRs[8+8:])) + h.LastPing = time.Now() + return peer default: - fmt.Print("[HS?]") + log.Println("Invalid handshake message from", h.addr) } return nil } -func (h *Handshake) Client(noncediff uint64, conn *net.UDPConn, key *[32]byte, data []byte) *Peer { +// 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. +// 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 { switch len(data) { case 88: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + NULLs - fmt.Print("[HS2]") if h.key != nil { - fmt.Print("[S?]") + log.Println("Invalid handshake stage from", h.addr) return nil } @@ -246,34 +274,27 @@ func (h *Handshake) Client(noncediff uint64, conn *net.UDPConn, key *[32]byte, d if _, err := conn.WriteTo(append(encRs, make([]byte, poly1305.TagSize)...), h.addr); err != nil { panic(err) } - fmt.Print("[OK]") + h.LastPing = time.Now() case 24: // ENC(K, RC) + NULLs - fmt.Print("[HS4]") if h.key == nil { - fmt.Print("[S?]") + 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) - if res := subtle.ConstantTimeCompare(dec, h.rClient[:]); res != 1 { - fmt.Print("[rC?]") + if subtle.ConstantTimeCompare(dec, h.rClient[:]) != 1 { + log.Println("Invalid client's random number with", h.addr) return nil } // Switch peer - peer := Peer{ - addr: h.addr, - nonceOur: noncediff + 1, - nonceRecv: noncediff + 0, - key: KeyFromSecrets(h.sServer[:], h.sClient[:]), - } - peer.nonceCipher = NewNonceCipher(peer.key) - fmt.Print("[OK]") - return &peer + peer := newPeer(h.addr, h.Id, 1, keyFromSecrets(h.sServer[:], h.sClient[:])) + h.LastPing = time.Now() + return peer default: - fmt.Print("[HS?]") + log.Println("Invalid handshake message from", h.addr) } return nil } diff --git a/identify.go b/identify.go new file mode 100644 index 0000000..8547e60 --- /dev/null +++ b/identify.go @@ -0,0 +1,123 @@ +/* +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 . +*/ + +package govpn + +import ( + "crypto/subtle" + "encoding/hex" + "log" + "os" + + "golang.org/x/crypto/xtea" +) + +const ( + IDSize = 128 / 8 +) + +type PeerId [IDSize]byte + +func (id PeerId) String() string { + return hex.EncodeToString(id[:]) +} + +type cipherCache map[PeerId]*xtea.Cipher + +var ( + PeersPath string + IDsCache cipherCache +) + +// Initialize (pre-cache) available peers info. +func PeersInit(path string) { + PeersPath = path + IDsCache = make(map[PeerId]*xtea.Cipher) + IDsCache.refresh() +} + +// Refresh IDsCache: remove disappeared keys, add missing ones with +// initialized ciphers. +func (cc cipherCache) refresh() { + dir, err := os.Open(PeersPath) + if err != nil { + panic(err) + } + peerIds, err := dir.Readdirnames(0) + if err != nil { + panic(err) + } + available := make(map[PeerId]bool) + for _, peerId := range peerIds { + id := IDDecode(peerId) + if id == nil { + continue + } + available[*id] = true + } + // Cleanup deleted ones from cache + for k, _ := range cc { + if _, exists := available[k]; !exists { + delete(cc, k) + log.Println("Cleaning key: ", k) + } + } + // Add missing ones + for peerId, _ := range available { + if _, exists := cc[peerId]; !exists { + log.Println("Adding key", peerId) + cipher, err := xtea.NewCipher(peerId[:]) + if err != nil { + panic(err) + } + cc[peerId] = cipher + } + } +} + +// 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() + buf := make([]byte, xtea.BlockSize) + for pid, cipher := range cc { + cipher.Decrypt(buf, ciphertext) + if subtle.ConstantTimeCompare(buf, plaintext) == 1 { + ppid := PeerId(pid) + return &ppid + } + } + return nil +} + +// Decode identification string. +// It must be 32 hexadecimal characters long. +// If it is not the valid one, then return nil. +func IDDecode(raw string) *PeerId { + if len(raw) != IDSize*2 { + return nil + } + idDecoded, err := hex.DecodeString(raw) + if err != nil { + return nil + } + idP := new([IDSize]byte) + copy(idP[:], idDecoded) + id := PeerId(*idP) + return &id +} diff --git a/makedist.sh b/makedist.sh new file mode 100755 index 0000000..9b2ac21 --- /dev/null +++ b/makedist.sh @@ -0,0 +1,18 @@ +#!/bin/sh -ex + +cur=$(pwd) +tmp=$(mktemp -d) +release=$1 +[ -n "$release" ] +git clone . $tmp/govpn-$release +cd $tmp/govpn-$release +git checkout $release +rm -fr .git +find . -name .gitignore -delete +echo > doc/download.texi +make -C doc +cd $tmp +tar cvf govpn-"$release".tar govpn-"$release" +xz -9 govpn-"$release".tar +gpg --detach-sign --sign --local-user FFE2F4A1 govpn-"$release".tar.xz +mv $tmp/govpn-"$release".tar.xz $tmp/govpn-"$release".tar.xz.sig $cur/doc/govpn.html/download diff --git a/makefile b/makefile new file mode 100644 index 0000000..d6bed1c --- /dev/null +++ b/makefile @@ -0,0 +1,17 @@ +.PHONY: govpn-client govpn-server + +VERSION=2.0 +LDFLAGS=-X govpn.Version $(VERSION) + +all: govpn-client govpn-server + +dependencies: + go get golang.org/x/crypto/poly1305 + go get golang.org/x/crypto/salsa20 + go get golang.org/x/crypto/xtea + +govpn-client: dependencies + go build -ldflags "$(LDFLAGS)" govpn/cmd/govpn-client + +govpn-server: dependencies + go build -ldflags "$(LDFLAGS)" govpn/cmd/govpn-server diff --git a/tap.go b/tap.go new file mode 100644 index 0000000..eabc924 --- /dev/null +++ b/tap.go @@ -0,0 +1,68 @@ +/* +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 . +*/ + +package govpn + +import ( + "io" + "log" + + "golang.org/x/crypto/poly1305" +) + +type TAP struct { + Name string + dev io.ReadWriteCloser + buf []byte + sink chan []byte + ready chan struct{} +} + +func NewTAP(ifaceName string) (*TAP, error) { + maxIfacePktSize := MTU - poly1305.TagSize - NonceSize + tapRaw, err := newTAPer(ifaceName) + if err != nil { + return nil, err + } + tap := TAP{ + Name: ifaceName, + dev: tapRaw, + buf: make([]byte, maxIfacePktSize), + sink: make(chan []byte), + ready: make(chan struct{}), + } + go func() { + var n int + var err error + for { + <-tap.ready + n, err = tap.dev.Read(tap.buf) + if err != nil { + panic(err) + } + tap.sink <- tap.buf[:n] + } + }() + return &tap, nil +} + +func (t *TAP) Write(data []byte) { + if _, err := t.dev.Write(data); err != nil { + log.Println("Error writing to iface: ", err) + } +} diff --git a/tap_freebsd.go b/tap_freebsd.go index 197df48..547fb06 100644 --- a/tap_freebsd.go +++ b/tap_freebsd.go @@ -1,21 +1,18 @@ // +build freebsd /* -govpn -- Simple secure virtual private network daemon +GoVPN -- simple secure free software virtual private network daemon Copyright (C) 2014-2015 Sergey Matveev */ -package main +package govpn import ( + "io" "os" "path" ) -func NewTAP(ifaceName string) TAP { - fd, err := os.OpenFile(path.Join("/dev/", ifaceName), os.O_RDWR, os.ModePerm) - if err != nil { - panic(err) - } - return fd +func newTAPer(ifaceName string) (io.ReadWriteCloser, error) { + return os.OpenFile(path.Join("/dev/", ifaceName), os.O_RDWR, os.ModePerm) } diff --git a/tap_linux.go b/tap_linux.go index 6fc4265..938a3b5 100644 --- a/tap_linux.go +++ b/tap_linux.go @@ -1,20 +1,18 @@ // +build linux /* -govpn -- Simple secure virtual private network daemon +GoVPN -- simple secure free software virtual private network daemon Copyright (C) 2014-2015 Sergey Matveev */ -package main +package govpn import ( + "io" + "github.com/chon219/water" ) -func NewTAP(string ifaceName) TAP { - iface, err := water.NewTAP(ifaceName) - if err != nil { - panic(err) - } - return iface +func newTAPer(string ifaceName) (io.ReadWriteCloser, error) { + return water.NewTAP(ifaceName) } diff --git a/transport.go b/transport.go new file mode 100644 index 0000000..3c460e6 --- /dev/null +++ b/transport.go @@ -0,0 +1,274 @@ +/* +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 . +*/ + +package govpn + +import ( + "crypto/subtle" + "encoding/binary" + "log" + "net" + "time" + + "golang.org/x/crypto/poly1305" + "golang.org/x/crypto/salsa20" + "golang.org/x/crypto/xtea" +) + +const ( + NonceSize = 8 + KeySize = 32 + // S20BS is Salsa20's internal blocksize in bytes + S20BS = 64 + HeartbeatSize = 12 + // Maximal amount of bytes transfered with single key (4 GiB) + MaxBytesPerKey = 4294967296 +) + +type UDPPkt struct { + Addr *net.UDPAddr + Size int +} + +type Peer struct { + Addr *net.UDPAddr + Id PeerId + Key *[KeySize]byte // encryption key + NonceOur uint64 // nonce for our messages + NonceRecv uint64 // latest received nonce from remote peer + NonceCipher *xtea.Cipher // nonce cipher + LastPing time.Time + LastSent time.Time + buf []byte + tag *[poly1305.TagSize]byte + keyAuth *[KeySize]byte + nonceRecv uint64 + Bytes int + frame []byte + nonce []byte +} + +func (p *Peer) String() string { + return p.Id.String() + ":" + p.Addr.String() +} + +var ( + HeartbeatMark = []byte("\x00\x00\x00HEARTBEAT") + Emptiness = make([]byte, KeySize) + taps = make(map[string]*TAP) + heartbeatPeriod *time.Duration +) + +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) { + var tap *TAP + var err error + tap, exists := taps[ifaceName] + if !exists { + tap, err = NewTAP(ifaceName) + if err != nil { + return nil, nil, nil, nil, err + } + taps[ifaceName] = tap + } + sink := make(chan []byte) + sinkReady := make(chan struct{}) + sinkTerminate := make(chan struct{}) + + go func() { + heartbeat := time.Tick(heartbeatPeriodGet()) + var pkt []byte + ListenCycle: + for { + select { + case <-sinkTerminate: + break ListenCycle + case <-heartbeat: + sink <- make([]byte, 0) + continue + case <-sinkReady: + if exists { + exists = false + break + } + tap.ready <- struct{}{} + } + HeartbeatCatched: + select { + case <-heartbeat: + sink <- make([]byte, 0) + goto HeartbeatCatched + case <-sinkTerminate: + break ListenCycle + case pkt = <-tap.sink: + sink <- pkt + } + } + close(sink) + }() + sinkReady <- struct{}{} + return tap, sink, sinkReady, sinkTerminate, nil +} + +// Create UDP listening goroutine. +// This function takes already listening UDP socket and a buffer where +// 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{}) { + buf := make([]byte, MTU) + sink := make(chan *UDPPkt) + sinkReady := make(chan struct{}) + go func(conn *net.UDPConn) { + var n int + var addr *net.UDPAddr + var err error + for { + <-sinkReady + conn.SetReadDeadline(time.Now().Add(time.Second)) + n, addr, err = conn.ReadFromUDP(buf) + if err != nil { + // This is needed for ticking the timeouts counter outside + sink <- nil + continue + } + sink <- &UDPPkt{addr, n} + } + }(conn) + sinkReady <- struct{}{} + return sink, buf, sinkReady +} + +func newNonceCipher(key *[KeySize]byte) *xtea.Cipher { + nonceKey := make([]byte, 16) + salsa20.XORKeyStream(nonceKey, make([]byte, KeySize), make([]byte, xtea.BlockSize), key) + ciph, err := xtea.NewCipher(nonceKey) + if err != nil { + panic(err) + } + return ciph +} + +func newPeer(addr *net.UDPAddr, id PeerId, nonce int, key *[KeySize]byte) *Peer { + peer := Peer{ + Addr: addr, + LastPing: time.Now(), + Id: id, + NonceOur: uint64(Noncediff + nonce), + NonceRecv: uint64(Noncediff + 0), + Key: key, + NonceCipher: newNonceCipher(key), + Bytes: 0, + buf: make([]byte, MTU+S20BS), + tag: new([poly1305.TagSize]byte), + keyAuth: new([KeySize]byte), + nonce: make([]byte, NonceSize), + } + return &peer +} + +// Process incoming UDP packet. +// udpPkt is received data, related to the peer tap interface and +// 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 { + size := len(udpPkt) + copy(p.buf[:KeySize], Emptiness) + copy(p.tag[:], udpPkt[size-poly1305.TagSize:]) + copy(p.buf[S20BS:], udpPkt[NonceSize:size-poly1305.TagSize]) + salsa20.XORKeyStream( + p.buf[:S20BS+size-poly1305.TagSize], + p.buf[:S20BS+size-poly1305.TagSize], + udpPkt[:NonceSize], + p.Key, + ) + copy(p.keyAuth[:], p.buf[:KeySize]) + if !poly1305.Verify(p.tag, udpPkt[:size-poly1305.TagSize], p.keyAuth) { + ready <- struct{}{} + return false + } + 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 { + ready <- struct{}{} + return false + } + 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) + if subtle.ConstantTimeCompare(p.frame[:HeartbeatSize], HeartbeatMark) == 1 { + return true + } + tap.Write(p.frame) + return true +} + +// 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{}) { + now := time.Now() + size := len(ethPkt) + // If this heartbeat is necessary + if size == 0 && !p.LastSent.Add(heartbeatPeriodGet()).Before(now) { + return + } + copy(p.buf[:KeySize], Emptiness) + if size > 0 { + copy(p.buf[S20BS:], ethPkt) + ready <- struct{}{} + } else { + copy(p.buf[S20BS:], HeartbeatMark) + size = HeartbeatSize + } + + p.NonceOur = 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] + poly1305.Sum(p.tag, p.frame, p.keyAuth) + + p.Bytes += 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) + } +} -- 2.44.0