From: Sergey Matveev Date: Wed, 6 Jan 2016 16:36:16 +0000 (+0300) Subject: Merge branch 'develop' X-Git-Tag: 5.0^0 X-Git-Url: http://www.git.cypherpunks.ru/?a=commitdiff_plain;h=9addeabf74df4ef01e4a10c9f960b362172524e8;hp=9de9bda535fa09950b8702181ba69d60efde9ac4;p=govpn.git Merge branch 'develop' Signed-off-by: Sergey Matveev --- diff --git a/README b/README index 7cbbb94..b95b958 100644 --- a/README +++ b/README @@ -4,11 +4,13 @@ be reviewable, secure, DPI/censorship-resistant, written on Go. It uses fast strong passphrase authenticated key agreement protocol with augmented zero-knowledge mutual peers authentication (PAKE DH A-EKE). Encrypted, authenticated data transport that hides message's length and -timestamps. Perfect forward secrecy property. Resistance to: offline -dictionary attacks, replay attacks, client's passphrases compromising -and dictionary attacks on the server side. Built-in heartbeating, -rehandshaking, real-time statistics. Ability to work through UDP, TCP -and HTTP proxies. IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support. +timestamps. Optional encryptionless mode, that still preserves data +confidentiality. Perfect forward secrecy property. Resistance to: +offline dictionary attacks, replay attacks, client's passphrases +compromising and dictionary attacks on the server side. Built-in +heartbeating, rehandshaking, real-time statistics. Ability to work +through UDP, TCP and HTTP proxies. IPv4/IPv6-compatibility. +GNU/Linux and FreeBSD support. GoVPN is free software: see the file COPYING for copying conditions. @@ -17,7 +19,7 @@ also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/ Please send questions regarding the use of GoVPN, bug reports and patches to govpn-devel mailing list: -https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel +https://lists.cypherpunks.ru/pipermail/govpn-devel/ Development Git source code repository currently is located here: http://git.cypherpunks.ru/cgit.cgi/govpn.git/ diff --git a/VERSION b/VERSION index bf77d54..819e07a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.2 +5.0 diff --git a/common.mk b/common.mk index 34fd0b5..a6b9e08 100644 --- a/common.mk +++ b/common.mk @@ -17,7 +17,7 @@ govpn-verifier: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" govpn/cmd/govpn-verifier bench: - cd src/govpn ; GOPATH=$(GOPATH) go test -bench . + GOPATH=$(GOPATH) go test -bench . govpn/... clean: rm -f govpn-client govpn-server govpn-verifier diff --git a/doc/Makefile b/doc/Makefile index ec5af2a..b0a891c 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,12 +2,12 @@ all: govpn.info govpn.html MAKEINFO ?= makeinfo -govpn.info: *.texi handshake.utxt - $(MAKEINFO) govpn.texi - handshake.utxt: handshake.txt plantuml -tutxt handshake.txt +govpn.info: *.texi handshake.utxt + $(MAKEINFO) govpn.texi + govpn.html: *.texi handshake.utxt rm -f govpn.html/*.html $(MAKEINFO) --html -o govpn.html govpn.texi diff --git a/doc/about.ru.texi b/doc/about.ru.texi index 31386a5..2797a63 100644 --- a/doc/about.ru.texi +++ b/doc/about.ru.texi @@ -24,6 +24,12 @@ A-EKE (Diffie-Hellman Augmented Encrypted Key Exchange)). передачи данных с 128-бит @ref{Developer, порогом безопасности} и современной криптографией. @item +Опциональный @ref{Encless, нешифрованный режим}: функции шифрования не +применяются для исходящего трафика, вместо них кодирование всё-равно +обеспечивающее конфиденциальность. Юрисдикции и суды не могут вас +вынудить выдать ключи шифрования или привлечь за использование +шифрования. +@item Цензуроустойчивые сообщения транспорта и рукопожатия: неотличимые от шума с опциональным скрытием размеров сообщений. @item @@ -60,9 +66,9 @@ A-EKE (Diffie-Hellman Augmented Encrypted Key Exchange)). статистики} о подключённых клиентах в режиме реального времени в @url{http://json.org/, JSON} формате. @item -Написан на языке @url{http://golang.org/, Go} с простым кодом, +Написан на языке @url{https://golang.org/, Go} с простым кодом, ориентированным на лёгкость чтения и анализа. @item Поддержка @url{https://www.gnu.org/, GNU}/Linux и -@url{http://www.freebsd.org/, FreeBSD}. +@url{https://www.freebsd.org/, FreeBSD}. @end itemize diff --git a/doc/about.texi b/doc/about.texi index 6e25072..03b5c89 100644 --- a/doc/about.texi +++ b/doc/about.texi @@ -21,8 +21,13 @@ Encrypted and authenticated @ref{Transport, payload transport} with 128-bit @ref{Developer, security margin} state-of-the-art cryptography. @item +Optional @ref{Encless, encryptionless mode} of operation: no encryption +functions are applied for outgoing traffic, but still confidentiality +preserving encoding. Jurisdictions and courts can not either force you +to reveal encryption keys or sue for encryption usage. +@item Censorship resistant handshake and transport messages: fully -indistinguishable from the noise with optionally hidden packets lengths. +indistinguishable from the noise with optionally hidden packets length. @item @url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy} property. @@ -55,9 +60,9 @@ Optional built-in HTTP-server for retrieving real-time @ref{Stats, statistics} information about known connected peers in @url{http://json.org/, JSON} format. @item -Written on @url{http://golang.org/, Go} programming language with +Written on @url{https://golang.org/, Go} programming language with simple code that can be read and reviewed. @item @url{https://www.gnu.org/, GNU}/Linux and -@url{http://www.freebsd.org/, FreeBSD} support. +@url{https://www.freebsd.org/, FreeBSD} support. @end itemize diff --git a/doc/client.texi b/doc/client.texi index 40f310f..88e338e 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -1,11 +1,14 @@ @node Client @section Client part -Except for common @code{-mtu}, @code{-stats}, @code{-egd} -options client has the following ones: +Except for common @code{-stats}, @code{-egd} options client has the +following ones: @table @code +@item -mtu +Expected TAP interface @ref{MTU}. + @item -proto @ref{Network, network protocol} to use. Can be either @emph{udp} (default) or @emph{tcp}. @@ -40,6 +43,9 @@ Enable @ref{Noise}. @item -cpr Set @ref{CPR} in KiB/sec. +@item -encless +Enable @ref{Encless, encryptionless mode}. + @item -up Optional path to script that will be executed after connection is established. Interface name will be given to it as a first argument. diff --git a/doc/contacts.texi b/doc/contacts.texi index 262acca..3f96135 100644 --- a/doc/contacts.texi +++ b/doc/contacts.texi @@ -2,7 +2,7 @@ @unnumbered Contacts Please send questions regarding the use of GoVPN, bug reports and patches to -@url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel, govpn-devel} +@url{https://lists.cypherpunks.ru/pipermail/govpn-devel/, govpn-devel} mailing list. Announcements also go to this mailing list. Official website is @url{http://www.cypherpunks.ru/govpn/} diff --git a/doc/cpr.texi b/doc/cpr.texi index 5256a81..5ea5717 100644 --- a/doc/cpr.texi +++ b/doc/cpr.texi @@ -1,5 +1,5 @@ @node CPR -@section Constant Packet Rate +@subsection Constant Packet Rate Constant Packet Rate is used to hide fact of underlying payload packets appearance. In this mode daemon inserts necessary dummy packets and @@ -7,4 +7,4 @@ delays other ones. This mode is turned by @code{-cpr} option, where you specify desired outgoing traffic rate in KiB/sec (kibibytes per second). This option also -@strong{forces} using of the @ref{Noise}! It is turned off by default. +@strong{forces} using of the @ref{Noise, noise}! It is turned off by default. diff --git a/doc/developer.texi b/doc/developer.texi index d9ec24f..4293f80 100644 --- a/doc/developer.texi +++ b/doc/developer.texi @@ -5,26 +5,37 @@ Pay attention how to get @ref{Sources, development source code}. @table @asis @item Nonce and identity encryption -@url{http://143.53.36.235:8080/tea.htm, XTEA}. + @url{http://www.cix.co.uk/~klockstone/xtea.pdf, 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}. + DH-A-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519} + 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 Verifier password hashing algorithm -@url{https://password-hashing.net/#argon2, Argon2d}. + @url{https://password-hashing.net/#argon2, Argon2d}. +@item Encryptionless confidentiality preserving encoding + @url{http://people.csail.mit.edu/rivest/chaffing-980701.txt, + Chaffing-and-Winnowing} (two Poly1305 MACs for each bit of message) + over 128 bits of + @url{http://theory.lcs.mit.edu/~cis/pubs/rivest/fusion.ps, + All-Or-Nothing-Transformed} (based on + @url{http://cseweb.ucsd.edu/~mihir/papers/oaep.html, OAEP} using + Salsa20 with @url{https://blake2.net/, BLAKE2b-256} based + @url{http://crypto.stanford.edu/~dabo/abstracts/saep.html, SAEP+} + checksums) data with 128-bits of feeded random. @item Packet overhead -26 bytes per packet. + 25 bytes per packet. Plus 4128 bytes and noise in encryptionless mode. @item Handshake overhead -4 UDP (2 from client, 2 from server) packets (round-trips for TCP), -264 bytes total payload. + 4 UDP (2 from client, 2 from server) packets (round-trips for TCP). + 264 bytes total payload, 20680 in encryptionless mode. @item Entropy required -832 bits in average on client, 832 bits in average on server side per -handshake. + 832 bits in average on client, 832 bits in average on server side + per handshake. 128 bits for each outgoing packet in encryptionless + mode. @end table @menu diff --git a/doc/download.texi b/doc/download.texi index 94c6a22..029c963 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -6,6 +6,10 @@ You can obtain releases source code prepared tarballs from the links below: @multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx} @headitem Version @tab Size @tab Tarball @tab SHA256 checksum +@item 4.2 @tab 233 KiB +@tab @url{download/govpn-4.2.tar.xz, link} @url{download/govpn-4.2.tar.xz.sig, sign} +@tab @code{dc2d390b9dcfb30a3612018d410b61ddf8edd82f4d9aa5ed2691b027be10ba0a} + @item 4.1 @tab 227 KiB @tab @url{download/govpn-4.1.tar.xz, link} @url{download/govpn-4.1.tar.xz.sig, sign} @tab @code{fbc7a730afe96384827dc1e1402c53165710ade5113d90531427c39172e40aca} diff --git a/doc/egd.texi b/doc/egd.texi index 62d1d87..c0006db 100644 --- a/doc/egd.texi +++ b/doc/egd.texi @@ -1,5 +1,5 @@ @node EGD -@section Entropy Gathering Daemon +@subsection Entropy Gathering Daemon Overall security mainly depends on client side: @ref{PAKE, good passphrase} and cryprographically good pseudo random diff --git a/doc/encless.texi b/doc/encless.texi new file mode 100644 index 0000000..6d44191 --- /dev/null +++ b/doc/encless.texi @@ -0,0 +1,36 @@ +@node Encless +@subsection Encryptionless mode + +Some jurisdictions can force user to reveal his encryption keys. However +they can not ask for authentication (signing) keys. So you are safe to +use authentication algorithms, but not the encryption ones. Moreover +some countries forbids usage of encryption (but again not the +authentication). + +GoVPN provides special encryptionless mode of operation. In this mode it +replaces Salsa20 function used for confidentiality with rather +well-known @url{http://people.csail.mit.edu/rivest/chaffing-980701.txt, +Chaffing-and-Winnowing} (CnW) technology. This is rather traffic and +resource hungry algorithm, so we use it after +@url{http://theory.lcs.mit.edu/~cis/pubs/rivest/fusion.ps, +All-Or-Nothing-Transformation} (based on +@url{http://cseweb.ucsd.edu/~mihir/papers/oaep.html, Optimal Asymmetric +Encryption Padding}) on the data. This is confidentiality preserving +encoding. + +AONT is just a keyless encoding of the data. CnW uses only +authentication function. Handshake additionally uses Diffie-Hellman and +signature algorithms. No encryption and steganography involved. + +In this mode each outgoing packet became larger on 4128 bytes and +@ref{Noise, noise} is forcefully enabled. So this is resource hungry mode! + +@strong{Beware}: by default packet serial numbers are still processed +through the XTEA encryption. It is not required for confidentiality and +security, but for randomizing some parts of the traffic to make it +indistinguishable from the noise, for making it more DPI-proof. It +safely can be disabled, turned off or maybe its keys even can be +revealed without security and forward secrecy loss. + +See @code{govpn/cnw} and @code{govpn/aont} packages for details of AONT +and chaffing operations. diff --git a/doc/example.texi b/doc/example.texi index 6fd62df..c2936a6 100644 --- a/doc/example.texi +++ b/doc/example.texi @@ -8,10 +8,9 @@ WiFi-reachable gateway. @item You have got @code{wlan0} NIC with 192.168.0/24 network on it. @item You want to create virtual encrypted and authenticated 172.16.0/24 network and use it as a default transport. -@item @code{wlan0} MTU is 1500, 20 bytes overhead per IPv4. So MTU for -GoVPN is 1500 - 20 - 8 = 1472. -@item During startup client and server will say that TAP interface MTU -is 1432. +@item Assume that outgoing GoVPN packets can be fragmented, so we do not +bother configuring MTU of TAP interfaces. For better performance just +lower it and check that no fragmentation of outgoing UDP packets occurs. @end itemize @strong{Install}. At first you must @ref{Installation, install} this @@ -29,6 +28,7 @@ Place the following JSON configuration entry on the server's side: "Alice": { "up": "/path/to/up.sh", + "iface": "or TAP interface name", "verifier": "$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg$KCNIqfS4DGsBTtVytamAzcISgrlEWvNxan1UfBrFu10" } @@ -39,17 +39,23 @@ Verifier was generated with: @end verbatim @strong{Prepare the server}. Add this entry to @code{peers.json} -configuration file. +configuration file: + +@verbatim +{ + "Alice": { + "iface": "tap10", + "verifier": "$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg$KCNIqfS4DGsBTtVytamAzcISgrlEWvNxan1UfBrFu10" + } +} +@end verbatim @strong{Prepare network on GNU/Linux IPv4 server}: @example server% umask 077 -server% echo "#!/bin/sh" > /path/to/up.sh -server% echo "echo tap10" >> /path/to/up.sh server% ip addr add 192.168.0.1/24 dev wlan0 server% tunctl -t tap10 -server% ip link set mtu 1432 dev tap10 server% ip addr add 172.16.0.1/24 dev tap10 server% ip link set up dev tap10 @end example @@ -57,7 +63,7 @@ server% ip link set up dev tap10 @strong{Run server daemon itself}: @example -server% govpn-server -bind 192.168.0.1:1194 -mtu 1472 +server% govpn-server -bind 192.168.0.1:1194 @end example @strong{Prepare network on GNU/Linux IPv4 client}: @@ -67,7 +73,6 @@ client% umask 066 client% utils/storekey.sh key.txt client% ip addr add 192.168.0.2/24 dev wlan0 client% tunctl -t tap10 -client% ip link set mtu 1432 dev tap10 client% ip addr add 172.16.0.2/24 dev tap10 client% ip link set up dev tap10 client% ip route add default via 172.16.0.1 @@ -79,8 +84,7 @@ client% govpn-client \ -key key.txt \ -verifier '$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg' \ -iface tap10 \ - -remote 192.168.0.1:1194 \ - -mtu 1472 + -remote 192.168.0.1:1194 @end example @strong{FreeBSD IPv6 similar client-server example}: @@ -93,7 +97,7 @@ server% govpn-server -bind "fe80::1%em0" @example client% ifconfig me0 inet6 -ifdisabled auto_linklocal client% ifconfig tap10 -client% ifconfig tap10 inet6 fc00::2/96 mtu 1412 up +client% ifconfig tap10 inet6 fc00::2/96 up client% route -6 add default fc00::1 client% govpn-client \ -key key.txt \ diff --git a/doc/faq.ru.texi b/doc/faq.ru.texi index 2023b76..10f7fda 100644 --- a/doc/faq.ru.texi +++ b/doc/faq.ru.texi @@ -67,6 +67,17 @@ Go очень легко читается, поддаётся ревью и по вид трафика от другого, то при цензуре ваше единственный вариант это заблокировать все его виды. +@item Когда я должен использовать @ref{Encless, нешифрованный режим}? +Если вы работаете под юрисдикциями где суды могут привлечь вас к +ответственности за использование шифрования или могут вынудить вас +как-либо выдать ваши ключи шифрования (хотя сессионные ключи шифрования +генерируются каждую сессию). В большинстве случаев, эти суды не могут +требовать аутентификационные ключи или ключи для ЭЦП. @strong{Не +позволяйте} названию режима вас смутить: он всё-равно обеспечивает +конфиденциальность и аутентичность передаваемых данных! Но имейте в +виду, что этот режим требователен к ресурсам и трафику и пока работает +только в TCP режиме. + @item Когда я должен использовать @ref{Noise, noise} опцию? В большинстве случаев она вам не нужна без включённого @ref{CPR, постоянного по скорости трафика} (CPR). Без CPR и шума, в diff --git a/doc/faq.texi b/doc/faq.texi index a3269f3..0ac8de8 100644 --- a/doc/faq.texi +++ b/doc/faq.texi @@ -65,6 +65,16 @@ just @code{cat /dev/urandom | nc somehost}. If you can not differentiate one kind of traffic from another, then your only option is to forbid all kinds of it. +@item When should I use @ref{Encless, encryptionless mode}? +If you are operating under jurisdiction where courts can either sue you +for encryption usage or force you to somehow reveal you encryption +keys (however new session encryption keys are generated each session). +Those courts can not demand for authentication and signing keys in most +cases. @strong{Do not} let mode's name to confuse you: it still +provides confidentiality and authenticity of transmitted data! But pay +attention that this mode is traffic and resource hungry and currently +operate only in TCP mode. + @item When should I use @ref{Noise, noise} option? In most cases you won't need it without @ref{CPR, constant packer rate} turned on. Without CPR and noise options GoVPN traffic (like TLS, IPsec, diff --git a/doc/glossary.texi b/doc/glossary.texi new file mode 100644 index 0000000..46b8e70 --- /dev/null +++ b/doc/glossary.texi @@ -0,0 +1,30 @@ +@node Glossary +@section Glossary + +@menu +* Entropy gathering daemon: EGD. +* Identity:: +* Password Authenticated Key Agreement: PAKE. +* Timeout:: +* Network transport: Network. +* Proxy:: +* Maximum Transmission Unit: MTU. +* Statistics: Stats. +* Noise:: +* Constant Packet Rate: CPR. +* Encryptionless mode: Encless. +* Verifier:: +@end menu + +@include egd.texi +@include identity.texi +@include pake.texi +@include timeout.texi +@include netproto.texi +@include proxy.texi +@include mtu.texi +@include stats.texi +@include noise.texi +@include cpr.texi +@include encless.texi +@include verifier.texi diff --git a/doc/govpn.texi b/doc/govpn.texi index ca719ea..7fab9f3 100644 --- a/doc/govpn.texi +++ b/doc/govpn.texi @@ -8,7 +8,7 @@ This manual is for GoVPN -- simple free software virtual private network daemon, aimed to be reviewable, secure, DPI/censorship-resistant, written on Go. -Copyright @copyright{} 2014-2015 @email{stargrave@@stargrave.org, Sergey Matveev} +Copyright @copyright{} 2014-2016 @email{stargrave@@stargrave.org, Sergey Matveev} @quotation Permission is granted to copy, distribute and/or modify this document @@ -24,6 +24,8 @@ A copy of the license is included in the section entitled "Copying conditions". @include about.texi +@center @strong{@ref{Tarballs, Download it}}. + @menu * About (russian): О демоне. * Frequently Asked Questions: FAQ. diff --git a/doc/handshake.texi b/doc/handshake.texi index b445985..29f59f8 100644 --- a/doc/handshake.texi +++ b/doc/handshake.texi @@ -9,7 +9,7 @@ a key. It is used to transmit identity and to mark packet as handshake message. If @ref{Noise} is enabled, then junk data is inserted before -@code{IDtag} to full up packet to MTU's size. +@code{IDtag} to fill up packet to MTU's size. @strong{Preparation stage}: @@ -93,3 +93,6 @@ symmetric encryption. @code{El()} is Elligator point encoding algorithm. has 128-bit security margin and that is why are not in use except in handshake process. @code{R*} are required for handshake randomization and two-way authentication. + +In @ref{Encless, encryptionless mode} each @code{enc()} is replaced with +AONT and chaffing function over the noised data. diff --git a/doc/handshake.txt b/doc/handshake.txt index 8804ae1..43ed4ff 100644 --- a/doc/handshake.txt +++ b/doc/handshake.txt @@ -1,9 +1,13 @@ @startuml +hide footbox participant Client participant Server +== Preparation == Client -> Client : R=rand(64bit) Client -> Client : CDHPriv=rand(256bit) + +== Interaction == Client -> Server : R, enc(H(DSAPub), R, El(CDHPub)) Server -> Server : SDHPriv=rand(256bit) Server -> Server : K=H(DH(SDHPriv, CDHPub)) @@ -15,8 +19,10 @@ Client -> Client : RC=rand(64bit); SC=rand(256bit) Client -> Server : enc(K, R+1, RS+RC+SC+Sign(DSAPriv, K)) Server -> Server : compare(RS) Server -> Server : Verify(DSAPub, Sign(DSAPriv, K), K) -Server -> Server : MasterKey=SS XOR SC Server -> Client : enc(K, R+2, RC) + +== Finalizing == Client -> Client : compare(RC) Client -> Client : MasterKey=SS XOR SC +Server -> Server : MasterKey=SS XOR SC @enduml diff --git a/doc/identity.texi b/doc/identity.texi index 3153abd..3a37790 100644 --- a/doc/identity.texi +++ b/doc/identity.texi @@ -1,5 +1,5 @@ @node Identity -@section Identity +@subsection Identity Client's identity is 128-bit string. It is not secret, so can be transmitted and stored in the clear. However handshake applies PRP on it diff --git a/doc/mtu.texi b/doc/mtu.texi index ef0db05..6ddb521 100644 --- a/doc/mtu.texi +++ b/doc/mtu.texi @@ -1,12 +1,11 @@ @node MTU -@section Maximum Transmission Unit +@subsection Maximum Transmission Unit -MTU command line argument is maximum allowable size of outgoing GoVPN's -packets. It varies and depends on your environment, so probably has to -be tuned. By default MTU equals to 1452 bytes: 40 bytes per IPv6 and 8 -bytes per UDP. +MTU option tells what maximum transmission unit is expected to get from +TAP interface. It is per-user configuration. Incoming packets of bigger +sizes (including the padding byte) will be ignored. If either +@ref{Noise, noise}, or @ref{CPR} are enabled, then all outgoing packets +are filled up to that MTU value. -Underlying TAP interface has lower MTU value -- 42 bytes smaller: 26 -bytes overhead on transport message and 14 bytes for Ethernet frame. -Client and server will print what MTU value should be used on TAP -interface. +Default MTU equals to 1514 bytes (1500 bytes of Ethernet payload, 14 +bytes of Ethernet header). diff --git a/doc/netproto.texi b/doc/netproto.texi index d033656..d57edb3 100644 --- a/doc/netproto.texi +++ b/doc/netproto.texi @@ -1,5 +1,5 @@ @node Network -@section Network transport +@subsection Network transport You can use either UDP or TCP underlying network transport protocols. diff --git a/doc/news.texi b/doc/news.texi index e5a334b..4086a79 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,19 @@ @table @strong +@item Release 5.0 +@itemize +@item New optional @ref{Encless, encryptionless mode} of operation. +Technically no encryption functions are applied for outgoing packets, so +you can not be forced to reveal your encryption keys or sued for +encryption usage. +@item @ref{MTU}s are configured on per-user basis. +@item Simplified payload padding scheme, saving one byte of data. +@item Ability to specify TAP interface name explicitly without any +up-scripts for convenience. +@item @code{govpn-verifier} utility also can use @ref{EGD}. +@end itemize + @item Release 4.2 @itemize @item Fixed non-critical bug when server may fail if up-script is not diff --git a/doc/noise.texi b/doc/noise.texi index 06ee1de..5df68a9 100644 --- a/doc/noise.texi +++ b/doc/noise.texi @@ -1,7 +1,7 @@ @node Noise -@section Noise +@subsection Noise -So-called noise is used to hide underlying payload packets lengths. +So-called noise is used to hide underlying payload packets length. Without it GoVPN provides confidentiality and authenticity of messages, but not their timestamps of appearance and sizes. diff --git a/doc/pake.texi b/doc/pake.texi index fa8a322..b80f569 100644 --- a/doc/pake.texi +++ b/doc/pake.texi @@ -1,5 +1,5 @@ @node PAKE -@section Password Authenticated Key Agreement +@subsection Password Authenticated Key Agreement Previously we used pre-shared high-entropy long-term static key for client-server authentication. Is is secure, but not convenient for some diff --git a/doc/proxy.texi b/doc/proxy.texi index 0e99035..b0f08fc 100644 --- a/doc/proxy.texi +++ b/doc/proxy.texi @@ -1,5 +1,5 @@ @node Proxy -@section Proxy +@subsection Proxy You can proxy your requests through HTTP using CONNECT method. This can help if you are only allowed to access outside world through HTTP proxy diff --git a/doc/server.texi b/doc/server.texi index a00c452..2668b1d 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -1,8 +1,8 @@ @node Server @section Server part -Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server -has the following ones: +Except for common @code{-stats}, @code{-egd} options server has the +following ones: @table @code @@ -26,23 +26,30 @@ Configuration file is JSON file with following example structure: @verbatim { "stargrave": { <-- Peer human readable name - "up": "./stargrave-up.sh", <-- up-script + "iface": "tap10", <-- OPTIONAL TAP interface name + "mtu": 1514, <-- OPTIONAL overriden MTU + "up": "./stargrave-up.sh", <-- OPTIONAL up-script "down": "./stargrave-down.sh", <-- OPTIONAL down-script "timeout": 60, <-- OPTIONAL overriden timeout "noise": true, <-- OPTIONAL noise enabler (default: false) "cpr": 64, <-- OPTIONAL constant packet rate in KiB/sec + "encless": false, <-- OPTIONAL Encryptionless mode "verifier": "$argon2d..." <-- verifier received from client }, [...] } @end verbatim -up-script executes each time connection with the client is established. -Its @emph{stdout} output must contain TAP interface name as the first -line. This script can be simple @code{echo tap10}, or maybe more -advanced like this: +At least one of either @code{iface} or @code{up} must be specified. If +you specify @code{iface}, then it will be forcefully used to determine +what TAP interface will be used. If it is not specified, then up-script +must output interface's name to stdout (first output line). + +For example up-script can be just @code{echo tap10}, or more advanced +like the following one: + @example #!/bin/sh $tap=$(ifconfig tap create) @@ -65,6 +72,7 @@ Place the following JSON configuration entry on the server's side: "Alice": { "up": "/path/to/up.sh", + "iface": "or TAP interface name", "verifier": "$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg$KCNIqfS4DGsBTtVytamAzcISgrlEWvNxan1UfBrFu10" } [...] diff --git a/doc/stats.texi b/doc/stats.texi index f6284e0..c543137 100644 --- a/doc/stats.texi +++ b/doc/stats.texi @@ -1,5 +1,5 @@ @node Stats -@section Stats +@subsection Statistics Both client and server has ability to show statistics about known connected peers. You retrieve them by downloading JSON from built-in diff --git a/doc/thanks.texi b/doc/thanks.texi index 5b9dba3..d551819 100644 --- a/doc/thanks.texi +++ b/doc/thanks.texi @@ -14,11 +14,10 @@ Thanks for contributions and suggestions to: @url{https://www.cs.columbia.edu/~smb/papers/aeke.pdf, Augmented Encrypted Key Exchange}: a Password-Based Protocol Secure Against Dictionary Attacks and Password File Compromise @copyright{} Steven M. Belloving, Michael Merrit. -@item @url{http://cr.yp.to/ecdh.html, A state-of-the-art Diffie-Hellman function}. -@item @url{http://cr.yp.to/snuffle.html, Snuffle 2005: the Salsa20 encryption function}. -@item @url{http://cr.yp.to/mac.html, A state-of-the-art message-authentication code}. -@item @url{http://ed25519.cr.yp.to/, Ed25519: high-speed high-security signatures}. @item @email{watsonbladd@@gmail.com, Watson Ladd} for suggestion of @url{http://elligator.cr.yp.to/, Elligator} encoding. @item @url{https://password-hashing.net/#argon2, PHC for Argon2}. +@item Ronald L. Rivest for its +@url{http://people.csail.mit.edu/rivest/chaffing-980701.txt, Chaffing +and Winnowing: Confidentiality without Encryption}. @end itemize diff --git a/doc/timeout.texi b/doc/timeout.texi index 0f2e2db..89dd5b0 100644 --- a/doc/timeout.texi +++ b/doc/timeout.texi @@ -1,5 +1,5 @@ @node Timeout -@section Timeout +@subsection Timeout Because of stateless UDP nature there is no way to reliably know if remote peer is alive. That is why timeouts are necessary. If no packets diff --git a/doc/transport.texi b/doc/transport.texi index bfe3501..f5ca87e 100644 --- a/doc/transport.texi +++ b/doc/transport.texi @@ -19,7 +19,7 @@ TAG || ENCRYPTED || NONCE --> PACKET +--< ENCRYPT(KEY, NONCE, PAYLOAD) ^ ^ | | - | +--< SIZE || DATA [|| NOISE] + | +--< DATA || PAD [|| ZEROS] | +--< PRP(PRP_KEY, SERIAL) @end verbatim @@ -36,7 +36,7 @@ XTEA's encryption key is the first 128-bit of Salsa20's output with established common key and zero nonce (message nonces start from 1). @verbatim -PRP_KEY = ENCRYPT(KEY, 0, 128-bit) +PRP_KEY = 128bit(ENCRYPT(KEY, 0)) @end verbatim @code{ENCRYPT} is Salsa20 stream cipher, with established session @@ -44,18 +44,27 @@ PRP_KEY = ENCRYPT(KEY, 0, 128-bit) Salsa20's output is ignored and only remaining is XORed with ther data, encrypting it. -@code{SIZE} is big-endian @emph{uint16} storing length of the -@code{DATA}. - -@code{NOISE} is optional. It is just some junk data, intended to fill up -packet to MTU size. This is useful for concealing payload packets length. +@code{DATA} is padded with @code{PAD} (0x80 byte). Optional @code{ZEROS} +may follow, to fillup packet with the junk to conceal pyload packet +length. @code{AUTH} is Poly1305 authentication function. First 256 bits of Salsa20's output are used as a one-time key for @code{AUTH}. @verbatim -AUTH_KEY = ENCRYPT(KEY, NONCE, 256 bit) +AUTH_KEY = 256bit(ENCRYPT(KEY, NONCE)) @end verbatim To prevent replay attacks we must remember received @code{SERIAL}s and drop when receiving duplicate ones. + +In @ref{Encless, encryptionless mode} this scheme is slightly different: + +@verbatim + PACKET = ENCODED || NONCE +ENCODED = ENCLESS(DATA || PAD || ZEROS) + NONCE = PRP(PRP_KEY, SERIAL) +@end verbatim + +@code{ENCLESS} is AONT and chaffing function. There is no need in +explicit separate authentication. diff --git a/doc/user.texi b/doc/user.texi index 73214da..d2118b3 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -14,33 +14,13 @@ What network performance can user expect? For example single with @emph{Go 1.5.1} gives 786 Mbps (UDP transport) throughput. @menu -* EGD:: Entropy gathering daemon -* Identity:: -* PAKE:: Password Authenticated Key Agreement -* Timeout:: -* Network transport: Network. -* Proxy:: -* MTU:: Maximum Transmission Unit -* Stats:: -* Noise:: -* CPR:: Constant Packet Rate -* Verifier:: +* Glossary:: * Client part: Client. * Server part: Server. * Example usage: Example. @end menu -@include egd.texi -@include identity.texi -@include pake.texi -@include timeout.texi -@include netproto.texi -@include proxy.texi -@include mtu.texi -@include stats.texi -@include noise.texi -@include cpr.texi -@include verifier.texi +@include glossary.texi @include client.texi @include server.texi @include example.texi diff --git a/doc/verifier.texi b/doc/verifier.texi index 74c6149..bb364d7 100644 --- a/doc/verifier.texi +++ b/doc/verifier.texi @@ -1,5 +1,5 @@ @node Verifier -@section Verifier +@subsection Verifier Verifier is created using @code{govpn-verifier} utility. But currently Go does not provide native instruments to read passwords without echoing diff --git a/src/govpn/aont/aont_test.go b/src/govpn/aont/aont_test.go new file mode 100644 index 0000000..93b7db5 --- /dev/null +++ b/src/govpn/aont/aont_test.go @@ -0,0 +1,98 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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 aont + +import ( + "bytes" + "crypto/rand" + "testing" + "testing/quick" +) + +var ( + testKey *[16]byte = new([16]byte) +) + +func init() { + rand.Read(testKey[:]) +} + +func TestSymmetric(t *testing.T) { + f := func(data []byte) bool { + encoded, err := Encode(testKey, data) + if err != nil { + return false + } + if len(encoded) != len(data)+16+32 { + return false + } + decoded, err := Decode(encoded) + if err != nil { + return false + } + return bytes.Compare(decoded, data) == 0 + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func TestSmallSize(t *testing.T) { + _, err := Decode([]byte("foobar")) + if err == nil { + t.Fail() + } +} + +func TestTampered(t *testing.T) { + f := func(data []byte, index int) bool { + if len(data) == 0 { + return true + } + encoded, _ := Encode(testKey, data) + encoded[len(data)%index] ^= byte('a') + _, err := Decode(encoded) + if err == nil { + return false + } + return true + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func BenchmarkEncode(b *testing.B) { + data := make([]byte, 128) + rand.Read(data) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Encode(testKey, data) + } +} + +func BenchmarkDecode(b *testing.B) { + data := make([]byte, 128) + rand.Read(data) + encoded, _ := Encode(testKey, data) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Decode(encoded) + } +} diff --git a/src/govpn/aont/oaep.go b/src/govpn/aont/oaep.go new file mode 100644 index 0000000..35d92fe --- /dev/null +++ b/src/govpn/aont/oaep.go @@ -0,0 +1,95 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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 . +*/ + +// All-Or-Nothing-Transform, based on OAEP. +// +// This package implements OAEP (Optimal Asymmetric Encryption Padding) +// (http://cseweb.ucsd.edu/~mihir/papers/oaep.html) +// used there as All-Or-Nothing-Transformation +// (http://theory.lcs.mit.edu/~cis/pubs/rivest/fusion.ps). +// We do not fix OAEP parts length, instead we add hash-based +// checksum like in SAEP+ +// (http://crypto.stanford.edu/~dabo/abstracts/saep.html). +// +// AONT takes 128-bit random r, data M to be encoded and produce the +// package PKG: +// +// PKG = P1 || P2 +// P1 = Salsa20(key=r, nonce=0x00, 0x00) XOR (M || BLAKE2b(r || M)) +// P2 = BLAKE2b(P1) XOR r +package aont + +import ( + "crypto/subtle" + "errors" + + "github.com/dchest/blake2b" + "golang.org/x/crypto/salsa20" +) + +const ( + HSize = 32 + RSize = 16 +) + +var ( + dummyNonce []byte = make([]byte, 8) +) + +// Encode the data, produce AONT package. Data size will be larger than +// the original one for 48 bytes. +func Encode(r *[RSize]byte, in []byte) ([]byte, error) { + out := make([]byte, len(in)+HSize+RSize) + copy(out, in) + h := blake2b.New256() + h.Write(r[:]) + h.Write(in) + copy(out[len(in):], h.Sum(nil)) + salsaKey := new([32]byte) + copy(salsaKey[:], r[:]) + salsa20.XORKeyStream(out, out, dummyNonce, salsaKey) + h.Reset() + h.Write(out[:len(in)+32]) + for i, b := range h.Sum(nil)[:RSize] { + out[len(in)+32+i] = b ^ r[i] + } + return out, nil +} + +// Decode the data from AONT package. Data size will be smaller than the +// original one for 48 bytes. +func Decode(in []byte) ([]byte, error) { + if len(in) < HSize+RSize { + return nil, errors.New("Too small input buffer") + } + h := blake2b.New256() + h.Write(in[:len(in)-RSize]) + salsaKey := new([32]byte) + for i, b := range h.Sum(nil)[:RSize] { + salsaKey[i] = b ^ in[len(in)-RSize+i] + } + h.Reset() + h.Write(salsaKey[:RSize]) + out := make([]byte, len(in)-RSize) + salsa20.XORKeyStream(out, in[:len(in)-RSize], dummyNonce, salsaKey) + h.Write(out[:len(out)-HSize]) + if subtle.ConstantTimeCompare(h.Sum(nil), out[len(out)-HSize:]) != 1 { + return nil, errors.New("Invalid checksum") + } + return out[:len(out)-HSize], nil +} diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index 09b06ca..c441405 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -41,9 +41,10 @@ var ( stats = flag.String("stats", "", "Enable stats retrieving on host:port") proxyAddr = flag.String("proxy", "", "Use HTTP proxy on host:port") proxyAuth = flag.String("proxy-auth", "", "user:password Basic proxy auth") - mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") + mtu = flag.Int("mtu", govpn.MTUDefault, "MTU of TAP interface") timeoutP = flag.Int("timeout", 60, "Timeout seconds") noisy = flag.Bool("noise", false, "Enable noise appending") + encless = flag.Bool("encless", false, "Encryptionless mode") cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate") egdPath = flag.String("egd", "", "Optional path to EGD socket") @@ -52,7 +53,7 @@ var ( timeout int firstUpCall bool = true knownPeers govpn.KnownPeers - idsCache govpn.CipherCache + idsCache *govpn.CipherCache ) func main() { @@ -61,8 +62,9 @@ func main() { var err error log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) - govpn.MTU = *mtu - + if *mtu > govpn.MTUMax { + log.Fatalln("Maximum allowable MTU is", govpn.MTUMax) + } if *egdPath != "" { log.Println("Using", *egdPath, "EGD") govpn.EGDInit(*egdPath) @@ -73,23 +75,31 @@ func main() { log.Fatalln(err) } priv := verifier.PasswordApply(govpn.StringFromFile(*keyPath)) + if *encless { + if *proto != "tcp" { + log.Fatalln("Currently encryptionless mode works only with TCP") + } + *noisy = true + } conf = &govpn.PeerConf{ Id: verifier.Id, + Iface: *ifaceName, + MTU: *mtu, Timeout: time.Second * time.Duration(timeout), Noise: *noisy, CPR: *cpr, + Encless: *encless, Verifier: verifier, DSAPriv: priv, } idsCache = govpn.NewCipherCache([]govpn.PeerId{*verifier.Id}) log.Println(govpn.VersionGet()) - tap, err = govpn.TAPListen(*ifaceName) + tap, err = govpn.TAPListen(*ifaceName, *mtu) if err != nil { log.Fatalln("Can not listen on TAP interface:", err) } - log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) statsPort, err := net.Listen("tcp", *stats) diff --git a/src/govpn/cmd/govpn-client/proxy.go b/src/govpn/cmd/govpn-client/proxy.go index bde828e..a4259f3 100644 --- a/src/govpn/cmd/govpn-client/proxy.go +++ b/src/govpn/cmd/govpn-client/proxy.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 diff --git a/src/govpn/cmd/govpn-client/tcp.go b/src/govpn/cmd/govpn-client/tcp.go index 1dd11ec..7f2af60 100644 --- a/src/govpn/cmd/govpn-client/tcp.go +++ b/src/govpn/cmd/govpn-client/tcp.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -43,7 +43,7 @@ func startTCP(timeouted, rehandshaking, termination chan struct{}) { func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan struct{}) { hs := govpn.HandshakeStart(*remoteAddr, conn, conf) - buf := make([]byte, govpn.MTU) + buf := make([]byte, 2*(govpn.EnclessEnlargeSize+*mtu)+*mtu) var n int var err error var prev int @@ -56,7 +56,7 @@ HandshakeCycle: break HandshakeCycle default: } - if prev == govpn.MTU { + if prev == len(buf) { log.Println("Timeouted waiting for the packet") timeouted <- struct{}{} break HandshakeCycle @@ -125,7 +125,7 @@ TransportCycle: break TransportCycle default: } - if prev == govpn.MTU { + if prev == len(buf) { log.Println("Timeouted waiting for the packet") timeouted <- struct{}{} break TransportCycle diff --git a/src/govpn/cmd/govpn-client/udp.go b/src/govpn/cmd/govpn-client/udp.go index 9f45f8b..581e66c 100644 --- a/src/govpn/cmd/govpn-client/udp.go +++ b/src/govpn/cmd/govpn-client/udp.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -39,7 +39,7 @@ func startUDP(timeouted, rehandshaking, termination chan struct{}) { log.Println("Connected to UDP:" + *remoteAddr) hs := govpn.HandshakeStart(*remoteAddr, conn, conf) - buf := make([]byte, govpn.MTU) + buf := make([]byte, *mtu*2) var n int var timeouts int var peer *govpn.Peer diff --git a/src/govpn/cmd/govpn-server/common.go b/src/govpn/cmd/govpn-server/common.go index 6560cfe..313fc39 100644 --- a/src/govpn/cmd/govpn-server/common.go +++ b/src/govpn/cmd/govpn-server/common.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -67,15 +67,23 @@ Processor: } func callUp(peerId *govpn.PeerId) (string, error) { - result, err := govpn.ScriptCall(confs[*peerId].Up, "") - if err != nil { - log.Println("Script", confs[*peerId].Up, "call failed", err) - return "", err + ifaceName := confs[*peerId].Iface + if confs[*peerId].Up != "" { + result, err := govpn.ScriptCall(confs[*peerId].Up, "") + if err != nil { + log.Println("Script", confs[*peerId].Up, "call failed", err) + return "", err + } + if ifaceName == "" { + sepIndex := bytes.Index(result, []byte{'\n'}) + if sepIndex < 0 { + sepIndex = len(result) + } + ifaceName = string(result[:sepIndex]) + } } - sepIndex := bytes.Index(result, []byte{'\n'}) - if sepIndex < 0 { - sepIndex = len(result) + if ifaceName == "" { + log.Println("Can not obtain interface name for", *peerId) } - ifaceName := string(result[:sepIndex]) return ifaceName, nil } diff --git a/src/govpn/cmd/govpn-server/conf.go b/src/govpn/cmd/govpn-server/conf.go index 76d56cd..fcee362 100644 --- a/src/govpn/cmd/govpn-server/conf.go +++ b/src/govpn/cmd/govpn-server/conf.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -33,7 +33,7 @@ const ( var ( confs map[govpn.PeerId]*govpn.PeerConf - idsCache govpn.CipherCache + idsCache *govpn.CipherCache ) func confRead() map[govpn.PeerId]*govpn.PeerConf { @@ -53,14 +53,27 @@ func confRead() map[govpn.PeerId]*govpn.PeerConf { if err != nil { log.Fatalln("Unable to decode the key:", err.Error(), pc.VerifierRaw) } + if pc.Encless { + pc.Noise = true + } + if pc.MTU == 0 { + pc.MTU = govpn.MTUDefault + } + if pc.MTU > govpn.MTUMax { + log.Println("MTU value", pc.MTU, "is too high, overriding to", govpn.MTUMax) + pc.MTU = govpn.MTUMax + } conf := govpn.PeerConf{ Verifier: verifier, Id: verifier.Id, Name: name, + Iface: pc.Iface, + MTU: pc.MTU, Up: pc.Up, Down: pc.Down, Noise: pc.Noise, CPR: pc.CPR, + Encless: pc.Encless, } if pc.TimeoutInt <= 0 { pc.TimeoutInt = govpn.TimeoutDefault diff --git a/src/govpn/cmd/govpn-server/main.go b/src/govpn/cmd/govpn-server/main.go index be4e7eb..9a73b25 100644 --- a/src/govpn/cmd/govpn-server/main.go +++ b/src/govpn/cmd/govpn-server/main.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -36,7 +36,6 @@ var ( confPath = flag.String("conf", "peers.json", "Path to configuration JSON") stats = flag.String("stats", "", "Enable stats retrieving on host:port") proxy = flag.String("proxy", "", "Enable HTTP proxy on host:port") - mtu = flag.Int("mtu", 1452, "MTU for outgoing packets") egdPath = flag.String("egd", "", "Optional path to EGD socket") ) @@ -46,7 +45,6 @@ func main() { log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) log.Println(govpn.VersionGet()) - govpn.MTU = *mtu confInit() knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer)) @@ -73,7 +71,6 @@ func main() { hsHeartbeat := time.Tick(timeout) go func() { <-hsHeartbeat }() - log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) statsPort, err := net.Listen("tcp", *stats) diff --git a/src/govpn/cmd/govpn-server/proxy.go b/src/govpn/cmd/govpn-server/proxy.go index 1a9f814..f1f5e09 100644 --- a/src/govpn/cmd/govpn-server/proxy.go +++ b/src/govpn/cmd/govpn-server/proxy.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 diff --git a/src/govpn/cmd/govpn-server/tcp.go b/src/govpn/cmd/govpn-server/tcp.go index dcd9606..e3458cb 100644 --- a/src/govpn/cmd/govpn-server/tcp.go +++ b/src/govpn/cmd/govpn-server/tcp.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -51,7 +51,7 @@ func startTCP() { func handleTCP(conn net.Conn) { addr := conn.RemoteAddr().String() - buf := make([]byte, govpn.MTU) + buf := make([]byte, govpn.EnclessEnlargeSize+2*govpn.MTUMax) var n int var err error var prev int @@ -61,7 +61,7 @@ func handleTCP(conn net.Conn) { var tap *govpn.TAP var conf *govpn.PeerConf for { - if prev == govpn.MTU { + if prev == len(buf) { break } conn.SetReadDeadline(time.Now().Add(time.Duration(govpn.TimeoutDefault) * time.Second)) @@ -120,7 +120,7 @@ func handleTCP(conn net.Conn) { peer = nil break } - tap, err = govpn.TAPListen(ifaceName) + tap, err = govpn.TAPListen(ifaceName, peer.MTU) if err != nil { log.Println("Unable to create TAP:", err) peer = nil @@ -157,7 +157,7 @@ func handleTCP(conn net.Conn) { prev = 0 var i int for { - if prev == govpn.MTU { + if prev == len(buf) { break } conn.SetReadDeadline(time.Now().Add(conf.Timeout)) diff --git a/src/govpn/cmd/govpn-server/udp.go b/src/govpn/cmd/govpn-server/udp.go index 0f5e555..ddbbee8 100644 --- a/src/govpn/cmd/govpn-server/udp.go +++ b/src/govpn/cmd/govpn-server/udp.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -50,7 +50,7 @@ func startUDP() { } log.Println("Listening on UDP:" + *bindAddr) - udpBufs <- make([]byte, govpn.MTU) + udpBufs <- make([]byte, govpn.MTUMax) go func() { var buf []byte var raddr *net.UDPAddr @@ -103,8 +103,8 @@ func startUDP() { hsLock.Unlock() go func() { - udpBufs <- make([]byte, govpn.MTU) - udpBufs <- make([]byte, govpn.MTU) + udpBufs <- make([]byte, govpn.MTUMax) + udpBufs <- make([]byte, govpn.MTUMax) }() peersByIdLock.RLock() addrPrev, exists = peersById[*peer.Id] @@ -139,7 +139,7 @@ func startUDP() { if err != nil { return } - tap, err := govpn.TAPListen(ifaceName) + tap, err := govpn.TAPListen(ifaceName, peer.MTU) if err != nil { log.Println("Unable to create TAP:", err) return diff --git a/src/govpn/cmd/govpn-verifier/main.go b/src/govpn/cmd/govpn-verifier/main.go index 97c4333..a7c16f0 100644 --- a/src/govpn/cmd/govpn-verifier/main.go +++ b/src/govpn/cmd/govpn-verifier/main.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -20,7 +20,6 @@ along with this program. If not, see . package main import ( - "crypto/rand" "crypto/subtle" "flag" "fmt" @@ -35,13 +34,17 @@ var ( mOpt = flag.Int("m", govpn.DefaultM, "Argon2d memory parameter (KiBs)") tOpt = flag.Int("t", govpn.DefaultT, "Argon2d iteration parameter") pOpt = flag.Int("p", govpn.DefaultP, "Argon2d parallelizm parameter") + egdPath = flag.String("egd", "", "Optional path to EGD socket") ) func main() { flag.Parse() + if *egdPath != "" { + govpn.EGDInit(*egdPath) + } if *verifier == "" { id := new([govpn.IDSize]byte) - if _, err := rand.Read(id[:]); err != nil { + if _, err := govpn.Rand.Read(id[:]); err != nil { log.Fatalln(err) } pid := govpn.PeerId(*id) diff --git a/src/govpn/cnw/cnw.go b/src/govpn/cnw/cnw.go new file mode 100644 index 0000000..931a0d1 --- /dev/null +++ b/src/govpn/cnw/cnw.go @@ -0,0 +1,157 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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 . +*/ + +// Chaffing-and-Winnowing. +// +// This package implements Chaffing-and-Winnowing technology +// (http://people.csail.mit.edu/rivest/chaffing-980701.txt). +// +// It outputs two Poly1305 MACs for each bit of input data: one valid, +// and other is not. MACs sequence is following: +// +// MAC of 1st byte, 1st bit, 0 possible value +// MAC of 1st byte, 1st bit, 1 possible value +// MAC of 1st byte, 2nd bit, 0 possible value +// MAC of 1st byte, 2nd bit, 1 possible value +// ... +// +// If bit value is 0, then first MAC is taken over "1" and the second +// one is over "0". If bit value is 1, then first is taken over "0" and +// second is over "1". +// +// Poly1305 uses 256-bit one-time key. We generate it using XSalsa20 for +// for the whole byte at once (16 MACs). +// +// MACKey1, MACKey2, ... = XSalsa20(authKey, nonce, 0x00...) +// nonce = prefix || 0x00... || big endian byte number +package cnw + +import ( + "crypto/subtle" + "encoding/binary" + "errors" + + "golang.org/x/crypto/poly1305" + "golang.org/x/crypto/salsa20" +) + +const ( + EnlargeFactor = 16 * poly1305.TagSize +) + +func zero(in []byte) { + for i := 0; i < len(in); i++ { + in[i] = 0 + } +} + +// Chaff the data. noncePrfx is 64-bit nonce. Output data will be much +// larger: 256 bytes for each input byte. +func Chaff(authKey *[32]byte, noncePrfx, in []byte) []byte { + out := make([]byte, len(in)*EnlargeFactor) + keys := make([]byte, 8*64) + nonce := make([]byte, 24) + copy(nonce[:8], noncePrfx) + var i int + var v byte + tag := new([16]byte) + macKey := new([32]byte) + for n, b := range in { + binary.BigEndian.PutUint64(nonce[16:], uint64(n)) + salsa20.XORKeyStream(keys, keys, nonce, authKey) + for i = 0; i < 8; i++ { + v = (b >> uint8(i)) & 1 + copy(macKey[:], keys[64*i:64*i+32]) + if v == 0 { + poly1305.Sum(tag, []byte("1"), macKey) + } else { + poly1305.Sum(tag, []byte("0"), macKey) + } + copy(out[16*(n*16+i*2):], tag[:]) + copy(macKey[:], keys[64*i+32:64*i+64]) + if v == 1 { + poly1305.Sum(tag, []byte("1"), macKey) + } else { + poly1305.Sum(tag, []byte("0"), macKey) + } + copy(out[16*(n*16+i*2+1):], tag[:]) + } + zero(keys) + } + zero(macKey[:]) + return out +} + +// Winnow the data. +func Winnow(authKey *[32]byte, noncePrfx, in []byte) ([]byte, error) { + if len(in)%EnlargeFactor != 0 { + return nil, errors.New("Invalid data size") + } + out := make([]byte, len(in)/EnlargeFactor) + keys := make([]byte, 8*64) + nonce := make([]byte, 24) + copy(nonce[:8], noncePrfx) + var i int + var v byte + tag := new([16]byte) + macKey := new([32]byte) + defer zero(macKey[:]) + var is01 bool + var is00 bool + var is11 bool + var is10 bool + for n := 0; n < len(out); n++ { + binary.BigEndian.PutUint64(nonce[16:], uint64(n)) + salsa20.XORKeyStream(keys, keys, nonce, authKey) + v = 0 + for i = 0; i < 8; i++ { + copy(macKey[:], keys[64*i:64*i+32]) + poly1305.Sum(tag, []byte("1"), macKey) + is01 = subtle.ConstantTimeCompare( + tag[:], + in[16*(n*16+i*2):16*(n*16+i*2+1)], + ) == 1 + poly1305.Sum(tag, []byte("0"), macKey) + is00 = subtle.ConstantTimeCompare( + tag[:], + in[16*(n*16+i*2):16*(n*16+i*2+1)], + ) == 1 + copy(macKey[:], keys[64*i+32:64*i+64]) + poly1305.Sum(tag, []byte("1"), macKey) + is11 = subtle.ConstantTimeCompare( + tag[:], + in[16*(n*16+i*2+1):16*(n*16+i*2+2)], + ) == 1 + poly1305.Sum(tag, []byte("0"), macKey) + is10 = subtle.ConstantTimeCompare( + tag[:], + in[16*(n*16+i*2+1):16*(n*16+i*2+2)], + ) == 1 + if !((is01 && is10) || (is00 && is11)) { + zero(keys) + return nil, errors.New("Invalid authenticator received") + } + if is11 { + v = v | 1< + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package cnw + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "testing" + "testing/quick" +) + +var ( + testKey *[32]byte = new([32]byte) +) + +func init() { + rand.Read(testKey[:]) +} + +func TestSymmetric(t *testing.T) { + nonce := make([]byte, 8) + f := func(data []byte, pktNum uint64) bool { + if len(data) == 0 { + return true + } + binary.BigEndian.PutUint64(nonce, pktNum) + chaffed := Chaff(testKey, nonce, data) + if len(chaffed) != len(data)*EnlargeFactor { + return false + } + decoded, err := Winnow(testKey, nonce, chaffed) + if err != nil { + return false + } + return bytes.Compare(decoded, data) == 0 + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func TestSmallSize(t *testing.T) { + _, err := Winnow(testKey, []byte("foobar12"), []byte("foobar")) + if err == nil { + t.Fail() + } +} + +func BenchmarkChaff(b *testing.B) { + nonce := make([]byte, 8) + data := make([]byte, 16) + rand.Read(nonce) + rand.Read(data) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Chaff(testKey, nonce, data) + } +} + +func BenchmarkWinnow(b *testing.B) { + nonce := make([]byte, 8) + data := make([]byte, 16) + rand.Read(nonce) + rand.Read(data) + chaffed := Chaff(testKey, nonce, data) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Winnow(testKey, nonce, chaffed) + } +} diff --git a/src/govpn/common.go b/src/govpn/common.go index 898690d..cb2f315 100644 --- a/src/govpn/common.go +++ b/src/govpn/common.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -23,15 +23,15 @@ import ( "os" "os/exec" "runtime" - "time" ) const ( TimeoutDefault = 60 + MTUMax = 9000 + MTUDefault = 1500 + 14 ) var ( - MTU int Version string ) @@ -54,7 +54,7 @@ func ScriptCall(path, ifaceName string) ([]byte, error) { } // Zero each byte. -func sliceZero(data []byte) { +func SliceZero(data []byte) { for i := 0; i < len(data); i++ { data[i] = 0 } @@ -63,10 +63,3 @@ func sliceZero(data []byte) { func VersionGet() string { return "GoVPN version " + Version + " built with " + runtime.Version() } - -func cprCycleCalculate(rate int) time.Duration { - if rate == 0 { - return time.Duration(0) - } - return time.Second / time.Duration(rate*(1<<10)/MTU) -} diff --git a/src/govpn/conf.go b/src/govpn/conf.go index d9e7481..4cb2f15 100644 --- a/src/govpn/conf.go +++ b/src/govpn/conf.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -27,12 +27,15 @@ import ( type PeerConf struct { Id *PeerId `json:"-"` Name string `json:"name"` + Iface string `json:"iface"` + MTU int `json:"mtu"` Up string `json:"up"` Down string `json:"down"` TimeoutInt int `json:"timeout"` Timeout time.Duration `json:"-"` Noise bool `json:"noise"` CPR int `json:"cpr"` + Encless bool `json:"encless"` VerifierRaw string `json:"verifier"` // This is passphrase verifier diff --git a/src/govpn/egd.go b/src/govpn/egd.go index 08dff83..04ec87b 100644 --- a/src/govpn/egd.go +++ b/src/govpn/egd.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -19,32 +19,38 @@ along with this program. If not, see . package govpn import ( + "crypto/rand" "errors" + "io" "net" ) var ( - egdPath string + Rand io.Reader = rand.Reader ) -func EGDInit(path string) { - egdPath = path -} +type EGDRand string // Read n bytes from EGD, blocking mode. -func EGDRead(b []byte) error { - c, err := net.Dial("unix", egdPath) +func (egdPath EGDRand) Read(b []byte) (int, error) { + conn, err := net.Dial("unix", string(egdPath)) if err != nil { - return err + return 0, err } - defer c.Close() - c.Write([]byte{0x02, byte(len(b))}) - r, err := c.Read(b) + conn.Write([]byte{0x02, byte(len(b))}) + read, err := conn.Read(b) if err != nil { - return err + conn.Close() + return read, err } - if r != len(b) { - return errors.New("Got less bytes than expected from EGD") + if read != len(b) { + conn.Close() + return read, errors.New("Got less bytes than expected from EGD") } - return nil + conn.Close() + return read, nil +} + +func EGDInit(path string) { + Rand = EGDRand(path) } diff --git a/src/govpn/encless.go b/src/govpn/encless.go new file mode 100644 index 0000000..21162b7 --- /dev/null +++ b/src/govpn/encless.go @@ -0,0 +1,71 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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 ( + "govpn/aont" + "govpn/cnw" +) + +const ( + EnclessEnlargeSize = aont.HSize + aont.RSize*cnw.EnlargeFactor +) + +// Confidentiality preserving (but encryptionless) encoding. +// +// It uses Chaffing-and-Winnowing technology (it is neither +// encryption nor steganography) over All-Or-Nothing-Transformed data. +// nonce is 64-bit nonce. Output data will be EnclessEnlargeSize larger. +// It also consumes 64-bits of entropy. +func EnclessEncode(authKey *[32]byte, nonce, in []byte) ([]byte, error) { + r := new([aont.RSize]byte) + var err error + if _, err = Rand.Read(r[:]); err != nil { + return nil, err + } + aonted, err := aont.Encode(r, in) + if err != nil { + return nil, err + } + out := append( + cnw.Chaff(authKey, nonce, aonted[:aont.RSize]), + aonted[aont.RSize:]..., + ) + SliceZero(aonted[:aont.RSize]) + return out, nil +} + +// Decode EnclessEncode-ed data. +func EnclessDecode(authKey *[32]byte, nonce, in []byte) ([]byte, error) { + var err error + winnowed, err := cnw.Winnow( + authKey, nonce, in[:aont.RSize*cnw.EnlargeFactor], + ) + if err != nil { + return nil, err + } + out, err := aont.Decode(append( + winnowed, in[aont.RSize*cnw.EnlargeFactor:]..., + )) + SliceZero(winnowed) + if err != nil { + return nil, err + } + return out, nil +} diff --git a/src/govpn/encless_test.go b/src/govpn/encless_test.go new file mode 100644 index 0000000..20b0df6 --- /dev/null +++ b/src/govpn/encless_test.go @@ -0,0 +1,77 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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" + "crypto/rand" + "encoding/binary" + "testing" + "testing/quick" +) + +var ( + testKey *[32]byte = new([32]byte) +) + +func init() { + rand.Read(testKey[:]) +} + +func TestEnclessSymmetric(t *testing.T) { + nonce := make([]byte, 8) + f := func(pktNum uint64, in []byte) bool { + binary.BigEndian.PutUint64(nonce, pktNum) + encoded, err := EnclessEncode(testKey, nonce, in) + if err != nil { + return false + } + decoded, err := EnclessDecode(testKey, nonce, encoded) + if err != nil { + return false + } + return bytes.Compare(decoded, in) == 0 + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func BenchmarkEnclessEncode(b *testing.B) { + nonce := make([]byte, 8) + data := make([]byte, 128) + rand.Read(nonce) + rand.Read(data) + b.ResetTimer() + for i := 0; i < b.N; i++ { + EnclessEncode(testKey, nonce, data) + } +} + +func BenchmarkEnclessDecode(b *testing.B) { + nonce := make([]byte, 8) + data := make([]byte, 128) + rand.Read(nonce) + rand.Read(data) + encoded, _ := EnclessEncode(testKey, nonce, data) + b.ResetTimer() + for i := 0; i < b.N; i++ { + EnclessDecode(testKey, nonce, encoded) + } +} diff --git a/src/govpn/govpn.go b/src/govpn/govpn.go index 996f31f..d65e72b 100644 --- a/src/govpn/govpn.go +++ b/src/govpn/govpn.go @@ -1,20 +1,2 @@ -/* -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, DPI/censorship-resistant free software VPN daemon. package govpn diff --git a/src/govpn/handshake.go b/src/govpn/handshake.go index f0a8e65..8c76967 100644 --- a/src/govpn/handshake.go +++ b/src/govpn/handshake.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -19,7 +19,6 @@ along with this program. If not, see . package govpn import ( - "crypto/rand" "crypto/subtle" "encoding/binary" "io" @@ -70,28 +69,28 @@ func HApply(data *[32]byte) { // Zero handshake's memory state func (h *Handshake) Zero() { if h.rNonce != nil { - sliceZero(h.rNonce[:]) + SliceZero(h.rNonce[:]) } if h.dhPriv != nil { - sliceZero(h.dhPriv[:]) + SliceZero(h.dhPriv[:]) } if h.key != nil { - sliceZero(h.key[:]) + SliceZero(h.key[:]) } if h.dsaPubH != nil { - sliceZero(h.dsaPubH[:]) + SliceZero(h.dsaPubH[:]) } if h.rServer != nil { - sliceZero(h.rServer[:]) + SliceZero(h.rServer[:]) } if h.rClient != nil { - sliceZero(h.rClient[:]) + SliceZero(h.rClient[:]) } if h.sServer != nil { - sliceZero(h.sServer[:]) + SliceZero(h.sServer[:]) } if h.sClient != nil { - sliceZero(h.sClient[:]) + SliceZero(h.sClient[:]) } } @@ -102,23 +101,13 @@ func (h *Handshake) rNonceNext(count uint64) []byte { 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 := randRead(priv[:]); err != nil { + if _, err := Rand.Read(priv[:]); err != nil { log.Fatalln("Error reading random for DH private key:", err) } reprFound = extra25519.ScalarBaseMult(pub, repr, priv) @@ -167,17 +156,25 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake { state.dhPriv, dhPubRepr = dhKeypairGen() state.rNonce = new([RSize]byte) - if err := randRead(state.rNonce[:]); err != nil { + if _, err := Rand.Read(state.rNonce[:]); err != nil { log.Fatalln("Error reading random for nonce:", err) } var enc []byte if conf.Noise { - enc = make([]byte, MTU-xtea.BlockSize-RSize) + enc = make([]byte, conf.MTU-xtea.BlockSize-RSize) } else { enc = make([]byte, 32) } copy(enc, dhPubRepr[:]) - salsa20.XORKeyStream(enc, enc, state.rNonce[:], state.dsaPubH) + if conf.Encless { + var err error + enc, err = EnclessEncode(state.dsaPubH, state.rNonce[:], enc) + if err != err { + panic(err) + } + } else { + salsa20.XORKeyStream(enc, enc, state.rNonce[:], state.dsaPubH) + } data := append(state.rNonce[:], enc...) data = append(data, idTag(state.Conf.Id, state.rNonce[:])...) state.conn.Write(data) @@ -191,61 +188,112 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake { // authenticated Peer is ready, then return nil. func (h *Handshake) Server(data []byte) *Peer { // R + ENC(H(DSAPub), R, El(CDHPub)) + IDtag - if h.rNonce == nil && len(data) >= 48 { - // Generate DH keypair - var dhPubRepr *[32]byte - h.dhPriv, dhPubRepr = dhKeypairGen() - + if h.rNonce == nil && ((!h.Conf.Encless && len(data) >= 48) || + (h.Conf.Encless && len(data) == EnclessEnlargeSize+h.Conf.MTU)) { h.rNonce = new([RSize]byte) copy(h.rNonce[:], data[:RSize]) - // Decrypt remote public key and compute shared key + // Decrypt remote public key cDHRepr := new([32]byte) - salsa20.XORKeyStream( - cDHRepr[:], - data[RSize:RSize+32], - h.rNonce[:], - h.dsaPubH, - ) + if h.Conf.Encless { + out, err := EnclessDecode( + h.dsaPubH, + h.rNonce[:], + data[RSize:len(data)-xtea.BlockSize], + ) + if err != nil { + log.Println("Unable to decode packet from", h.addr, err) + return nil + } + copy(cDHRepr[:], out) + } else { + salsa20.XORKeyStream( + cDHRepr[:], + data[RSize:RSize+32], + h.rNonce[:], + h.dsaPubH, + ) + } + + // Generate DH keypair + var dhPubRepr *[32]byte + h.dhPriv, dhPubRepr = dhKeypairGen() + + // Compute shared key cDH := new([32]byte) extra25519.RepresentativeToPublicKey(cDH, cDHRepr) h.key = dhKeyGen(h.dhPriv, cDH) - encPub := make([]byte, 32) - salsa20.XORKeyStream(encPub, dhPubRepr[:], h.rNonceNext(1), h.dsaPubH) + var encPub []byte + var err error + if h.Conf.Encless { + encPub = make([]byte, h.Conf.MTU) + copy(encPub, dhPubRepr[:]) + encPub, err = EnclessEncode(h.dsaPubH, h.rNonceNext(1), encPub) + if err != nil { + panic(err) + } + } else { + encPub = make([]byte, 32) + salsa20.XORKeyStream(encPub, dhPubRepr[:], h.rNonceNext(1), h.dsaPubH) + } // Generate R* and encrypt them h.rServer = new([RSize]byte) - if err := randRead(h.rServer[:]); err != nil { + if _, err = Rand.Read(h.rServer[:]); err != nil { log.Fatalln("Error reading random for R:", err) } h.sServer = new([SSize]byte) - if err := randRead(h.sServer[:]); err != nil { + if _, err = Rand.Read(h.sServer[:]); err != nil { log.Fatalln("Error reading random for S:", err) } var encRs []byte - if h.Conf.Noise { - encRs = make([]byte, MTU-len(encPub)-xtea.BlockSize) + if h.Conf.Noise && !h.Conf.Encless { + encRs = make([]byte, h.Conf.MTU-len(encPub)-xtea.BlockSize) + } else if h.Conf.Encless { + encRs = make([]byte, h.Conf.MTU-xtea.BlockSize) } else { encRs = make([]byte, RSize+SSize) } copy(encRs, append(h.rServer[:], h.sServer[:]...)) - salsa20.XORKeyStream(encRs, encRs, h.rNonce[:], h.key) + if h.Conf.Encless { + encRs, err = EnclessEncode(h.key, h.rNonce[:], encRs) + if err != nil { + panic(err) + } + } else { + salsa20.XORKeyStream(encRs, encRs, h.rNonce[:], h.key) + } // Send that to client h.conn.Write(append(encPub, append(encRs, idTag(h.Conf.Id, encPub)...)...)) h.LastPing = time.Now() } else // ENC(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag - if h.rClient == nil && len(data) >= 120 { - // Decrypted Rs compare rServer - dec := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) - salsa20.XORKeyStream( - dec, - data[:RSize+RSize+SSize+ed25519.SignatureSize], - h.rNonceNext(1), - h.key, - ) + if h.rClient == nil && ((!h.Conf.Encless && len(data) >= 120) || + (h.Conf.Encless && len(data) == EnclessEnlargeSize+h.Conf.MTU)) { + var dec []byte + var err error + if h.Conf.Encless { + dec, err = EnclessDecode( + h.key, + h.rNonceNext(1), + data[:len(data)-xtea.BlockSize], + ) + if err != nil { + log.Println("Unable to decode packet from", h.addr, err) + return nil + } + dec = dec[:RSize+RSize+SSize+ed25519.SignatureSize] + } else { + dec = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) + salsa20.XORKeyStream( + dec, + data[:RSize+RSize+SSize+ed25519.SignatureSize], + h.rNonceNext(1), + h.key, + ) + } if subtle.ConstantTimeCompare(dec[:RSize], h.rServer[:]) != 1 { log.Println("Invalid server's random number with", h.addr) return nil @@ -260,12 +308,19 @@ func (h *Handshake) Server(data []byte) *Peer { // Send final answer to client var enc []byte if h.Conf.Noise { - enc = make([]byte, MTU-xtea.BlockSize) + enc = make([]byte, h.Conf.MTU-xtea.BlockSize) } else { enc = make([]byte, RSize) } copy(enc, dec[RSize:RSize+RSize]) - salsa20.XORKeyStream(enc, enc, h.rNonceNext(2), h.key) + if h.Conf.Encless { + enc, err = EnclessEncode(h.key, h.rNonceNext(2), enc) + if err != nil { + panic(err) + } + } else { + salsa20.XORKeyStream(enc, enc, h.rNonceNext(2), h.key) + } h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...)) // Switch peer @@ -290,54 +345,120 @@ func (h *Handshake) Server(data []byte) *Peer { // authenticated Peer is ready, then return nil. func (h *Handshake) Client(data []byte) *Peer { // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag - if h.rServer == nil && h.key == nil && len(data) >= 80 { - // Decrypt remote public key and compute shared key + if h.rServer == nil && h.key == nil && + ((!h.Conf.Encless && len(data) >= 80) || + (h.Conf.Encless && len(data) == 2*(EnclessEnlargeSize+h.Conf.MTU))) { + // Decrypt remote public key sDHRepr := new([32]byte) - salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH) + var tmp []byte + var err error + if h.Conf.Encless { + tmp, err = EnclessDecode( + h.dsaPubH, + h.rNonceNext(1), + data[:len(data)/2], + ) + if err != nil { + log.Println("Unable to decode packet from", h.addr, err) + return nil + } + copy(sDHRepr[:], tmp[:32]) + } else { + salsa20.XORKeyStream( + sDHRepr[:], + data[:32], + h.rNonceNext(1), + h.dsaPubH, + ) + } + + // Compute shared key sDH := new([32]byte) extra25519.RepresentativeToPublicKey(sDH, sDHRepr) h.key = dhKeyGen(h.dhPriv, sDH) // Decrypt Rs - decRs := make([]byte, RSize+SSize) - salsa20.XORKeyStream(decRs, data[SSize:32+RSize+SSize], h.rNonce[:], h.key) h.rServer = new([RSize]byte) - copy(h.rServer[:], decRs[:RSize]) h.sServer = new([SSize]byte) - copy(h.sServer[:], decRs[RSize:]) + if h.Conf.Encless { + tmp, err = EnclessDecode( + h.key, + h.rNonce[:], + data[len(data)/2:len(data)-xtea.BlockSize], + ) + if err != nil { + log.Println("Unable to decode packet from", h.addr, err) + return nil + } + copy(h.rServer[:], tmp[:RSize]) + copy(h.sServer[:], tmp[RSize:RSize+SSize]) + } else { + decRs := make([]byte, RSize+SSize) + salsa20.XORKeyStream( + decRs, + data[SSize:SSize+RSize+SSize], + h.rNonce[:], + h.key, + ) + copy(h.rServer[:], decRs[:RSize]) + copy(h.sServer[:], decRs[RSize:]) + } // Generate R* and signature and encrypt them h.rClient = new([RSize]byte) - if err := randRead(h.rClient[:]); err != nil { + if _, err = Rand.Read(h.rClient[:]); err != nil { log.Fatalln("Error reading random for R:", err) } h.sClient = new([SSize]byte) - if err := randRead(h.sClient[:]); err != nil { + if _, err = Rand.Read(h.sClient[:]); err != nil { log.Fatalln("Error reading random for S:", err) } sign := ed25519.Sign(h.Conf.DSAPriv, h.key[:]) var enc []byte if h.Conf.Noise { - enc = make([]byte, MTU-xtea.BlockSize) + enc = make([]byte, h.Conf.MTU-xtea.BlockSize) } else { enc = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) } - copy(enc, - append(h.rServer[:], - append(h.rClient[:], - append(h.sClient[:], sign[:]...)...)...)) - salsa20.XORKeyStream(enc, enc, h.rNonceNext(1), h.key) + copy(enc, h.rServer[:]) + copy(enc[RSize:], h.rClient[:]) + copy(enc[RSize+RSize:], h.sClient[:]) + copy(enc[RSize+RSize+SSize:], sign[:]) + if h.Conf.Encless { + enc, err = EnclessEncode(h.key, h.rNonceNext(1), enc) + if err != nil { + panic(err) + } + } else { + salsa20.XORKeyStream(enc, enc, h.rNonceNext(1), h.key) + } // Send that to server h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...)) h.LastPing = time.Now() } else // ENC(K, R+2, RC) + IDtag - if h.key != nil && len(data) >= 16 { + if h.key != nil && ((!h.Conf.Encless && len(data) >= 16) || + (h.Conf.Encless && len(data) == EnclessEnlargeSize+h.Conf.MTU)) { + var err error // Decrypt rClient - dec := make([]byte, RSize) - salsa20.XORKeyStream(dec, data[:RSize], h.rNonceNext(2), h.key) + var dec []byte + if h.Conf.Encless { + dec, err = EnclessDecode( + h.key, + h.rNonceNext(2), + data[:len(data)-xtea.BlockSize], + ) + if err != nil { + log.Println("Unable to decode packet from", h.addr, err) + return nil + } + dec = dec[:RSize] + } else { + dec = make([]byte, RSize) + salsa20.XORKeyStream(dec, data[:RSize], h.rNonceNext(2), h.key) + } if subtle.ConstantTimeCompare(dec, h.rClient[:]) != 1 { log.Println("Invalid client's random number with", h.addr) return nil diff --git a/src/govpn/handshake_test.go b/src/govpn/handshake_test.go new file mode 100644 index 0000000..20bafae --- /dev/null +++ b/src/govpn/handshake_test.go @@ -0,0 +1,79 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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 ( + "testing" +) + +func TestHandshakeSymmetric(t *testing.T) { + // initial values are taken from peer_test.go's init() + v := VerifierNew(DefaultM, DefaultT, DefaultP, &testPeerId) + testConf.Verifier = v + testConf.DSAPriv = v.PasswordApply("does not matter") + hsS := NewHandshake("server", Dummy{&testCt}, testConf) + hsC := HandshakeStart("client", Dummy{&testCt}, testConf) + hsS.Server(testCt) + hsC.Client(testCt) + if hsS.Server(testCt) == nil { + t.Fail() + } + if hsC.Client(testCt) == nil { + t.Fail() + } +} + +func TestHandshakeNoiseSymmetric(t *testing.T) { + // initial values are taken from peer_test.go's init() + v := VerifierNew(DefaultM, DefaultT, DefaultP, &testPeerId) + testConf.Verifier = v + testConf.DSAPriv = v.PasswordApply("does not matter") + testConf.Noise = true + hsS := NewHandshake("server", Dummy{&testCt}, testConf) + hsC := HandshakeStart("client", Dummy{&testCt}, testConf) + hsS.Server(testCt) + hsC.Client(testCt) + if hsS.Server(testCt) == nil { + t.Fail() + } + if hsC.Client(testCt) == nil { + t.Fail() + } + testConf.Noise = false +} +func TestHandshakeEnclessSymmetric(t *testing.T) { + // initial values are taken from peer_test.go's init() + v := VerifierNew(DefaultM, DefaultT, DefaultP, &testPeerId) + testConf.Verifier = v + testConf.DSAPriv = v.PasswordApply("does not matter") + testConf.Encless = true + testConf.Noise = true + hsS := NewHandshake("server", Dummy{&testCt}, testConf) + hsC := HandshakeStart("client", Dummy{&testCt}, testConf) + hsS.Server(testCt) + hsC.Client(testCt) + if hsS.Server(testCt) == nil { + t.Fail() + } + if hsC.Client(testCt) == nil { + t.Fail() + } + testConf.Encless = false + testConf.Noise = false +} diff --git a/src/govpn/identify.go b/src/govpn/identify.go index b028b39..ed55e0d 100644 --- a/src/govpn/identify.go +++ b/src/govpn/identify.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -46,14 +46,14 @@ type CipherCache struct { l sync.RWMutex } -func NewCipherCache(peerIds []PeerId) CipherCache { +func NewCipherCache(peerIds []PeerId) *CipherCache { cc := CipherCache{c: make(map[PeerId]*xtea.Cipher, len(peerIds))} cc.Update(peerIds) - return cc + return &cc } // Remove disappeared keys, add missing ones with initialized ciphers. -func (cc CipherCache) Update(peerIds []PeerId) { +func (cc *CipherCache) Update(peerIds []PeerId) { available := make(map[PeerId]struct{}) for _, peerId := range peerIds { available[peerId] = struct{}{} @@ -81,7 +81,7 @@ func (cc CipherCache) Update(peerIds []PeerId) { // Try to find peer's identity (that equals to an encryption key) // by taking first blocksize sized bytes from data at the beginning // as plaintext and last bytes as cyphertext. -func (cc CipherCache) Find(data []byte) *PeerId { +func (cc *CipherCache) Find(data []byte) *PeerId { if len(data) < xtea.BlockSize*2 { return nil } diff --git a/src/govpn/peer.go b/src/govpn/peer.go index 45b5dd7..5ea245b 100644 --- a/src/govpn/peer.go +++ b/src/govpn/peer.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -19,8 +19,10 @@ along with this program. If not, see . package govpn import ( + "bytes" "encoding/binary" "io" + "log" "sync" "sync/atomic" "time" @@ -38,12 +40,12 @@ const ( S20BS = 64 // Maximal amount of bytes transfered with single key (4 GiB) MaxBytesPerKey int64 = 1 << 32 - // Size of packet's size mark in bytes - PktSizeSize = 2 // Heartbeat rate, relative to Timeout TimeoutHeartbeat = 4 // Minimal valid packet length - MinPktLength = 2 + 16 + 8 + MinPktLength = 1 + 16 + 8 + // Padding byte + PadByte = byte(0x80) ) func newNonceCipher(key *[32]byte) *xtea.Cipher { @@ -70,6 +72,8 @@ type Peer struct { NoiseEnable bool CPR int CPRCycle time.Duration `json:"-"` + Encless bool + MTU int // Cryptography related Key *[SSize]byte `json:"-"` @@ -108,7 +112,7 @@ type Peer struct { bufR []byte tagR *[TagSize]byte keyAuthR *[SSize]byte - pktSizeR uint16 + pktSizeR int // Transmitter BusyT sync.Mutex `json:"-"` @@ -127,11 +131,11 @@ func (p *Peer) String() string { func (p *Peer) Zero() { p.BusyT.Lock() p.BusyR.Lock() - sliceZero(p.Key[:]) - sliceZero(p.bufR) - sliceZero(p.bufT) - sliceZero(p.keyAuthR[:]) - sliceZero(p.keyAuthT[:]) + SliceZero(p.Key[:]) + SliceZero(p.bufR) + SliceZero(p.bufT) + SliceZero(p.keyAuthR[:]) + SliceZero(p.keyAuthT[:]) p.BusyT.Unlock() p.BusyR.Unlock() } @@ -141,11 +145,24 @@ func (p *Peer) NonceExpectation(buf []byte) { p.NonceCipher.Encrypt(buf, buf) } +func cprCycleCalculate(conf *PeerConf) time.Duration { + if conf.CPR == 0 { + return time.Duration(0) + } + rate := conf.CPR * 1 << 10 + if conf.Encless { + rate /= EnclessEnlargeSize + conf.MTU + } else { + rate /= conf.MTU + } + return time.Second / time.Duration(rate) +} + func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[SSize]byte) *Peer { now := time.Now() timeout := conf.Timeout - cprCycle := cprCycleCalculate(conf.CPR) + cprCycle := cprCycleCalculate(conf) noiseEnable := conf.Noise if conf.CPR > 0 { noiseEnable = true @@ -154,6 +171,11 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S timeout = timeout / TimeoutHeartbeat } + bufSize := S20BS + 2*conf.MTU + if conf.Encless { + bufSize += EnclessEnlargeSize + noiseEnable = true + } peer := Peer{ Addr: addr, Id: conf.Id, @@ -162,6 +184,8 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S NoiseEnable: noiseEnable, CPR: conf.CPR, CPRCycle: cprCycle, + Encless: conf.Encless, + MTU: conf.MTU, Key: key, NonceCipher: newNonceCipher(key), @@ -172,8 +196,8 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S Established: now, LastPing: now, - bufR: make([]byte, S20BS+MTU+NonceSize), - bufT: make([]byte, S20BS+MTU+NonceSize), + bufR: make([]byte, bufSize), + bufT: make([]byte, bufSize), tagR: new([TagSize]byte), tagT: new([TagSize]byte), keyAuthR: new([SSize]byte), @@ -195,34 +219,37 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S // that he is free to receive new packets. Encrypted and authenticated // packets will be sent to remote Peer side immediately. func (p *Peer) EthProcess(data []byte) { + if len(data) > p.MTU-1 { // 1 is for padding byte + log.Println("Padded data packet size", len(data)+1, "is bigger than MTU", p.MTU, p) + return + } p.now = time.Now() p.BusyT.Lock() // Zero size is a heartbeat packet + SliceZero(p.bufT) if len(data) == 0 { // If this heartbeat is necessary if !p.LastSent.Add(p.Timeout).Before(p.now) { p.BusyT.Unlock() return } - p.bufT[S20BS+0] = byte(0) - p.bufT[S20BS+1] = byte(0) + p.bufT[S20BS+0] = PadByte p.HeartbeatSent++ } else { // Copy payload to our internal buffer and we are ready to // accept the next one - binary.BigEndian.PutUint16( - p.bufT[S20BS:S20BS+PktSizeSize], - uint16(len(data)), - ) - copy(p.bufT[S20BS+PktSizeSize:], data) + copy(p.bufT[S20BS:], data) + p.bufT[S20BS+len(data)] = PadByte p.BytesPayloadOut += int64(len(data)) } - if p.NoiseEnable { - p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize] + if p.NoiseEnable && !p.Encless { + p.frameT = p.bufT[S20BS : S20BS+p.MTU-TagSize] + } else if p.Encless { + p.frameT = p.bufT[S20BS : S20BS+p.MTU] } else { - p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize] + p.frameT = p.bufT[S20BS : S20BS+len(data)+1+NonceSize] } p.nonceOur += 2 binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur) @@ -230,20 +257,30 @@ func (p *Peer) EthProcess(data []byte) { p.frameT[len(p.frameT)-NonceSize:], p.frameT[len(p.frameT)-NonceSize:], ) - for i := 0; i < SSize; i++ { - p.bufT[i] = byte(0) + var out []byte + if p.Encless { + var err error + out, err = EnclessEncode( + p.Key, + p.frameT[len(p.frameT)-NonceSize:], + p.frameT[:len(p.frameT)-NonceSize], + ) + if err != nil { + panic(err) + } + out = append(out, p.frameT[len(p.frameT)-NonceSize:]...) + } else { + salsa20.XORKeyStream( + p.bufT[:S20BS+len(p.frameT)-NonceSize], + p.bufT[:S20BS+len(p.frameT)-NonceSize], + p.frameT[len(p.frameT)-NonceSize:], + p.Key, + ) + copy(p.keyAuthT[:], p.bufT[:SSize]) + poly1305.Sum(p.tagT, p.frameT, p.keyAuthT) + atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize)) + out = append(p.tagT[:], p.frameT...) } - salsa20.XORKeyStream( - p.bufT[:S20BS+len(p.frameT)-NonceSize], - p.bufT[:S20BS+len(p.frameT)-NonceSize], - p.frameT[len(p.frameT)-NonceSize:], - p.Key, - ) - - copy(p.keyAuthT[:], p.bufT[:SSize]) - poly1305.Sum(p.tagT, p.frameT, p.keyAuthT) - - atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize)) p.FramesOut++ if p.CPRCycle != time.Duration(0) { @@ -255,7 +292,7 @@ func (p *Peer) EthProcess(data []byte) { } p.LastSent = p.now - p.Conn.Write(append(p.tagT[:], p.frameT...)) + p.Conn.Write(out) p.BusyT.Unlock() } @@ -263,25 +300,43 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool { if len(data) < MinPktLength { return false } - p.BusyR.Lock() - for i := 0; i < SSize; i++ { - p.bufR[i] = byte(0) - } - copy(p.bufR[S20BS:], data[TagSize:]) - salsa20.XORKeyStream( - p.bufR[:S20BS+len(data)-TagSize-NonceSize], - p.bufR[:S20BS+len(data)-TagSize-NonceSize], - data[len(data)-NonceSize:], - p.Key, - ) - - copy(p.keyAuthR[:], p.bufR[:SSize]) - copy(p.tagR[:], data[:TagSize]) - if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) { - p.FramesUnauth++ - p.BusyR.Unlock() + if !p.Encless && len(data) > len(p.bufR)-S20BS { return false } + var out []byte + p.BusyR.Lock() + if p.Encless { + var err error + out, err = EnclessDecode( + p.Key, + data[len(data)-NonceSize:], + data[:len(data)-NonceSize], + ) + if err != nil { + p.FramesUnauth++ + p.BusyR.Unlock() + return false + } + } else { + for i := 0; i < SSize; i++ { + p.bufR[i] = 0 + } + copy(p.bufR[S20BS:], data[TagSize:]) + salsa20.XORKeyStream( + p.bufR[:S20BS+len(data)-TagSize-NonceSize], + p.bufR[:S20BS+len(data)-TagSize-NonceSize], + data[len(data)-NonceSize:], + p.Key, + ) + copy(p.keyAuthR[:], p.bufR[:SSize]) + copy(p.tagR[:], data[:TagSize]) + if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) { + p.FramesUnauth++ + p.BusyR.Unlock() + return false + } + out = p.bufR[S20BS : S20BS+len(data)-TagSize-NonceSize] + } // Check if received nonce is known to us in either of two buckets. // If yes, then this is ignored duplicate. @@ -323,18 +378,26 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool { p.FramesIn++ atomic.AddInt64(&p.BytesIn, int64(len(data))) p.LastPing = time.Now() - p.pktSizeR = binary.BigEndian.Uint16(p.bufR[S20BS : S20BS+PktSizeSize]) + p.pktSizeR = bytes.LastIndexByte(out, PadByte) + if p.pktSizeR == -1 { + p.BusyR.Unlock() + return false + } + // Validate the pad + for i := p.pktSizeR + 1; i < len(out); i++ { + if out[i] != 0 { + p.BusyR.Unlock() + return false + } + } if p.pktSizeR == 0 { p.HeartbeatRecv++ p.BusyR.Unlock() return true } - if int(p.pktSizeR) > len(data)-MinPktLength { - return false - } p.BytesPayloadIn += int64(p.pktSizeR) - tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR]) + tap.Write(out[:p.pktSizeR]) p.BusyR.Unlock() return true } diff --git a/src/govpn/peer_test.go b/src/govpn/peer_test.go index c9c2647..7fa8aab 100644 --- a/src/govpn/peer_test.go +++ b/src/govpn/peer_test.go @@ -1,16 +1,35 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2016 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 ( "testing" + "testing/quick" "time" ) var ( - peer *Peer - plaintext []byte - ciphertext []byte - peerId *PeerId - conf *PeerConf + testPeer *Peer + testPt []byte + testCt []byte + testPeerId PeerId + testConf *PeerConf ) type Dummy struct { @@ -25,39 +44,94 @@ func (d Dummy) Write(b []byte) (int, error) { } func init() { - MTU = 1500 id := new([IDSize]byte) - peerId := PeerId(*id) - conf = &PeerConf{ - Id: &peerId, + testPeerId = PeerId(*id) + testConf = &PeerConf{ + Id: &testPeerId, + MTU: MTUDefault, Timeout: time.Second * time.Duration(TimeoutDefault), - Noise: false, - CPR: 0, } - peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte)) - plaintext = make([]byte, 789) + testPeer = newPeer(true, "foo", Dummy{&testCt}, testConf, new([SSize]byte)) + testPt = make([]byte, 789) +} + +func TestTransportSymmetric(t *testing.T) { + peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte)) + f := func(payload []byte) bool { + if len(payload) == 0 { + return true + } + testPeer.EthProcess(payload) + return peerd.PktProcess(testCt, Dummy{nil}, true) + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func TestTransportSymmetricNoise(t *testing.T) { + peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte)) + testPeer.NoiseEnable = true + peerd.NoiseEnable = true + f := func(payload []byte) bool { + if len(payload) == 0 { + return true + } + testPeer.EthProcess(payload) + return peerd.PktProcess(testCt, Dummy{nil}, true) + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } + testPeer.NoiseEnable = true +} + +func TestTransportSymmetricEncless(t *testing.T) { + peerd := newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte)) + testPeer.Encless = true + testPeer.NoiseEnable = true + peerd.Encless = true + peerd.NoiseEnable = true + f := func(payload []byte) bool { + if len(payload) == 0 { + return true + } + testPeer.EthProcess(payload) + return peerd.PktProcess(testCt, Dummy{nil}, true) + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } + testPeer.NoiseEnable = false + testPeer.Encless = false } func BenchmarkEnc(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - peer.EthProcess(plaintext) + testPeer.EthProcess(testPt) } } func BenchmarkDec(b *testing.B) { - peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte)) - peer.EthProcess(plaintext) - peer = newPeer(true, "foo", Dummy{nil}, conf, new([SSize]byte)) - orig := make([]byte, len(ciphertext)) - copy(orig, ciphertext) + testPeer = newPeer(true, "foo", Dummy{&testCt}, testConf, new([SSize]byte)) + testPeer.EthProcess(testPt) + testPeer = newPeer(true, "foo", Dummy{nil}, testConf, new([SSize]byte)) + orig := make([]byte, len(testCt)) + copy(orig, testCt) b.ResetTimer() for i := 0; i < b.N; i++ { - peer.nonceBucket0 = make(map[uint64]struct{}, 1) - peer.nonceBucket1 = make(map[uint64]struct{}, 1) - copy(ciphertext, orig) - if !peer.PktProcess(ciphertext, Dummy{nil}, true) { + testPeer.nonceBucket0 = make(map[uint64]struct{}, 1) + testPeer.nonceBucket1 = make(map[uint64]struct{}, 1) + copy(testCt, orig) + if !testPeer.PktProcess(testCt, Dummy{nil}, true) { b.Fail() } } } + +func TestTransportBigger(t *testing.T) { + tmp := make([]byte, MTUMax*4) + Rand.Read(tmp) + testPeer.PktProcess(tmp, Dummy{nil}, true) +} diff --git a/src/govpn/stats.go b/src/govpn/stats.go index 7cdb3b7..1d95674 100644 --- a/src/govpn/stats.go +++ b/src/govpn/stats.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 diff --git a/src/govpn/tap.go b/src/govpn/tap.go index 9c999b4..6e545b4 100644 --- a/src/govpn/tap.go +++ b/src/govpn/tap.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -20,12 +20,6 @@ package govpn import ( "io" - - "golang.org/x/crypto/poly1305" -) - -const ( - EtherSize = 14 ) type TAP struct { @@ -41,14 +35,7 @@ var ( taps = make(map[string]*TAP) ) -// Return maximal acceptable TAP interface MTU. This is daemon's MTU -// minus nonce, MAC, packet size mark and Ethernet header sizes. -func TAPMaxMTU() int { - return MTU - poly1305.TagSize - NonceSize - PktSizeSize - EtherSize -} - -func NewTAP(ifaceName string) (*TAP, error) { - maxIfacePktSize := TAPMaxMTU() + EtherSize +func NewTAP(ifaceName string, mtu int) (*TAP, error) { tapRaw, err := newTAPer(ifaceName) if err != nil { return nil, err @@ -56,8 +43,8 @@ func NewTAP(ifaceName string) (*TAP, error) { tap := TAP{ Name: ifaceName, dev: tapRaw, - buf0: make([]byte, maxIfacePktSize), - buf1: make([]byte, maxIfacePktSize), + buf0: make([]byte, mtu), + buf1: make([]byte, mtu), Sink: make(chan []byte), } go func() { @@ -85,12 +72,12 @@ func (t *TAP) Write(data []byte) (n int, err error) { return t.dev.Write(data) } -func TAPListen(ifaceName string) (*TAP, error) { +func TAPListen(ifaceName string, mtu int) (*TAP, error) { tap, exists := taps[ifaceName] if exists { return tap, nil } - tap, err := NewTAP(ifaceName) + tap, err := NewTAP(ifaceName, mtu) if err != nil { return nil, err } diff --git a/src/govpn/tap_freebsd.go b/src/govpn/tap_freebsd.go index 6763030..157c583 100644 --- a/src/govpn/tap_freebsd.go +++ b/src/govpn/tap_freebsd.go @@ -2,7 +2,7 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 Sergey Matveev */ package govpn diff --git a/src/govpn/tap_linux.go b/src/govpn/tap_linux.go index 71ea90d..fb9df56 100644 --- a/src/govpn/tap_linux.go +++ b/src/govpn/tap_linux.go @@ -2,7 +2,7 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 Sergey Matveev */ package govpn diff --git a/src/govpn/verifier.go b/src/govpn/verifier.go index fe09653..ba51846 100644 --- a/src/govpn/verifier.go +++ b/src/govpn/verifier.go @@ -1,6 +1,6 @@ /* GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2016 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 @@ -58,7 +58,7 @@ func (v *Verifier) PasswordApply(password string) *[ed25519.PrivateKeySize]byte if err != nil { log.Fatalln("Unable to apply Argon2d", err) } - defer sliceZero(r) + defer SliceZero(r) src := bytes.NewBuffer(r) pub, prv, err := ed25519.GenerateKey(src) if err != nil { diff --git a/utils/makedist.sh b/utils/makedist.sh index 89bd8c8..cf819ec 100755 --- a/utils/makedist.sh +++ b/utils/makedist.sh @@ -22,7 +22,6 @@ golang.org/x/crypto/LICENSE golang.org/x/crypto/PATENTS golang.org/x/crypto/README golang.org/x/crypto/curve25519 -golang.org/x/crypto/pbkdf2 golang.org/x/crypto/poly1305 golang.org/x/crypto/salsa20 golang.org/x/crypto/xtea @@ -73,11 +72,13 @@ be reviewable, secure, DPI/censorship-resistant, written on Go. It uses fast strong passphrase authenticated key agreement protocol with augmented zero-knowledge mutual peers authentication (PAKE DH A-EKE). Encrypted, authenticated data transport that hides message's length and -timestamps. Perfect forward secrecy property. Resistance to: offline -dictionary attacks, replay attacks, client's passphrases compromising -and dictionary attacks on the server side. Built-in heartbeating, -rehandshaking, real-time statistics. Ability to work through UDP, TCP -and HTTP proxies. IPv4/IPv6-compatibility. GNU/Linux and FreeBSD support. +timestamps. Optional encryptionless mode, that still preserves data +confidentiality. Perfect forward secrecy property. Resistance to: +offline dictionary attacks, replay attacks, client's passphrases +compromising and dictionary attacks on the server side. Built-in +heartbeating, rehandshaking, real-time statistics. Ability to work +through UDP, TCP and HTTP proxies. IPv4/IPv6-compatibility. +GNU/Linux and FreeBSD support. ----------------8<-----------------8<-----------------8<---------------- @@ -87,7 +88,7 @@ $(git cat-file -p $release | sed -n '6,/^.*BEGIN/p' | sed '$d') ----------------8<-----------------8<-----------------8<---------------- -GoVPN's home page is: http://govpn.info -> http://www.cypherpunks.ru/govpn/ +GoVPN's home page is: http://www.cypherpunks.ru/govpn/ (http://govpn.info/) also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/ Source code and its signature for that version can be found here: @@ -100,7 +101,7 @@ GPG key ID: 0xF2F59045FFE2F4A1 GoVPN release signing key Fingerprint: D269 9B73 3C41 2068 D8DA 656E F2F5 9045 FFE2 F4A1 Please send questions regarding the use of GoVPN, bug reports and patches -to mailing list: https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/ +to mailing list: https://lists.cypherpunks.ru/pipermail/govpn-devel/ EOF cat < http://www.cypherpunks.ru/govpn/ -Коротко о демоне: http://www.cypherpunks.ru/govpn/About-RU.html +Домашняя страница GoVPN: http://www.cypherpunks.ru/govpn/ (http://govpn.info/) +Коротко о демоне: http://www.cypherpunks.ru/govpn/O-demone.html также доступна как скрытый сервис Tor: http://vabu56j2ep2rwv3b.onion/govpn/ Исходный код и его подпись для этой версии находится здесь: @@ -147,5 +149,5 @@ SHA256 хэш: $hash Пожалуйста все вопросы касающиеся использования GoVPN, отчёты об ошибках и патчи отправляйте в govpn-devel почтовую рассылку: -https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/ +https://lists.cypherpunks.ru/pipermail/govpn-devel/ EOF diff --git a/utils/newclient.sh b/utils/newclient.sh index e0d8c85..a57becb 100755 --- a/utils/newclient.sh +++ b/utils/newclient.sh @@ -1,5 +1,7 @@ #!/bin/sh -e +PATH=$PATH:. + [ -n "$1" ] || { cat <