TODO | 3 +-- VERSION | 2 +- doc/client.texi | 7 ++----- doc/developer.texi | 19 +++++++++++-------- doc/download.texi | 4 ++++ doc/egd.texi | 19 +++++++++++++++++++ doc/govpn.texi | 2 ++ doc/keywords.texi | 17 +++++++++-------- doc/news.texi | 10 ++++++++++ doc/noncediff.texi | 17 ----------------- doc/overview.texi | 65 +++++++++++++++++++++++------------------------------ doc/precautions.texi | 34 +++++++++++++++------------------- doc/server.texi | 8 ++------ doc/todo.texi | 11 +++++++++++ doc/transport.texi | 15 ++++++--------- doc/user.texi | 4 ++-- doc/verifierstruct.texi | 8 ++++---- src/govpn/cmd/govpn-client/main.go | 8 ++++++-- src/govpn/cmd/govpn-server/main.go | 6 ++++++ src/govpn/egd.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/govpn/handshake.go | 22 ++++++++++++++++------ src/govpn/identify.go | 6 +----- src/govpn/transport.go | 74 ++++++++++++++++++++++++++++++++++------------------- src/govpn/transport_test.go | 3 ++- diff --git a/TODO b/TODO old mode 100644 new mode 120000 index a2043caacde4e0a934049ddc1a15beca7921bbca..e8b79ff7bcccc2d0ab0409382a279d9b0ddf963e --- a/TODO +++ b/TODO @@ -1,2 +1 @@ -* Increase performance -* Randomize ports usage +doc/todo.texi \ No newline at end of file diff --git a/VERSION b/VERSION index eb39e5382f4f035e4d71c7f67712cdbfa6c0c335..2f4b60750dc3500b0e4cf08f316a960a7ca42b40 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.3 +3.4 diff --git a/doc/client.texi b/doc/client.texi index dfee4f19b68f9dc3c4c8f56490b0e1ca278c17a6..e2c26fd727b83089a079e0d1afb14b899ce2d18f 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -1,8 +1,8 @@ @node Client part @section Client part -Except for common @code{-mtu}, @code{-stats} options client has the -following ones: +Except for common @code{-mtu}, @code{-stats}, @code{-egd} options client +has the following ones: @table @code @@ -21,9 +21,6 @@ how to enter passphrase from stdin silently and store it in the file. @item -timeout @ref{Timeout} setting in seconds. - -@item -noncediff -Allowable @ref{Nonce difference}. @item -noise Enable @ref{Noise}. diff --git a/doc/developer.texi b/doc/developer.texi index be57f2c8a3f117d87b6988c8154b2a1324281804..5f85e00c4dad1cb6a088238549dc70bb56d531f5 100644 --- a/doc/developer.texi +++ b/doc/developer.texi @@ -3,23 +3,26 @@ @unnumbered Developer manual @table @asis @item Nonce and identity encryption -@url{http://143.53.36.235:8080/tea.htm, XTEA} +@url{http://143.53.36.235:8080/tea.htm, XTEA}. @item Data encryption -@url{http://cr.yp.to/snuffle.html, Salsa20} +@url{http://cr.yp.to/snuffle.html, Salsa20}. @item Message authentication -@url{http://cr.yp.to/mac.html, Poly1305} +@url{http://cr.yp.to/mac.html, Poly1305}. @item Password authenticated key agreement DH-A-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519} -and @url{http://ed25519.cr.yp.to/, Ed25519} +and @url{http://ed25519.cr.yp.to/, Ed25519}. @item DH elliptic-curve point encoding for public keys -@url{http://elligator.cr.yp.to/, Elligator} +@url{http://elligator.cr.yp.to/, Elligator}. @item Key derivation function for verifier generation @url{https://en.wikipedia.org/wiki/PBKDF2, PBKDF2} based on -@url{https://en.wikipedia.org/wiki/SHA-2, SHA-512} +@url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}. @item Packet overhead -26 bytes per packet +26 bytes per packet. @item Handshake overhead -4 UDP (2 from client, 2 from server) packets, 264 bytes total payload +4 UDP (2 from client, 2 from server) packets, 264 bytes total payload. +@item Entropy required +832 bits in average on client, 832 bits in average on server side per +handshake. @end table @menu diff --git a/doc/download.texi b/doc/download.texi index 4ce61a53934e3bdfbe620c603e6a80b7d4ccc440..83abb667b808e364500024d78d0e7f24ebb8463b 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -38,6 +38,10 @@ @item 3.2 @tab 174 KiB @tab @url{download/govpn-3.2.tar.xz, link} @url{download/govpn-3.2.tar.xz.sig, sign} @tab @code{388e98d6adef5ebf3431b0d48419f54d2e2064c657de67e23c669ebcf273126d} +@item 3.3 @tab 175 KiB +@tab @url{download/govpn-3.3.tar.xz, link} @url{download/govpn-3.3.tar.xz.sig, sign} +@tab @code{1834a057215324f49d6272b2beb89f1532105156f7e853eae855659992ac0c84} + @end multitable Also you can try its @ref{Contacts, .onion} version. diff --git a/doc/egd.texi b/doc/egd.texi new file mode 100644 index 0000000000000000000000000000000000000000..77d34bf1e1906cf5e618e5047069ee89c6077b6c --- /dev/null +++ b/doc/egd.texi @@ -0,0 +1,19 @@ +@node EGD +@section EGD + +Overall security mainly depends on client side: +@ref{PAKE, good passphrase} and cryprographically good pseudo random +number generator. + +Some operating systems do not have good enough quality PRNG, bad +@code{/dev/urandom}. You should use separate PRNG with them. GoVPN +communicates with them using +@url{http://egd.sourceforge.net/, Entropy Gathering Daemon} protocol. + +To switch using EGD-compatible daemons instead of @code{crypto/rand} +library you provide @code{-egd PATH} command line option, where +@code{PATH} is the domain socket. + +@example +% ./govpn-server [...] -egd /var/run/egd.sock +@end example diff --git a/doc/govpn.texi b/doc/govpn.texi index a22e85f98efe9e633c825c5a31f348f22e8b33b7..c8fa79c4fa7a05010c2fc1d0502c7112b300fd3d 100644 --- a/doc/govpn.texi +++ b/doc/govpn.texi @@ -42,6 +42,7 @@ * Developer manual:: * Contacts:: * Copying conditions:: * Thanks:: +* TODO:: @end menu @include overview.texi @@ -59,4 +60,5 @@ @insertcopying @verbatiminclude fdl.txt @include thanks.texi +@include todo.texi @bye diff --git a/doc/keywords.texi b/doc/keywords.texi index a513724a640648574277389211b6c56f4627447c..a365ce5fef119d61fdea85e55ca0f3929156f00c 100644 --- a/doc/keywords.texi +++ b/doc/keywords.texi @@ -2,11 +2,12 @@ Some keywords describing this project: encryption, authentication, key exchange, EKE, Diffie-Hellman, DH, DH-EKE, Augmented EKE, A-EKE, security, encrypted key exchange, 128-bit margin, DPI, censorship, resistance, free software, GPLv3+, reviewability, easy, simple, -Curve25519, Ed25519, Elligator, SHA-512, Salsa20, Poly1305, AEAD, XTEA, PBKDF2, -PRP, signatures, asymmetric cryptography, zero-knowledge password proof, -PAKE, password, passphrase, password authenticated key exchange, perfect -forward secrecy, PFS, MAC, nonce, verifier, rehandshake, heartbeat, -replay attack, MiTM, length hiding, timestamps hiding, noise, constant -traffic, constant packet rate, CPR, TAP, TAP, VPN, GNU, Linux, FreeBSD, -IPv6, dictionary attack, mutual authentication, simultaneous clients, -JSON, HTTP-server, statistics, PRNG, traffic analysis, Go, golang. +Curve25519, Ed25519, Elligator, SHA-512, Salsa20, Poly1305, AEAD, XTEA, +PBKDF2, PRP, signatures, asymmetric cryptography, zero-knowledge +password proof, PAKE, password, passphrase, password authenticated key +exchange, perfect forward secrecy, PFS, MAC, nonce, verifier, +rehandshake, heartbeat, replay attack, MiTM, length hiding, timestamps +hiding, noise, constant traffic, constant packet rate, CPR, EGD, +entropy, TAP, TAP, VPN, GNU, Linux, FreeBSD, IPv6, dictionary attack, +mutual authentication, simultaneous clients, JSON, HTTP-server, +statistics, PRNG, traffic analysis, Go, golang. diff --git a/doc/news.texi b/doc/news.texi index 0ef3f82365c16d0475ad20abdc497f3ac134f64f..d3a9ab13a56cb511b7afdea17373fd83efa65282 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,16 @@ @unnumbered News @table @strong +@item Release 3.4 +@itemize @bullet +@item Ability to use external @ref{EGD}-compatible PRNGs. Now you are +able to use GoVPN even on systems with the bad @code{/dev/random}, +providing higher quality entropy from external sources. +@item Removed @code{-noncediff} option. It is replaced with in-memory +storage of seen nonces, thus eliminating possible replay attacks at all +without performance degradation related to inbound packets reordering. +@end itemize + @item Release 3.3 @itemize @bullet @item Compatibility with an old GNU Make 3.x. Previously only BSD Make diff --git a/doc/noncediff.texi b/doc/noncediff.texi deleted file mode 100644 index 7ce3aa25c8b42ae94a3d95702f383b50f6965635..0000000000000000000000000000000000000000 --- a/doc/noncediff.texi +++ /dev/null @@ -1,17 +0,0 @@ -@node Nonce difference -@section Nonce difference - -GoVPN prevents replay attacks by remembering the last used nonce in -messages from the remote peer. All incoming messages must have higher -nonce number (technically it is counter), otherwise they are dropped. - -Because of UDP nature that does not guarantee packet ordering during -transmission, GoVPN will drop valid non-replayed UDP packets. That leads -to performance decrease. - -In most cases there is no need in so strict nonce boundaries and -@code{-noncediff} command line option allows to create the window of -allowable nonce differences. This is trade-off between highest security -and possible performance degradation. For example @code{-noncediff 128} -works rather well (no packet drops) with 1 Gbps link with two switches. -By default no nonce differences are allowed (highest security). diff --git a/doc/overview.texi b/doc/overview.texi index dc68994470bfa4776cb60685c1fd9545b5e4d260..23ba75d24332396b43a37418312e8b832dce69a1 100644 --- a/doc/overview.texi +++ b/doc/overview.texi @@ -11,29 +11,16 @@ goals for that daemon. Most modern widespread protocols and their implementations in software are too complex to be reviewed, analyzed and modified. -State off art cryptography technologies includes: -@url{http://cr.yp.to/snuffle.html, Salsa20} stream encryption, -@url{http://143.53.36.235:8080/tea.htm, XTEA} PRP, -@url{http://cr.yp.to/mac.html, Poly1305} message authentication, -@url{https://en.wikipedia.org/wiki/PBKDF2} password-based key derivation -function based on @url{https://en.wikipedia.org/wiki/SHA-2, SHA-512} -hash function, -@url{https://en.wikipedia.org/wiki/Encrypted_key_exchange, -Diffie-Hellman Augmented Encrypted Key Exchange} -(DH-A-EKE) powered by @url{http://cr.yp.to/ecdh.html, Curve25519}, -@url{http://ed25519.cr.yp.to/, Ed25519} signatures and -@url{http://elligator.cr.yp.to/, Elligator} curve-point encoding. -Strong -@url{https://en.wikipedia.org/wiki/Zero-knowledge_password_proof, zero-knowledge} -mutual authentication with key exchange stage is invulnerable -to man-in-the-middle attacks. +@ref{Developer manual, State off art cryptography technologies}. Strong +mutual authenticated key exchange is invulnerable to man-in-the middle +attachs. @url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy} -property guarantee that compromising of long-term authentication -pre-shared key can not lead to previously captured traffic decrypting. -Compromising of peers password file on server side won't allow attacker +property guarantees that compromising of long-term authentication keys +does not lead to previously captured traffic decrypting. +Compromising of peers password files on server side won't allow attacker to masquerade as the client, because of asymmetric @strong{verifiers} usage, resistant to dictionary attacks. Rehandshaking ensures session -keys rotation. MAC authentication with one-time keys protects against +keys rotation. One-time keys MAC authentication protects against @url{https://en.wikipedia.org/wiki/Replay_attack, replay attacks}. Server can work with several clients simultaneously. Each client is @@ -61,22 +48,26 @@ Works with @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP} network interfaces on top of UDP entirely @item @url{https://www.gnu.org/, GNU}/Linux and -@url{http://www.freebsd.org/, FreeBSD} support -@item IPv6 compatible -@item Encrypted and authenticated payload transport -@item Relatively fast handshake -@item Password-authenticated key exchange -@item Server-side password verifiers are secure against dictionary attacks -@item Attacker can not masquerade a client even with password files compromising -@item Replay attack protection -@item Perfect forward secrecy property -@item Mutual two-side authentication -@item Zero knowledge authentication -@item Built-in rehandshake and heartbeat features -@item Several simultaneous clients support -@item Per-client configuration options -@item Hiding of payload packets length with noise -@item Hiding of payload packets timestamps with constant packet rate traffic +@url{http://www.freebsd.org/, FreeBSD} support. +@item IPv6 compatible. +@item Encrypted and authenticated payload transport. +@item Relatively fast handshake. +@item Password-authenticated key exchange. +@item Server-side password verifiers are secure against dictionary +attacks. +@item Attacker can not masquerade a client even with password files +compromising. +@item Replay attack protection. +@item Perfect forward secrecy property. +@item Mutual two-side authentication. +@item Zero knowledge authentication. +@item Built-in rehandshake and heartbeat features. +@item Several simultaneous clients support. +@item Per-client configuration options. +@item Hiding of payload packets length with noise. +@item Hiding of payload packets timestamps with constant packet rate +traffic. @item Optional built-in HTTP-server for retrieving information about -known connected peers in @url{http://json.org/, JSON} format +known connected peers in @url{http://json.org/, JSON} format. +@item Compatibility with @url{http://egd.sourceforge.net/, EGD} PRNGs. @end itemize diff --git a/doc/precautions.texi b/doc/precautions.texi index cd759ea72708dd0503cb77fd01d3f226c0b6f3f5..0539822893cd83d64bec34431b6c73c588c69438 100644 --- a/doc/precautions.texi +++ b/doc/precautions.texi @@ -1,24 +1,20 @@ @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 its 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 its 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. +@enumerate +@item +We use password (passphrase) authentication, so overall security fully +depends on its strength. You @strong{should} use long, high-entropy +passphrases. Also remember to keep passphrase in temporary file and read +it securely as described in @ref{Verifier}. -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. +@item +You must @strong{never} use one key for multiple clients. -We use password (passphrase) authentication, so overall security fully -depends on its strength. So you should use long, high-entropy -passphrases. Also remember to keep passphrase on temporary file as -described in @ref{Verifier}. +@item +You must use @strong{cryptographically good} pseudo random number +generator. By default we use default @code{crypto/rand} library that +reads @code{/dev/urandom} source. Some GNU/Linux and FreeBSD systems +are rather good with this entropy source. Closed proprietary ones are +always not and you must use optional @ref{EGD} feature with them. +@end enumerate diff --git a/doc/server.texi b/doc/server.texi index c5f2eb29e8213190bbae74e3574d53b16e2acc10..989a8a8a8f8e17f0964a7e15a36d82aed7d3fa4f 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -1,8 +1,8 @@ @node Server part @section Server part -Except for common @code{-mtu}, @code{-stats} options server has the -following ones: +Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server +has the following ones: @table @code @item -bind @@ -45,10 +45,6 @@ @item timeout Optional. Contains @ref{Timeout} setting (decimal notation) in seconds. Otherwise default minute timeout will be used. - -@item noncediff -Optional. Contains allowable @ref{Nonce difference} setting (decimal -notation). @item noise Optional. Contains either "1" (enable @ref{Noise} adding), or "0". diff --git a/doc/todo.texi b/doc/todo.texi new file mode 100644 index 0000000000000000000000000000000000000000..93b95f11d10150646a4caaa11cb87b30278be906 --- /dev/null +++ b/doc/todo.texi @@ -0,0 +1,11 @@ +@node TODO +@unnumbered TODO + +@itemize +@item Ability to tunnel only specified TCP connections, without full + featured VPN solution. Similar to ssh -R. +@item Ability to work over HTTP, WebSockets and something similar. +@item Ability to noise handshake packets sizes. +@item Increase performance. +@item Randomize ports usage. +@end itemize diff --git a/doc/transport.texi b/doc/transport.texi index b76286138f63af78bb9f43bc02ec5d774d1e5716..cdbe7aecfa47365502a5ffdd0dc811d1c262d90b 100644 --- a/doc/transport.texi +++ b/doc/transport.texi @@ -34,12 +34,9 @@ Salsa20 output are used as a one-time key for @code{AUTH}. Next 256 bits of Salsa20 are ignored. All remaining output is XORed with the data, encrypting it. -To prevent replay attacks we remember latest @code{SERIAL} from the -remote peer. If received message's @code{SERIAL} is not greater that the -saved one, then drop it. Optionally, because some UDP packets can be -reordered during transmission, we can allow some window for valid -serials with the @code{-noncediff} option. @code{-noncediff 10} with -current saved serial state equals to 78 allows messages with 68…78 -serials. That time window can be used by attacker to replay packets, so -by default it equals to 1. However it can improve performance because of -rearranged UDP packets. +To prevent replay attacks we must remember received @code{SERIAL}s and +if meet one, then drop it. Basically we could just store latest number +and check if received one is greater, but because of UDP packets +reordering this can lead to valid packets dropping and overall +performance degradation. We store up to 256 seen nonces in hash +structure, in two swapping buckets. diff --git a/doc/user.texi b/doc/user.texi index 3080ce1f1ff3974939c64a568b061802936337cc..3702f8488b7c5a005e7b003cbcb9b466e7b62b93 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -10,10 +10,10 @@ IP-related network management is not touched by VPN at all. You can automate it using up and down shell scripts. @menu +* EGD:: Entropy gathering daemon * Identity:: * PAKE:: Password Authenticated Key Agreement * Timeout:: -* Nonce difference:: * MTU:: Maximum Transmission Unit * Stats:: * Noise:: @@ -24,10 +24,10 @@ * Server part:: * Example usage:: @end menu +@include egd.texi @include identity.texi @include pake.texi @include timeout.texi -@include noncediff.texi @include mtu.texi @include stats.texi @include noise.texi diff --git a/doc/verifierstruct.texi b/doc/verifierstruct.texi index b508b483536d4b9e4aebef987362d126b42abe5d..65a4e086c14d7fbf8e5fd896fc4145c05845e077 100644 --- a/doc/verifierstruct.texi +++ b/doc/verifierstruct.texi @@ -10,7 +10,7 @@ SOURCE = PBKDF2(SALT=PeerId, PASSWORD, 1<<16, SHA512) PUB, PRIV = Ed25519.Generate(SOURCE) @end verbatim -Verifier is public key of Ed25519 generated from the PBKDF2 of the -passphrase in hexadecimal encoding. @code{PeerId} is used as a 128-bit -salt. Server stores and knows only verifier. Client can compute the -whole keypair every time he makes handshake. +Verifier is @code{PUB} public key of Ed25519 generated from the PBKDF2 +of the passphrase in hexadecimal encoding. @code{PeerId} is used as a +128-bit salt. Server stores and knows only verifier. Client can compute +the whole keypair every time he makes handshake. diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index a951160a21d7ce737e01d53188b3fa4b70a272df..9b6567763ecea2e8f1787c9230f7f712aea0a78a 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -39,10 +39,10 @@ upPath = flag.String("up", "", "Path to up-script") downPath = flag.String("down", "", "Path to down-script") stats = flag.String("stats", "", "Enable stats retrieving on host:port") mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") - nonceDiff = flag.Int("noncediff", 1, "Allow nonce difference") timeoutP = flag.Int("timeout", 60, "Timeout seconds") noisy = flag.Bool("noise", false, "Enable noise appending") cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate") + egdPath = flag.String("egd", "", "Optional path to EGD socket") ) func main() { @@ -58,11 +58,15 @@ if err != nil { log.Fatalln(err) } + if *egdPath != "" { + log.Println("Using", *egdPath, "EGD") + govpn.EGDInit(*egdPath) + } + pub, priv := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath)) conf := &govpn.PeerConf{ Id: id, Timeout: time.Second * time.Duration(timeout), - Noncediff: *nonceDiff, NoiseEnable: *noisy, CPR: *cpr, DSAPub: pub, diff --git a/src/govpn/cmd/govpn-server/main.go b/src/govpn/cmd/govpn-server/main.go index 0a127efa78104147f93b0a48e72853ba910ed21b..9fb7ae1edf7d9225c6504ee1d89da65515857f18 100644 --- a/src/govpn/cmd/govpn-server/main.go +++ b/src/govpn/cmd/govpn-server/main.go @@ -37,6 +37,7 @@ bindAddr = flag.String("bind", "[::]:1194", "Bind to address") peersPath = flag.String("peers", "peers", "Path to peers keys directory") stats = flag.String("stats", "", "Enable stats retrieving on host:port") mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") + egdPath = flag.String("egd", "", "Optional path to EGD socket") ) type PeerReadyEvent struct { @@ -82,6 +83,11 @@ log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) govpn.MTU = *mtu govpn.PeersInit(*peersPath) + + if *egdPath != "" { + log.Println("Using", *egdPath, "EGD") + govpn.EGDInit(*egdPath) + } bind, err := net.ResolveUDPAddr("udp", *bindAddr) if err != nil { diff --git a/src/govpn/egd.go b/src/govpn/egd.go new file mode 100644 index 0000000000000000000000000000000000000000..08dff8318a8b1ec3d06120dee910d672f0328bba --- /dev/null +++ b/src/govpn/egd.go @@ -0,0 +1,50 @@ +/* +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 ( + "errors" + "net" +) + +var ( + egdPath string +) + +func EGDInit(path string) { + egdPath = path +} + +// Read n bytes from EGD, blocking mode. +func EGDRead(b []byte) error { + c, err := net.Dial("unix", egdPath) + if err != nil { + return err + } + defer c.Close() + c.Write([]byte{0x02, byte(len(b))}) + r, err := c.Read(b) + if err != nil { + return err + } + if r != len(b) { + return errors.New("Got less bytes than expected from EGD") + } + return nil +} diff --git a/src/govpn/handshake.go b/src/govpn/handshake.go index 7019148e899949f754435bdbad61c2042963350c..61b5b4f67dfe1f50fdb37d3c12ca032c62f8ce16 100644 --- a/src/govpn/handshake.go +++ b/src/govpn/handshake.go @@ -101,13 +101,23 @@ binary.PutUvarint(nonce, nonceCurrent+count) return nonce } +func randRead(b []byte) error { + var err error + if egdPath == "" { + _, err = rand.Read(b) + } else { + err = EGDRead(b) + } + return err +} + func dhKeypairGen() (*[32]byte, *[32]byte) { priv := new([32]byte) pub := new([32]byte) repr := new([32]byte) reprFound := false for !reprFound { - if _, err := rand.Read(priv[:]); err != nil { + if err := randRead(priv[:]); err != nil { log.Fatalln("Error reading random for DH private key:", err) } reprFound = extra25519.ScalarBaseMult(pub, repr, priv) @@ -157,7 +167,7 @@ var dhPubRepr *[32]byte state.dhPriv, dhPubRepr = dhKeypairGen() state.rNonce = new([RSize]byte) - if _, err := rand.Read(state.rNonce[:]); err != nil { + if err := randRead(state.rNonce[:]); err != nil { log.Fatalln("Error reading random for nonce:", err) } enc := make([]byte, 32) @@ -201,11 +211,11 @@ salsa20.XORKeyStream(encPub, dhPubRepr[:], h.rNonceNext(1), h.dsaPubH) // Generate R* and encrypt them h.rServer = new([RSize]byte) - if _, err := rand.Read(h.rServer[:]); err != nil { + if err := randRead(h.rServer[:]); err != nil { log.Fatalln("Error reading random for R:", err) } h.sServer = new([SSize]byte) - if _, err := rand.Read(h.sServer[:]); err != nil { + if err := randRead(h.sServer[:]); err != nil { log.Fatalln("Error reading random for S:", err) } encRs := make([]byte, RSize+SSize) @@ -287,11 +297,11 @@ copy(h.sServer[:], decRs[RSize:]) // Generate R* and signature and encrypt them h.rClient = new([RSize]byte) - if _, err := rand.Read(h.rClient[:]); err != nil { + if err := randRead(h.rClient[:]); err != nil { log.Fatalln("Error reading random for R:", err) } h.sClient = new([SSize]byte) - if _, err := rand.Read(h.sClient[:]); err != nil { + if err := randRead(h.sClient[:]); err != nil { log.Fatalln("Error reading random for S:", err) } sign := ed25519.Sign(h.Conf.DSAPriv, h.key[:]) diff --git a/src/govpn/identify.go b/src/govpn/identify.go index 663afe73599bf14b8fc40acf7e2b738c9ae6d7e6..c7675b6368b48cc62e7eef3180e8515ac1421e1b 100644 --- a/src/govpn/identify.go +++ b/src/govpn/identify.go @@ -59,7 +59,6 @@ type PeerConf struct { Id *PeerId Timeout time.Duration - Noncediff int NoiseEnable bool CPR int // This is passphrase verifier @@ -180,7 +179,7 @@ func (id *PeerId) Conf() *PeerConf { if dummyConf != nil { return dummyConf } - conf := PeerConf{Id: id, Noncediff: 1, NoiseEnable: false, CPR: 0} + conf := PeerConf{Id: id, NoiseEnable: false, CPR: 0} peerPath := path.Join(PeersPath, id.String()) verPath := path.Join(peerPath, "verifier") @@ -207,9 +206,6 @@ timeout = val } conf.Timeout = time.Second * time.Duration(timeout) - if val, err := readIntFromFile(path.Join(peerPath, "noncediff")); err == nil { - conf.Noncediff = val - } if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 { conf.NoiseEnable = true } diff --git a/src/govpn/transport.go b/src/govpn/transport.go index f46eeefb62b3aae5c9af6fbebe46705d9688e1b7..2f5a345e57a6f5c1dd9034f7885408ff886db64b 100644 --- a/src/govpn/transport.go +++ b/src/govpn/transport.go @@ -30,7 +30,8 @@ "golang.org/x/crypto/xtea" ) const ( - NonceSize = 8 + NonceSize = 8 + NonceBucketSize = 128 // S20BS is Salsa20's internal blocksize in bytes S20BS = 64 // Maximal amount of bytes transfered with single key (4 GiB) @@ -56,11 +57,14 @@ CPR int CPRCycle time.Duration `json:"-"` // Cryptography related - Key *[SSize]byte `json:"-"` - Noncediff int - NonceOur uint64 `json:"-"` - NonceRecv uint64 `json:"-"` - NonceCipher *xtea.Cipher `json:"-"` + Key *[SSize]byte `json:"-"` + NonceOur uint64 `json:"-"` + NonceRecv uint64 `json:"-"` + NonceCipher *xtea.Cipher `json:"-"` + nonceBucket0 map[uint64]struct{} + nonceBucket1 map[uint64]struct{} + nonceFound bool + nonceBucketN int32 // Timers Timeout time.Duration `json:"-"` @@ -243,23 +247,24 @@ } else { timeout = timeout / TimeoutHeartbeat } peer := Peer{ - Addr: addr, - Timeout: timeout, - Established: now, - LastPing: now, - Id: conf.Id, - NoiseEnable: noiseEnable, - CPR: conf.CPR, - CPRCycle: cprCycle, - Noncediff: conf.Noncediff, - NonceOur: uint64(conf.Noncediff + nonce), - NonceRecv: uint64(conf.Noncediff + 0), - Key: key, - NonceCipher: newNonceCipher(key), - buf: make([]byte, MTU+S20BS), - tag: new([poly1305.TagSize]byte), - keyAuth: new([SSize]byte), - nonce: make([]byte, NonceSize), + Addr: addr, + Timeout: timeout, + Established: now, + LastPing: now, + Id: conf.Id, + NoiseEnable: noiseEnable, + CPR: conf.CPR, + CPRCycle: cprCycle, + NonceOur: uint64(nonce), + NonceRecv: uint64(0), + nonceBucket0: make(map[uint64]struct{}, NonceBucketSize), + nonceBucket1: make(map[uint64]struct{}, NonceBucketSize), + Key: key, + NonceCipher: newNonceCipher(key), + buf: make([]byte, MTU+S20BS), + tag: new([poly1305.TagSize]byte), + keyAuth: new([SSize]byte), + nonce: make([]byte, NonceSize), } return &peer } @@ -286,14 +291,31 @@ ready <- struct{}{} p.FramesUnauth++ return false } + + // Check if received nonce is known to us in either of two buckets. + // If yes, then this is ignored duplicate. + // Check from the oldest bucket, as in most cases this will result + // in constant time check. + // If Bucket0 is filled, then it becomes Bucket1. p.NonceCipher.Decrypt(p.buf, udpPkt[:NonceSize]) + ready <- struct{}{} p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize]) - if int(p.NonceRecv)-p.Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-p.Noncediff { - ready <- struct{}{} + if _, p.nonceFound = p.nonceBucket1[p.NonceRecv]; p.nonceFound { + p.FramesDup++ + return false + } + if _, p.nonceFound = p.nonceBucket0[p.NonceRecv]; p.nonceFound { p.FramesDup++ return false } - ready <- struct{}{} + p.nonceBucket0[p.NonceRecv] = struct{}{} + p.nonceBucketN++ + if p.nonceBucketN == NonceBucketSize { + p.nonceBucket1 = p.nonceBucket0 + p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize) + p.nonceBucketN = 0 + } + p.FramesIn++ p.BytesIn += int64(p.size) p.LastPing = time.Now() diff --git a/src/govpn/transport_test.go b/src/govpn/transport_test.go index d12c26dacb68210e1f54ebf8d93ced4580d9eb3f..45f4d01cb6b6997bd871b7d9b6a5df29c87894d0 100644 --- a/src/govpn/transport_test.go +++ b/src/govpn/transport_test.go @@ -24,7 +24,6 @@ peerId, _ = IDDecode("ffffffffffffffffffffffffffffffff") conf = &PeerConf{ Id: peerId, Timeout: time.Second * time.Duration(TimeoutDefault), - Noncediff: 1, NoiseEnable: false, CPR: 0, } @@ -62,6 +61,8 @@ peer.EthProcess(plaintext, dummy, ready) peer = newPeer(addr, conf, 128, new([SSize]byte)) b.ResetTimer() for i := 0; i < b.N; i++ { + peer.nonceBucket0 = make(map[uint64]struct{}, 1) + peer.nonceBucket1 = make(map[uint64]struct{}, 1) if !peer.UDPProcess(ciphertext, dummy, ready) { b.Fail() }