From: Sergey Matveev Date: Sat, 19 Sep 2015 20:26:49 +0000 (+0300) Subject: Merge branch 'develop' X-Git-Tag: 4.0^0 X-Git-Url: http://www.git.cypherpunks.ru/?a=commitdiff_plain;h=4a58ee4c1365408452e03535ca68146aa9cf3540;hp=296f3d2e9197b76209a4e86ed8856aea9195d94c;p=govpn.git Merge branch 'develop' Signed-off-by: Sergey Matveev --- diff --git a/README b/README index 6173be4..7cbbb94 100644 --- a/README +++ b/README @@ -1,28 +1,25 @@ -GoVPN is simple secure free software virtual private network daemon, -aimed to be reviewable, secure, DPI/censorship-resistant, written on Go. +GoVPN is simple free software virtual private network daemon, aimed to +be reviewable, secure, DPI/censorship-resistant, written on Go. -It uses fast strong password authenticated key agreement protocol with -augmented 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 passwords 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. +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. GoVPN is free software: see the file COPYING for copying conditions. -Home page: http://www.cypherpunks.ru/govpn/ +Home page: http://govpn.info/ -> http://www.cypherpunks.ru/govpn/ also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/ -Please send all questions, bug reports and patches to -govpn-devel@lists.cypherpunks.ru mailing list. -Also it is used for announcements. Either visit -https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel for -subscription and archive access information, or send email with the -subject "subscribe" to govpn-devel-request@lists.cypherpunks.ru. +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 Development Git source code repository currently is located here: http://git.cypherpunks.ru/cgit.cgi/govpn.git/ -For futher information please read either doc/govpn.info or doc/govpn.texi. +For further information please read either doc/govpn.info or doc/govpn.texi. diff --git a/README.RU b/README.RU new file mode 100644 index 0000000..d6f5bfa --- /dev/null +++ b/README.RU @@ -0,0 +1,31 @@ +GoVPN это простой демон виртуальных частных сетей, код которого нацелен +на лёгкость чтения и анализа, безопасность, устойчивость к DPI/цензуре, +написан на Go и является свободным программным обеспечением. + +Он использует быстрый сильный аутентифицируемый по парольной фразе +несбалансированный протокол согласования ключей с двусторонней +аутентификацией сторон (PAKE DH A-EKE). Зашифрованный, аутентифицируемый +транспортный протокол передачи данных, скрывающий длины сообщений и их +временные характеристики. Свойство совершенной прямой секретности. +Устойчивость к: внесетевым (offline) атакам по словарю, атакам +повторного воспроизведения (replay), компрометации клиентских парольных +фраз на стороне сервера. Встроенные функции сердцебиения (heartbeat), +пересогласования ключей, статистика реального времени. Возможность +работы поверх UDP, TCP и HTTP прокси. Совместимость с IPv4 и IPv6. +Поддержка GNU/Linux и FreeBSD. + +GoVPN это свободное программное обеспечением: условия распространения +находятся в файле COPYING. + +Домашняя страница: http://govpn.info/ -> http://www.cypherpunks.ru/govpn/ +также доступна как скрытый сервис Tor: http://vabu56j2ep2rwv3b.onion/govpn/ + +Пожалуйста все вопросы касающиеся использования GoVPN, отчёты об ошибках +и патчи отправляйте в govpn-devel почтовую рассылку: +https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/ + +Исходный код для разработчика находится в Git репозитории: +http://git.cypherpunks.ru/cgit.cgi/govpn.git/ + +За большей информацией пожалуйста прочтите или doc/govpn.info или +doc/govpn.texi. diff --git a/VERSION b/VERSION index 5a95802..5186d07 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.5 +4.0 diff --git a/common.mk b/common.mk index 2ae9bb1..34fd0b5 100644 --- a/common.mk +++ b/common.mk @@ -36,8 +36,8 @@ install: all doc cp -f utils/newclient.sh utils/storekey.sh $(SHAREDIR) chmod 755 $(SHAREDIR)/newclient.sh $(SHAREDIR)/storekey.sh mkdir -p $(DOCDIR) - cp -f -L AUTHORS INSTALL NEWS README THANKS $(DOCDIR) - chmod 644 $(DOCDIR)/AUTHORS $(DOCDIR)/INSTALL $(DOCDIR)/NEWS $(DOCDIR)/README $(DOCDIR)/THANKS + cp -f -L AUTHORS INSTALL NEWS README README.RU THANKS $(DOCDIR) + chmod 644 $(DOCDIR)/* install-strip: install strip $(BINDIR)/govpn-client $(BINDIR)/govpn-server $(BINDIR)/govpn-verifier diff --git a/doc/about.ru.texi b/doc/about.ru.texi new file mode 100644 index 0000000..d70318f --- /dev/null +++ b/doc/about.ru.texi @@ -0,0 +1,60 @@ +@node About RU +@unnumbered Подробнее о демоне GoVPN + +GoVPN это простой демон виртуальных частных сетей, код которого нацелен +на лёгкость чтения и анализа, безопасность, устойчивость к DPI/цензуре. + +@itemize @bullet +@item +Свободное программное обеспечение, копилефт: лицензировано под условиями +@url{https://www.gnu.org/licenses/gpl-3.0.ru.html, GPLv3+}. +@item +Быстрый сильный аутентифицируемый по парольной фразе несбалансированный +протокол согласования ключей с двусторонней аутентификацией сторон и +нулевым неразглашением (PAKE DH A-EKE (Diffie-Hellman Augmented +Encrypted Key Exchange)). +@item +Несбалансированные аутентификационные токены устойчивые к внесетевым +(offline) атакам по словарю. Злоумышленник не может замаскироваться под +клиента даже скомпрометировав базу данных токенов сервера. +@item +Зашифрованный и аутентифицируемый транспортный протокол передачи данных +с 128-бит порогом безопасности и современной криптографией. +@item +Цензуроустойчивые сообщения транспорта и рукопожатия: неотличимые от +шума с опциональным скрытием размеров сообщений. +@item +Свойство совершенной прямой секретности (perfect forward secrecy). +@item +Защита от атак повторного воспроизведения (replay) (используя +одноразовые MAC). +@item +Встроенные функции пересогласования ключей (ротация сессионных ключей) и +сердцебиения (heartbeat). +@item +Возможность скрывать размеры пакетов путём зашумления данных. +@item +Возможность скрывать временные характеристики полезной нагрузки путём +постоянного по скорости трафика. +@item +Совместимость с @url{http://egd.sourceforge.net/, EGD} (демон сборки +энтропии) генераторами псевдослучайных чисел. +@item +Поддержка нескольких клиентов одновременно с специфичной для каждого +конфигурацией. Клиенты имеют заранее установленный идентификатор, +невидимый третьим лицам (они анонимны для них). +@item +Использует TAP низлежащие сетевые интерфейсы. +@item +Может работать поверх UDP, TCP и HTTP прокси для доступа к серверу. +@item +Полностью IPv4 и IPv6 совместимый. +@item +Опциональный встроенный HTTP-сервер для получения статистики о +подключённых клиентах в режиме реального времени в JSON формате. +@item +Написан на языке Go с простым кодом, ориентированным на лёгкость чтения +и анализа. +@item +Поддержка GNU/Linux и FreeBSD. +@end itemize diff --git a/doc/about.texi b/doc/about.texi new file mode 100644 index 0000000..d205a10 --- /dev/null +++ b/doc/about.texi @@ -0,0 +1,62 @@ +GoVPN is simple free software virtual private network daemon, +aimed to be reviewable, secure and +@url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}/censorship-resistant. + +@itemize @bullet +@item +Copylefted free software: licenced under +@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}. +@item +Fast strong @ref{PAKE, passphrase authenticated} augmented +@ref{Handshake, key agreement protocol} with zero-knowledge mutual peers +authentication (PAKE DH A-EKE (Diffie-Hellman Augmented Encrypted Key +Exchange)). +@item +@ref{Verifier structure, Augmented authentication tokens} resistant to +offline dictionary attacks. An attacker can not masquerade a client +even with server passphrase verifiers compromising. +@item +Encrypted and authenticated @ref{Transport, payload transport} +with 128-bit @ref{Developer, security margin} state-of-the-art +cryptography. +@item +Censorship resistant handshake and transport messages: fully +indistinguishable from the noise with optionally hidden packets lengths. +@item +@url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy} +property. +@item +Replay attack protection (using one-time MACs). +@item +Built-in rehandshake (session key rotation) and heartbeat features. +@item +Ability to hide packets length with the @ref{Noise, noise} data. +@item +Ability to hide payload timestamps with @ref{CPR, constant packet rate} +traffic. +@item +Compatible with @url{http://egd.sourceforge.net/, EGD} (entropy +gathering daemon) PRNGs. +@item +Several simultaneous clients support with per-client configuration +options. Clients have pre-established @ref{Identity, identity} invisible +for third-parties (they are anonymous). +@item +Uses @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP} +underlying network interfaces. +@item +Can use @ref{Network, UDP and TCP} or HTTP @ref{Proxy, proxies} +for accessing the server. +@item +Fully IPv4 and IPv6 compatible. +@item +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 +simple code that can be read and reviewed. +@item +@url{https://www.gnu.org/, GNU}/Linux and +@url{http://www.freebsd.org/, FreeBSD} support. +@end itemize diff --git a/doc/client.texi b/doc/client.texi index ec816c4..bab6a47 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -1,4 +1,4 @@ -@node Client part +@node Client @section Client part Except for common @code{-mtu}, @code{-stats}, @code{-egd} @@ -7,7 +7,8 @@ options client has the following ones: @table @code @item -proto -@ref{Network transport} to use. Can be either @emph{udp} or @emph{tcp}. +@ref{Network, network protocol} to use. Can be either @emph{udp} +(default) or @emph{tcp}. @item -proxy Use specified @emph{host:port} @ref{Proxy} server for accessing remote @@ -37,7 +38,7 @@ how to enter passphrase from stdin silently and store it in the file. Enable @ref{Noise}. @item -cpr -Enable @ref{CPR} in KiB/sec. +Set @ref{CPR} in KiB/sec. @item -up Optional path to script that will be executed after connection is diff --git a/doc/contacts.texi b/doc/contacts.texi index 1154d8d..262acca 100644 --- a/doc/contacts.texi +++ b/doc/contacts.texi @@ -1,15 +1,11 @@ @node Contacts @unnumbered Contacts -Please send all questions, bug reports and patches to to -@email{govpn-devel@@lists.cypherpunks.ru} mailing list. -Announcements go to this mailing list too. +Please send questions regarding the use of GoVPN, bug reports and patches to +@url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel, govpn-devel} +mailing list. Announcements also go to this mailing list. -Either visit @url{https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel} -for information about subscription options and archived messages access, or -send email with the subject @code{subscribe} to -@email{govpn-devel-request@@lists.cypherpunks.ru}. - -Official website is @url{http://www.cypherpunks.ru/govpn/}, also available -as @url{https://www.torproject.org/, Tor} hidden service: +Official website is @url{http://www.cypherpunks.ru/govpn/} +(with @url{http://govpn.info/} alias), also available as +@url{https://www.torproject.org/, Tor} hidden service: @url{http://vabu56j2ep2rwv3b.onion/govpn/}. diff --git a/doc/cpr.texi b/doc/cpr.texi index cd7b48a..5256a81 100644 --- a/doc/cpr.texi +++ b/doc/cpr.texi @@ -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 -forces using of the @ref{Noise}! It is turned off by default. +@strong{forces} using of the @ref{Noise}! It is turned off by default. diff --git a/doc/developer.texi b/doc/developer.texi index 7ccd927..6035225 100644 --- a/doc/developer.texi +++ b/doc/developer.texi @@ -1,7 +1,7 @@ -@node Developer manual +@node Developer @unnumbered Developer manual -Pay attention how to get @ref{Development source code}. +Pay attention how to get @ref{Sources, development source code}. @table @asis @item Nonce and identity encryption @@ -19,10 +19,10 @@ and @url{http://ed25519.cr.yp.to/, Ed25519}. @url{https://en.wikipedia.org/wiki/PBKDF2, PBKDF2} based on @url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}. @item Packet overhead -26 bytes per packet. Two more bytes in TCP mode. +26 bytes per packet. @item Handshake overhead 4 UDP (2 from client, 2 from server) packets (round-trips for TCP), -264 bytes total payload (8 bytes more in TCP mode). +264 bytes total payload. @item Entropy required 832 bits in average on client, 832 bits in average on server side per handshake. @@ -30,8 +30,8 @@ handshake. @menu * Verifier structure:: -* Transport protocol:: -* Handshake protocol:: +* Transport protocol: Transport. +* Handshake protocol: Handshake. @end menu @include verifierstruct.texi diff --git a/doc/download.texi b/doc/download.texi index 124ea63..96367a1 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -1,4 +1,4 @@ -@node Prepared tarballs +@node Tarballs @section Prepared tarballs You can obtain releases source code prepared tarballs from the links below: @@ -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 3.5 @tab 179 KiB +@tab @url{download/govpn-3.5.tar.xz, link} @url{download/govpn-3.5.tar.xz.sig, sign} +@tab @code{6b60c2cd4a8b4b2c893e52d3366510678704fd68a02a0ea24cb112bd753ea54b} + @item 3.4 @tab 175 KiB @tab @url{download/govpn-3.4.tar.xz, link} @url{download/govpn-3.4.tar.xz.sig, sign} @tab @code{266612a7f8faa6ceb2955ed611c0c21872776306f4eaad5b785145bbb0390c82} diff --git a/doc/example.texi b/doc/example.texi index a2e4173..c262846 100644 --- a/doc/example.texi +++ b/doc/example.texi @@ -1,4 +1,4 @@ -@node Example usage +@node Example @section Example usage Let's assume that there is some insecure link between your computer and @@ -15,44 +15,38 @@ is 1432. @end itemize @strong{Install}. At first you must @ref{Installation, install} this -software: download, check the signature, compile. +software: download, @ref{Integrity, check the signature}, compile. -@strong{Prepare the server}. Create the new client, named (for example) -"Alice": +@strong{Prepare the client}. Generate client's identity and verifier for +Alice as an example: +@verbatim +client% ./utils/newclient.sh Alice +Enter passphrase: +Your id is: 7012df29deee2170594119df5091d4a2 -@example -server% ./utils/newclient.sh Alice -Place verifier to peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier -@end example - -"6d4ac605ce8dc37c2f0bf21cb542a713" -- is the new client's identity. - -@strong{Prepare the client}. Generate @ref{Verifier} for known client -identity: +Place the following JSON configuration entry on the server's side: -@example -client% ./utils/storekey.sh /tmp/passphrase -Enter passphrase:[my secure passphrase is here] -client% govpn-verifier -id 6d4ac605ce8dc37c2f0bf21cb542a713 -key /tmp/passphrase -562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55 -client% rm /tmp/passphrase -@end example + "7012df29deee2170594119df5091d4a2": { + "name": "Alice", + "up": "/path/to/up.sh", + "verifier": "fb43255ca3fe5bd884e364e5eae0cd37ad14774930a027fd38d8938fd0b57425" + } -"562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55" -- -this is verifier itself. +Verifier was generated with: -@strong{Save verifier on server}. + ./utils/storekey.sh /tmp/passphrase + govpn-verifier -id 7012df29deee2170594119df5091d4a2 -key /tmp/passphrase +@end verbatim -@example -server% cat > peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier <> peers/6d4ac605ce8dc37c2f0bf21cb542a713/up.sh +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 @@ -83,7 +77,7 @@ client% ip route add default via 172.16.0.1 @example client% govpn-client \ -key key.txt \ - -id 6d4ac605ce8dc37c2f0bf21cb542a713 \ + -id 906e34b98750c4f686d6c5489508763c \ -iface tap10 \ -remote 192.168.0.1:1194 \ -mtu 1472 @@ -103,7 +97,7 @@ client% ifconfig tap10 inet6 fc00::2/96 mtu 1412 up client% route -6 add default fc00::1 client% govpn-client \ -key key.txt \ - -id 6d4ac605ce8dc37c2f0bf21cb542a713 \ + -id 906e34b98750c4f686d6c5489508763c \ -iface tap10 \ -remote "[fe80::1%me0]":1194 @end example diff --git a/doc/govpn.texi b/doc/govpn.texi index c4cd8c9..c31b59e 100644 --- a/doc/govpn.texi +++ b/doc/govpn.texi @@ -4,8 +4,8 @@ @settitle GoVPN @copying -This manual is for GoVPN -- simple secure free software virtual private -network daemon, aimed to be reviewable, secure, DPI/censorship-resistant, +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} @@ -22,93 +22,36 @@ A copy of the license is included in the section entitled "Copying conditions". @node Top @top GoVPN -GoVPN is simple secure free software virtual private network daemon, -aimed to be reviewable, secure and -@url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}/censorship-resistant. - -@itemize @bullet -@item -Copylefted free software: licensed under -@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}. -@item -Fast strong @ref{PAKE, password authenticated} augmented key agreement -(PAKE DH A-EKE) @ref{Handshake protocol, handshake}. -@item -Mutual two-side zero-knowledge peers authentication. -@item -@ref{Verifier structure, Augmented authentication tokens} resistant to -offline dictionary attacks. An attacker can not masquerade a client -even with server password verifiers compromising. -@item -Encrypted and authenticated @ref{Transport protocol, payload transport} -with 128-bit @ref{Developer manual, security margin} state-of-the-art -cryptography and censorship resistance (indistinguishability from noise). -@item -@url{https://en.wikipedia.org/wiki/Forward_secrecy, Perfect forward secrecy} -property. -@item -Replay attack protection (using one-time MACs). -@item -Built-in rehandshake (session key rotation) and heartbeat features. -@item -Ability to hide payload packets length with the @ref{Noise, noise} data. -@item -Ability to hide payload timestamps with @ref{CPR, constant packet rate} -traffic. -@item -Compatible with @url{http://egd.sourceforge.net/, EGD} (entropy -gathering daemon) PRNGs. -@item -Several simultaneous clients support with per-client configuration -options. Clients have pre-established @ref{Identity, identity} invisible -for third-parties (they are anonymous). -@item -Uses @url{https://en.wikipedia.org/wiki/TAP_(network_driver), TAP} -underlying network interfaces. -@item -Can use @ref{Network transport, UDP and TCP} or HTTP @ref{Proxy, proxies} -for accessing the server. -@item -Fully IPv4 and IPv6 compatible. -@item -Optional built-in HTTP-server for retrieving -@ref{Stats, statistics} information about known connected peers in -@url{http://json.org/, JSON} format. -@item -Written on on @url{http://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. -@end itemize - -@include media.texi +@include about.texi @menu +* Подробнее о демоне: About RU. * News:: * Installation:: * Precautions:: -* User manual:: -* Developer manual:: -* Contacts:: -* Copying conditions:: +* User manual: User. +* Developer manual: Developer. +* Contacts and feedback: Contacts. * Thanks:: +* In the media: Media. * TODO:: +* Copying conditions:: @end menu +@include about.ru.texi @include news.texi @include installation.texi @include precautions.texi @include user.texi @include developer.texi @include contacts.texi +@include thanks.texi +@include media.texi +@include todo.texi @node Copying conditions @unnumbered Copying conditions @insertcopying @verbatiminclude fdl.txt - -@include thanks.texi -@include todo.texi @bye diff --git a/doc/handshake.texi b/doc/handshake.texi index 0b7f4bd..c15e456 100644 --- a/doc/handshake.texi +++ b/doc/handshake.texi @@ -1,4 +1,4 @@ -@node Handshake protocol +@node Handshake @section Handshake protocol @verbatiminclude handshake.utxt @@ -6,9 +6,10 @@ Each handshake message ends with so called @code{IDtag}: it is an XTEA encrypted first 64 bits of each message with client's @ref{Identity} as a key. It is used to transmit identity and to mark packet as handshake -message. Server can determine used identity by trying all possible known -to him keys. It consumes resources, but XTEA is rather fast algorithm -and handshake messages checking is seldom enough event. +message. + +If @ref{Noise} is enabled, then junk data is inserted before +@code{IDtag} to full up packet to MTU's size. @strong{Preparation stage}: diff --git a/doc/identity.texi b/doc/identity.texi index cb3061f..46a6889 100644 --- a/doc/identity.texi +++ b/doc/identity.texi @@ -7,7 +7,7 @@ to make DPI and deanonymization much harder to success. @example % ./utils/newclient.sh doesnotmatter -Place verifier to peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier +Your id is: 7012df29deee2170594119df5091d4a2 @end example -"6d4ac605ce8dc37c2f0bf21cb542a713" is client's identity. +@code{7012df29deee2170594119df5091d4a2} is client's identity. diff --git a/doc/installation.texi b/doc/installation.texi index 3d496e3..cc5debb 100644 --- a/doc/installation.texi +++ b/doc/installation.texi @@ -1,6 +1,13 @@ @node Installation @unnumbered Installation +Possibly GoVPN already exists in your distribution: + +@itemize @bullet +@item @url{https://aur.archlinux.org/packages/govpn/, AUR} +@item @url{http://www.freshports.org/security/govpn/, FreeBSD ports} +@end itemize + GoVPN is written on Go programming language and you have to install Go compiler (1.5+ version is highly recommended): @code{lang/go} port in FreeBSD and @code{golang} package in most GNU/Linux distributions. @@ -20,8 +27,8 @@ Included required libraries: @item @code{github.com/bigeagle/water} @tab GNU/Linux @tab BSD 3-Clause @end multitable -Get @ref{Prepared tarballs, the tarball}, check its -@ref{Tarballs integrity check, authenticity} and run @code{make}. +Get @ref{Tarballs, the tarball}, check its +@ref{Integrity, integrity and authenticity} and run @code{make}. @emph{govpn-client}, @emph{govpn-server}, @emph{govpn-verifier} binaries will be built in the current directory: @@ -37,11 +44,11 @@ There is @code{install} target respecting @code{DESTDIR}. It will install binaries, info-documentation and utilities. @menu -* Prepared tarballs:: -* Development source code:: -* Tarballs integrity check:: +* Prepared tarballs: Tarballs. +* Tarballs integrity check: Integrity. +* Development source code: Sources. @end menu @include download.texi -@include sources.texi @include integrity.texi +@include sources.texi diff --git a/doc/integrity.texi b/doc/integrity.texi index 01badc5..dc51aaf 100644 --- a/doc/integrity.texi +++ b/doc/integrity.texi @@ -1,11 +1,11 @@ -@node Tarballs integrity check +@node Integrity @section Tarballs integrity check You @strong{have to} verify downloaded archives integrity and check their signature to be sure that you have got trusted, untampered software. For integrity and authentication of downloaded binaries @url{https://www.gnupg.org/, The GNU Privacy Guard} is used. You must -download signature provided with the tarball. +download signature (.sig) provided with the tarball. For the very first time you need to import signing public keys. They are provided below, but be sure that you are reading them from the diff --git a/doc/media.texi b/doc/media.texi index c69d35a..9423086 100644 --- a/doc/media.texi +++ b/doc/media.texi @@ -1,4 +1,5 @@ -In the media: +@node Media +@unnumbered In the media @itemize @bullet @item @url{http://habrahabr.ru/company/ivi/blog/256365/, Реализуем безопасный VPN-протокол} (on russian) diff --git a/doc/netproto.texi b/doc/netproto.texi index 083e833..d033656 100644 --- a/doc/netproto.texi +++ b/doc/netproto.texi @@ -1,10 +1,9 @@ -@node Network transport +@node Network @section Network transport You can use either UDP or TCP underlying network transport protocols. -TCP consumes more traffic: two additional bytes per packet. Also it is -has more complex and slightly slower code. Moreover because of packet -loss and TCP reliability it can lead to "meltdown" effect: significant -performance decrease of underlying TCP connections. So generally TCP is -not advisable for VPNs, but it can help with some nasty firewalls. +TCP is more resource hungry. Moreover because of packet loss and TCP +reliability it can lead to "meltdown" effect: significant performance +loss of underlying TCP connections. Generally TCP is not advisable for +VPNs, but it can help with some nasty firewalls. diff --git a/doc/news.texi b/doc/news.texi index 9eacc0f..bdb178e 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,9 +3,18 @@ @table @strong +@item Release 4.0 +@itemize @bullet +@item Handshake messages can be noised: their messages lengths are +hidden. Now they are indistinguishable from transport messages. +@item Parallelized clients processing on the server side. +@item Much higher overall performance. +@item Single JSON file server configuration. +@end itemize + @item Release 3.5 @itemize @bullet -@item Ability to use @ref{Network transport, TCP} network transport. +@item Ability to use @ref{Network, TCP} network transport. Server can listen on both UDP and TCP sockets. @item Ability to use @ref{Proxy, HTTP proxies} (through CONNECT method) for accessing the server. Server can also emulate HTTP proxy behaviour. @@ -96,7 +105,7 @@ JSON of all known connected peers information. Real-time client's statistics. @item -Documentation is explicitly licensed under GNU FDL 1.3+. +Documentation is explicitly licenced under GNU FDL 1.3+. @end itemize @item Release 2.3 diff --git a/doc/pake.texi b/doc/pake.texi index e96ad1a..d2cb773 100644 --- a/doc/pake.texi +++ b/doc/pake.texi @@ -6,7 +6,7 @@ client-server authentication. Is is secure, but not convenient for some user use-cases: @itemize @bullet -@item Compromising of password files on either server or client side +@item Compromising of passphrase files on either server or client side allows attacker to masquerade himself a client. @item To prevent compromising of keys on the client side, one needs some kind of passphrase protected secure storage (like either PGP with @@ -15,7 +15,7 @@ decryption to the memory, or full-disk encryption). Overall security on the client side is concentrated in passphrase (high-entropy password), so it is convenient to use it in GoVPN -directly, without static on-disk keys. That is why we use password +directly, without static on-disk keys. That is why we use passphrase authenticated key agreement. We use "passphrase" term instead of "password". Technically there may be diff --git a/doc/server.texi b/doc/server.texi index e86d86a..49a09fb 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -1,4 +1,4 @@ -@node Server part +@node Server @section Server part Except for common @code{-mtu}, @code{-stats}, @code{-egd} options server @@ -7,75 +7,69 @@ has the following ones: @table @code @item -proto -@ref{Network transport} to use. Can be @emph{udp}, @emph{tcp} or @emph{all}. +@ref{Network, network protocol} to use. Can be @emph{udp} (default), +@emph{tcp} or @emph{all}. @item -bind Address (@code{host:port} format) we must bind to. -@item -peers -Path to the directory containing peers information, database. +@item -conf +Path to JSON file with the configuration. @item -proxy Start trivial HTTP @ref{Proxy} server on specified @emph{host:port}. @end table -Peers directory must contain subdirectories with the names of client's -identities in hexadecimal notation. Each subdirectory has the following -files: - -@table @code - -@item verifier -@strong{Required}. Contains corresponding verifier used to authenticate -the client in hexadecimal notation. See @ref{Verifier} for how -to create it. - -@item up.sh -@strong{Required}. up-script executes each time connection with the -client is established. It's @emph{stdout} output must contain TAP -interface name on the first string. This script can be simple -@code{echo tap10}, or maybe more advanced like this: - @example - #!/bin/sh - $tap=$(ifconfig tap create) - ifconfig $tap inet6 fc00::1/96 mtu 1412 up - echo $tap - @end example - -@item down.sh -Optional. Same as @code{up.sh} above, but executes when connection is -lost. - -@item name -Optional. Contains human readable username. Used to beauty output of -@ref{Stats}. - -@item timeout -Optional. Contains @ref{Timeout} setting (decimal notation) in seconds. -Otherwise default minute timeout will be used. - -@item noise -Optional. Contains either "1" (enable @ref{Noise} adding), or "0". - -@item cpr -Optional. Contains @ref{CPR} setting (decimal notation) in KiB/sec. - -@end table +Configuration file is JSON file with following example structure: + +@verbatim +{ + "9b40701bdaf522f2b291cb039490312": { <-- Peer identifier + "name": "stargrave", <-- OPTIONAL human readable name + "up": "./stargrave-up.sh", <-- 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 + "verifier": "2c15bbdffc73193bea56db412bce1143c68ccbdaa9e2eade53a684497646a685" + }, + [...] +} +@end verbatim + +See @ref{Verifier} for its description. + +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: +@example +#!/bin/sh +$tap=$(ifconfig tap create) +ifconfig $tap inet6 fc00::1/96 mtu 1412 up +echo $tap +@end example -Each minute server refreshes peers directory contents and adds newly -appeared identities, deletes an obsolete ones. +Each minute server rereads and refreshes peers configuration and adds +newly appeared identities, deletes an obsolete ones. You can use convenient @code{utils/newclient.sh} script for new client creation: -@example +@verbatim % ./utils/newclient.sh Alice -Place verifier to peers/9b40701bdaf522f2b291cb039490312/verifier -@end example - -@code{9b40701bdaf522f2b291cb039490312} is client's identification. -@code{peers/9b40701bdaf522f2b291cb039490312/name} contains @emph{Alice}, -@code{peers/9b40701bdaf522f2b291cb039490312/verifier} contains dummy -verifier and @code{peers/9b40701bdaf522f2b291cb039490312/up.sh} contains -currently dummy empty up-script. +[...] +Your id is: 7012df29deee2170594119df5091d4a2 + +Place the following JSON configuration entry on the server's side: + + "906e34b98750c4f686d6c5489508763c": { + "name": "Alice", + "up": "/path/to/up.sh", + "verifier": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + } +[...] +@end verbatim diff --git a/doc/sources.texi b/doc/sources.texi index 3b7e02c..40aa499 100644 --- a/doc/sources.texi +++ b/doc/sources.texi @@ -1,13 +1,14 @@ -@node Development source code +@node Sources @section Development source code Development source code contains the latest version of the code. It may be buggy. It does not contain compiled documentation and dependent libraries source code. Because of that, it is not recommended for -porters: use @ref{Prepared tarballs} instead. +porters: use @ref{Tarballs} instead. -You can obtain it by cloning Git repository and fetching dependent -libraries source code as git submodules: +You can obtain it by cloning @url{http://git-scm.com/, Git} +@url{http://git.cypherpunks.ru/cgit.cgi/govpn.git/log/, repository} +and fetching dependent libraries source code as git submodules: @example % git clone git://git.cypherpunks.ru/govpn.git govpn diff --git a/doc/todo.texi b/doc/todo.texi index f8064cf..f7d00f3 100644 --- a/doc/todo.texi +++ b/doc/todo.texi @@ -2,9 +2,10 @@ @unnumbered TODO @itemize +@item Use Argon2: +@url{https://password-hashing.net/, Password Hashing Competition} +winner, instead of PBKDF2. @item Ability to tunnel only specified TCP connections, without full featured VPN solution. Similar to ssh -R. -@item Ability to noise handshake packets sizes. -@item Increase performance. @item Randomize ports usage. @end itemize diff --git a/doc/transport.texi b/doc/transport.texi index 51dc9de..bfe3501 100644 --- a/doc/transport.texi +++ b/doc/transport.texi @@ -1,46 +1,61 @@ -@node Transport protocol +@node Transport @section Transport protocol @verbatim -[PktLen] + ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE) + - AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA_SIZE+DATA+NOISE)) +TAG || ENCRYPTED || NONCE --> PACKET + ^ ^ ^ + | | | + | | +-------------+ + | | | + | +-------------+ | + | | | + +--< AUTH(AUTH_KEY, ENCRYPTED || NONCE) + ^ ^ + | | ++------------------------+ | +| | +| +---------------+ +| | ++--< ENCRYPT(KEY, NONCE, PAYLOAD) + ^ ^ + | | + | +--< SIZE || DATA [|| NOISE] + | + +--< PRP(PRP_KEY, SERIAL) @end verbatim -All transport and handshake messages are indistinguishable from -pseudo random noise, except when using TCP connections. - -@code{PktLen} is used only with TCP connections. It is big-endian -@emph{uint16} length of the whole packet (except @code{PktLen} itself). - @code{SERIAL} is message's serial number. Odds are reserved for client(->server) messages, evens for server(->client) messages. -@code{ENCn} is XTEA block cipher algorithm used here as PRP (pseudo +@code{PRP} is XTEA block cipher algorithm used here as PRP (pseudo random permutation function) to obfuscate @code{SERIAL}. Plaintext @code{SERIAL} state is kept in peers internal state, but encrypted -before transmission. XTEA is compact and fast enough. Salsa20 is PRF -function and requires much more code to create PRP from it. +before transmission. 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). -@code{ENC} is Salsa20 stream cipher, with established session @code{KEY} -and obfuscated @code{SERIAL} used as a nonce. First 256 bits of -Salsa20's output is used as Poly1305 authentication key, next 256 bits -are ignored. All remaining output is XORed with the data, encrypting it. +@verbatim +PRP_KEY = ENCRYPT(KEY, 0, 128-bit) +@end verbatim + +@code{ENCRYPT} is Salsa20 stream cipher, with established session +@code{KEY} and obfuscated @code{SERIAL} used as a nonce. 512 bit of +Salsa20's output is ignored and only remaining is XORed with ther data, +encrypting it. -@code{DATA_SIZE} is big-endian @emph{uint16} storing length of the +@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{AUTH} is Poly1305 authentication function. First 256 bits of -Salsa20 output are used as a one-time key for @code{AUTH}. +Salsa20's output are used as a one-time key for @code{AUTH}. + +@verbatim +AUTH_KEY = ENCRYPT(KEY, NONCE, 256 bit) +@end verbatim To prevent replay attacks we must remember received @code{SERIAL}s and -if meet one, then drop it. Basically we could just store latest number -and check if received one is greater, but because of UDP packets -reordering this can lead to valid packets dropping and overall -performance degradation. We store up to 256 seen nonces in hash -structure, in two swapping buckets. +drop when receiving duplicate ones. diff --git a/doc/user.texi b/doc/user.texi index 0d237a0..73214da 100644 --- a/doc/user.texi +++ b/doc/user.texi @@ -1,33 +1,33 @@ -@node User manual +@node User @unnumbered User manual Announcements about updates and new releases can be found in @ref{Contacts}. -GoVPN is split into two pieces: client and server. Each of them work on -top of UDP/TCP and TAP virtual network interfaces. GoVPN is just a -tunnelling of Ethernet frames, nothing less, nothing more. All you -IP-related network management is not touched by VPN at all. You can -automate it using up and down shell scripts. +GoVPN is split into two pieces: @ref{Client} and @ref{Server}. Each of +them work on top of @ref{Network, UDP/TCP} and TAP virtual network +interfaces. GoVPN is just a tunnelling of Ethernet frames, nothing less, +nothing more. All you IP-related network management is not touched by +VPN at all. You can automate it using up and down shell scripts. What network performance can user expect? For example single @emph{Intel i5-2450M 2.5 GHz} core on @emph{FreeBSD 10.2 amd64} -with @emph{Go 1.5} gives 435 Mbps TCP (over UDP) throughput. +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 transport: Network. * Proxy:: * MTU:: Maximum Transmission Unit * Stats:: * Noise:: * CPR:: Constant Packet Rate * Verifier:: -* Client part:: -* Server part:: -* Example usage:: +* Client part: Client. +* Server part: Server. +* Example usage: Example. @end menu @include egd.texi diff --git a/doc/verifier.texi b/doc/verifier.texi index 6e97b7e..61b1e44 100644 --- a/doc/verifier.texi +++ b/doc/verifier.texi @@ -13,8 +13,8 @@ Enter passphrase:[hello world] 210e3878542828901a3af9b4aa00b004de530410eef5c1ba2ffb6d04504371b2 @end example -Store "210...1b2" string on the server's side in corresponding -@code{verifier} file. +Store @code{210...1b2} string on the server's side in corresponding +@code{verifier} configuration file's field. You can check passphrase against verifier by specifying @code{-verifier} option with the path to verifier file: diff --git a/src/github.com/agl/ed25519 b/src/github.com/agl/ed25519 index d2b94fd..278e1ec 160000 --- a/src/github.com/agl/ed25519 +++ b/src/github.com/agl/ed25519 @@ -1 +1 @@ -Subproject commit d2b94fd789ea21d12fac1a4443dd3a3f79cda72c +Subproject commit 278e1ec8e8a6e017cd07577924d6766039146ced diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index 7f32b6c..6c611e7 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -16,12 +16,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Simple secure free software virtual private network daemon client. +// Simple secure, DPI/censorship-resistant free software VPN daemon client. package main import ( "flag" - "io" "log" "net" "os" @@ -47,11 +46,18 @@ var ( noisy = flag.Bool("noise", false, "Enable noise appending") cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate") egdPath = flag.String("egd", "", "Optional path to EGD socket") + + conf *govpn.PeerConf + tap *govpn.TAP + timeout int + firstUpCall bool = true + knownPeers govpn.KnownPeers + idsCache govpn.CipherCache ) func main() { flag.Parse() - timeout := *timeoutP + timeout = *timeoutP var err error log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) @@ -68,50 +74,22 @@ func main() { } pub, priv := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath)) - conf := &govpn.PeerConf{ - Id: id, - Timeout: time.Second * time.Duration(timeout), - NoiseEnable: *noisy, - CPR: *cpr, - DSAPub: pub, - DSAPriv: priv, - } - govpn.PeersInitDummy(id, conf) - - var conn io.Writer - var sink chan []byte - var ready chan struct{} - switch *proto { - case "udp": - conn, sink, ready = startUDP() - case "tcp": - if *proxyAddr != "" { - conn, sink, ready = proxyTCP() - } else { - conn, sink, ready = startTCP() - } - default: - log.Fatalln("Unknown protocol specified") + conf = &govpn.PeerConf{ + Id: id, + Timeout: time.Second * time.Duration(timeout), + Noise: *noisy, + CPR: *cpr, + DSAPub: pub, + DSAPriv: priv, } + idsCache = govpn.NewCipherCache([]govpn.PeerId{*id}) + log.Println(govpn.VersionGet()) - tap, ethSink, ethReady, _, err := govpn.TAPListen( - *ifaceName, - time.Second*time.Duration(timeout), - *cpr, - ) + tap, err = govpn.TAPListen(*ifaceName) if err != nil { log.Fatalln("Can not listen on TAP interface:", err) } - timeouts := 0 - firstUpCall := true - var peer *govpn.Peer - var ethPkt []byte - var pkt []byte - knownPeers := govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer}) - - log.Println(govpn.VersionGet()) - log.Println("Connected to", *proto, *remoteAddr) log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) @@ -125,65 +103,35 @@ func main() { termSignal := make(chan os.Signal, 1) signal.Notify(termSignal, os.Interrupt, os.Kill) - log.Println("Starting handshake") - handshake := govpn.HandshakeStart(*remoteAddr, conn, conf) - MainCycle: for { - if peer != nil && (peer.BytesIn+peer.BytesOut) > govpn.MaxBytesPerKey { - peer.Zero() - peer = nil - handshake = govpn.HandshakeStart(*remoteAddr, conn, conf) - log.Println("Rehandshaking") + timeouted := make(chan struct{}) + rehandshaking := make(chan struct{}) + termination := make(chan struct{}) + switch *proto { + case "udp": + go startUDP(timeouted, rehandshaking, termination) + case "tcp": + if *proxyAddr != "" { + go proxyTCP(timeouted, rehandshaking, termination) + } else { + go startTCP(timeouted, rehandshaking, termination) + } + default: + log.Fatalln("Unknown protocol specified") } select { case <-termSignal: + log.Fatalln("Finishing") + termination <- struct{}{} break MainCycle - case ethPkt = <-ethSink: - if peer == nil { - if len(ethPkt) > 0 { - ethReady <- struct{}{} - } - continue - } - peer.EthProcess(ethPkt, ethReady) - case pkt = <-sink: - timeouts++ - if timeouts >= timeout { - break MainCycle - } - if pkt == nil { - ready <- struct{}{} - continue - } - - if peer == nil { - if govpn.IDsCache.Find(pkt) == nil { - log.Println("Invalid identity in handshake packet") - ready <- struct{}{} - continue - } - if p := handshake.Client(pkt); p != nil { - log.Println("Handshake completed") - if firstUpCall { - go govpn.ScriptCall(*upPath, *ifaceName) - firstUpCall = false - } - peer = p - handshake.Zero() - handshake = nil - } - ready <- struct{}{} - continue - } - if peer == nil { - ready <- struct{}{} - continue - } - if peer.PktProcess(pkt, tap, ready) { - timeouts = 0 - } + case <-timeouted: + break MainCycle + case <-rehandshaking: } + close(timeouted) + close(rehandshaking) + close(termination) } govpn.ScriptCall(*downPath, *ifaceName) } diff --git a/src/govpn/cmd/govpn-client/proxy.go b/src/govpn/cmd/govpn-client/proxy.go index 678a5ae..bde828e 100644 --- a/src/govpn/cmd/govpn-client/proxy.go +++ b/src/govpn/cmd/govpn-client/proxy.go @@ -21,13 +21,12 @@ package main import ( "bufio" "encoding/base64" - "io" "log" "net" "net/http" ) -func proxyTCP() (io.Writer, chan []byte, chan struct{}) { +func proxyTCP(timeouted, rehandshaking, termination chan struct{}) { proxyAddr, err := net.ResolveTCPAddr("tcp", *proxyAddr) if err != nil { log.Fatalln("Can not resolve proxy address:", err) @@ -51,9 +50,6 @@ func proxyTCP() (io.Writer, chan []byte, chan struct{}) { if err != nil || resp.StatusCode != http.StatusOK { log.Fatalln("Unexpected response from proxy") } - sink := make(chan []byte) - ready := make(chan struct{}) - go handleTCP(conn, sink, ready) - go func() { ready <- struct{}{} }() - return TCPSender{conn}, sink, ready + log.Println("Connected to proxy:", *proxyAddr) + go handleTCP(conn, timeouted, rehandshaking, termination) } diff --git a/src/govpn/cmd/govpn-client/tcp.go b/src/govpn/cmd/govpn-client/tcp.go index 373789a..1dd11ec 100644 --- a/src/govpn/cmd/govpn-client/tcp.go +++ b/src/govpn/cmd/govpn-client/tcp.go @@ -19,81 +19,151 @@ along with this program. If not, see . package main import ( - "encoding/binary" - "io" + "bytes" "log" "net" + "sync/atomic" + "time" "govpn" ) -// TCPSender prepends size prefix to each outgoing packet. -type TCPSender struct { - conn *net.TCPConn -} - -func (c TCPSender) Write(data []byte) (int, error) { - size := make([]byte, 2) - binary.BigEndian.PutUint16(size, uint16(len(data))) - return c.conn.Write(append(size, data...)) -} - -func startTCP() (io.Writer, chan []byte, chan struct{}) { +func startTCP(timeouted, rehandshaking, termination chan struct{}) { remote, err := net.ResolveTCPAddr("tcp", *remoteAddr) if err != nil { log.Fatalln("Can not resolve remote address:", err) } - c, err := net.DialTCP("tcp", nil, remote) - conn := TCPSender{c} + conn, err := net.DialTCP("tcp", nil, remote) if err != nil { - log.Fatalln("Can not connect TCP:", err) + log.Fatalln("Can not connect to address:", err) } - sink := make(chan []byte) - ready := make(chan struct{}) - go handleTCP(c, sink, ready) - go func() { ready <- struct{}{} }() - return conn, sink, ready + log.Println("Connected to TCP:" + *remoteAddr) + handleTCP(conn, timeouted, rehandshaking, termination) } -func handleTCP(conn *net.TCPConn, sink chan []byte, ready chan struct{}) { - var err error - var n int - var sizeNbuf int - sizeBuf := make([]byte, 2) - var sizeNeed uint16 - var bufN uint16 +func handleTCP(conn *net.TCPConn, timeouted, rehandshaking, termination chan struct{}) { + hs := govpn.HandshakeStart(*remoteAddr, conn, conf) buf := make([]byte, govpn.MTU) + var n int + var err error + var prev int + var peer *govpn.Peer + var terminator chan struct{} +HandshakeCycle: for { - <-ready - if sizeNbuf != 2 { - n, err = conn.Read(sizeBuf[sizeNbuf:2]) - if err != nil { - break - } - sizeNbuf += n - if sizeNbuf != 2 { - sink <- nil - continue - } - sizeNeed = binary.BigEndian.Uint16(sizeBuf) - if int(sizeNeed) > govpn.MTU-2 { - log.Println("Invalid TCP size, skipping") - sizeNbuf = 0 - sink <- nil - continue - } - bufN = 0 + select { + case <-termination: + break HandshakeCycle + default: + } + if prev == govpn.MTU { + log.Println("Timeouted waiting for the packet") + timeouted <- struct{}{} + break HandshakeCycle + } + + conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second)) + n, err = conn.Read(buf[prev:]) + if err != nil { + log.Println("Connection timeouted") + timeouted <- struct{}{} + break HandshakeCycle + } + + prev += n + peerId := idsCache.Find(buf[:prev]) + if peerId == nil { + continue + } + peer = hs.Client(buf[:prev]) + prev = 0 + if peer == nil { + continue } - ReadMore: - if sizeNeed != bufN { - n, err = conn.Read(buf[bufN:sizeNeed]) - if err != nil { - break + log.Println("Handshake completed") + knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer}) + if firstUpCall { + go govpn.ScriptCall(*upPath, *ifaceName) + firstUpCall = false + } + hs.Zero() + terminator = make(chan struct{}) + go func() { + heartbeat := time.NewTicker(peer.Timeout) + var data []byte + Processor: + for { + select { + case <-heartbeat.C: + peer.EthProcess(nil) + case <-terminator: + break Processor + case data = <-tap.Sink: + peer.EthProcess(data) + } } - bufN += uint16(n) - goto ReadMore + heartbeat.Stop() + peer.Zero() + }() + break HandshakeCycle + } + if hs != nil { + hs.Zero() + } + if peer == nil { + return + } + + nonceExpectation := make([]byte, govpn.NonceSize) + peer.NonceExpectation(nonceExpectation) + prev = 0 + var i int +TransportCycle: + for { + select { + case <-termination: + break TransportCycle + default: } - sizeNbuf = 0 - sink <- buf[:sizeNeed] + if prev == govpn.MTU { + log.Println("Timeouted waiting for the packet") + timeouted <- struct{}{} + break TransportCycle + } + conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Second)) + n, err = conn.Read(buf[prev:]) + if err != nil { + log.Println("Connection timeouted") + timeouted <- struct{}{} + break TransportCycle + } + prev += n + CheckMore: + if prev < govpn.MinPktLength { + continue + } + i = bytes.Index(buf[:prev], nonceExpectation) + if i == -1 { + continue + } + if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) { + log.Println("Unauthenticated packet, dropping connection") + timeouted <- struct{}{} + break TransportCycle + } + if atomic.LoadInt64(&peer.BytesIn)+atomic.LoadInt64(&peer.BytesOut) > govpn.MaxBytesPerKey { + log.Println("Need rehandshake") + rehandshaking <- struct{}{} + break TransportCycle + } + peer.NonceExpectation(nonceExpectation) + copy(buf, buf[i+govpn.NonceSize:prev]) + prev = prev - i - govpn.NonceSize + goto CheckMore + } + if terminator != nil { + terminator <- struct{}{} } + peer.Zero() + conn.Close() } diff --git a/src/govpn/cmd/govpn-client/udp.go b/src/govpn/cmd/govpn-client/udp.go index 103ed6d..9f45f8b 100644 --- a/src/govpn/cmd/govpn-client/udp.go +++ b/src/govpn/cmd/govpn-client/udp.go @@ -19,41 +19,104 @@ along with this program. If not, see . package main import ( - "io" "log" "net" + "sync/atomic" "time" "govpn" ) -func startUDP() (io.Writer, chan []byte, chan struct{}) { +func startUDP(timeouted, rehandshaking, termination chan struct{}) { remote, err := net.ResolveUDPAddr("udp", *remoteAddr) if err != nil { log.Fatalln("Can not resolve remote address:", err) } - c, err := net.DialUDP("udp", nil, remote) - conn := io.Writer(c) + conn, err := net.DialUDP("udp", nil, remote) if err != nil { log.Fatalln("Can not listen on UDP:", err) } - sink := make(chan []byte) - ready := make(chan struct{}) - go func() { - buf := make([]byte, govpn.MTU) - var n int - var err error - for { - <-ready - c.SetReadDeadline(time.Now().Add(time.Second)) - n, err = c.Read(buf) - if err != nil { - sink <- nil - continue + log.Println("Connected to UDP:" + *remoteAddr) + + hs := govpn.HandshakeStart(*remoteAddr, conn, conf) + buf := make([]byte, govpn.MTU) + var n int + var timeouts int + var peer *govpn.Peer + var terminator chan struct{} +MainCycle: + for { + select { + case <-termination: + break MainCycle + default: + } + + conn.SetReadDeadline(time.Now().Add(time.Second)) + n, err = conn.Read(buf) + if timeouts == timeout { + log.Println("Timeouted") + timeouted <- struct{}{} + break + } + if err != nil { + timeouts++ + continue + } + if peer != nil { + if peer.PktProcess(buf[:n], tap, true) { + timeouts = 0 + } else { + log.Println("Unauthenticated packet") + timeouts++ + } + if atomic.LoadInt64(&peer.BytesIn)+atomic.LoadInt64(&peer.BytesOut) > govpn.MaxBytesPerKey { + log.Println("Need rehandshake") + rehandshaking <- struct{}{} + break MainCycle } - sink <- buf[:n] + continue + } + if idsCache.Find(buf[:n]) == nil { + log.Println("Invalid identity in handshake packet") + continue } - }() - ready <- struct{}{} - return conn, sink, ready + timeouts = 0 + peer = hs.Client(buf[:n]) + if peer == nil { + continue + } + log.Println("Handshake completed") + knownPeers = govpn.KnownPeers(map[string]**govpn.Peer{*remoteAddr: &peer}) + if firstUpCall { + go govpn.ScriptCall(*upPath, *ifaceName) + firstUpCall = false + } + hs.Zero() + terminator = make(chan struct{}) + go func() { + heartbeat := time.NewTicker(peer.Timeout) + var data []byte + Processor: + for { + select { + case <-heartbeat.C: + peer.EthProcess(nil) + case <-terminator: + break Processor + case data = <-tap.Sink: + peer.EthProcess(data) + } + } + heartbeat.Stop() + peer.Zero() + }() + } + if terminator != nil { + terminator <- struct{}{} + } + if hs != nil { + hs.Zero() + } + conn.Close() } diff --git a/src/govpn/cmd/govpn-server/common.go b/src/govpn/cmd/govpn-server/common.go new file mode 100644 index 0000000..4692e52 --- /dev/null +++ b/src/govpn/cmd/govpn-server/common.go @@ -0,0 +1,61 @@ +package main + +import ( + "bytes" + "sync" + "time" + + "govpn" +) + +type PeerState struct { + peer *govpn.Peer + terminator chan struct{} + tap *govpn.TAP +} + +var ( + handshakes map[string]*govpn.Handshake = make(map[string]*govpn.Handshake) + hsLock sync.RWMutex + + peers map[string]*PeerState = make(map[string]*PeerState) + peersLock sync.RWMutex + + peersById map[govpn.PeerId]string = make(map[govpn.PeerId]string) + peersByIdLock sync.RWMutex + + knownPeers govpn.KnownPeers + kpLock sync.RWMutex +) + +func peerReady(ps PeerState) { + var data []byte + heartbeat := time.NewTicker(ps.peer.Timeout) +Processor: + for { + select { + case <-heartbeat.C: + ps.peer.EthProcess(nil) + case <-ps.terminator: + break Processor + case data = <-ps.tap.Sink: + ps.peer.EthProcess(data) + } + } + close(ps.terminator) + ps.peer.Zero() + heartbeat.Stop() +} + +func callUp(peerId *govpn.PeerId) (string, error) { + result, err := govpn.ScriptCall(confs[*peerId].Up, "") + if err != nil { + return "", err + } + sepIndex := bytes.Index(result, []byte{'\n'}) + if sepIndex < 0 { + sepIndex = len(result) + } + 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 new file mode 100644 index 0000000..4136fab --- /dev/null +++ b/src/govpn/cmd/govpn-server/conf.go @@ -0,0 +1,105 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package main + +import ( + "encoding/hex" + "encoding/json" + "io/ioutil" + "log" + "time" + + "github.com/agl/ed25519" + + "govpn" +) + +const ( + RefreshRate = time.Minute +) + +var ( + confs map[govpn.PeerId]*govpn.PeerConf + idsCache govpn.CipherCache +) + +func confRead() map[govpn.PeerId]*govpn.PeerConf { + data, err := ioutil.ReadFile(*confPath) + if err != nil { + log.Fatalln("Unable to read configuration:", err) + } + confsRaw := new(map[string]govpn.PeerConf) + err = json.Unmarshal(data, confsRaw) + if err != nil { + log.Fatalln("Unable to parse configuration:", err) + } + + confs := make(map[govpn.PeerId]*govpn.PeerConf, len(*confsRaw)) + for peerIdRaw, pc := range *confsRaw { + peerId, err := govpn.IDDecode(peerIdRaw) + if err != nil { + log.Fatalln("Invalid peer ID:", peerIdRaw, err) + } + conf := govpn.PeerConf{ + Id: peerId, + Name: pc.Name, + Up: pc.Up, + Down: pc.Down, + Noise: pc.Noise, + CPR: pc.CPR, + } + if pc.TimeoutInt <= 0 { + pc.TimeoutInt = govpn.TimeoutDefault + } + conf.Timeout = time.Second * time.Duration(pc.TimeoutInt) + + if len(pc.Verifier) != ed25519.PublicKeySize*2 { + log.Fatalln("Verifier must be 64 hex characters long") + } + keyDecoded, err := hex.DecodeString(string(pc.Verifier)) + if err != nil { + log.Fatalln("Unable to decode the key:", err.Error(), pc.Verifier) + } + conf.DSAPub = new([ed25519.PublicKeySize]byte) + copy(conf.DSAPub[:], keyDecoded) + + confs[*peerId] = &conf + } + return confs +} + +func confRefresh() { + confs = confRead() + ids := make([]govpn.PeerId, 0, len(confs)) + for peerId, _ := range confs { + ids = append(ids, peerId) + } + idsCache.Update(ids) +} + +func confInit() { + idsCache = govpn.NewCipherCache(nil) + confRefresh() + go func() { + for { + time.Sleep(RefreshRate) + confRefresh() + } + }() +} diff --git a/src/govpn/cmd/govpn-server/main.go b/src/govpn/cmd/govpn-server/main.go index 37135b9..be4e7eb 100644 --- a/src/govpn/cmd/govpn-server/main.go +++ b/src/govpn/cmd/govpn-server/main.go @@ -16,75 +16,30 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Simple secure free software virtual private network daemon. +// Simple secure, DPI/censorship-resistant free software VPN daemon. package main import ( - "bytes" "flag" - "io" "log" "net" "os" "os/signal" - "path" "time" "govpn" ) var ( - bindAddr = flag.String("bind", "[::]:1194", "Bind to address") - proto = flag.String("proto", "udp", "Protocol to use: udp, tcp or all") - peersPath = flag.String("peers", "peers", "Path to peers keys directory") - 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") + bindAddr = flag.String("bind", "[::]:1194", "Bind to address") + proto = flag.String("proto", "udp", "Protocol to use: udp, tcp or all") + 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") ) -type Pkt struct { - addr string - conn io.Writer - data []byte - ready chan struct{} -} - -type PeerReadyEvent struct { - peer *govpn.Peer - iface string -} - -type PeerState struct { - peer *govpn.Peer - tap *govpn.TAP - sink chan []byte - ready chan struct{} - terminate chan struct{} -} - -func NewPeerState(peer *govpn.Peer, iface string) *PeerState { - tap, sink, ready, terminate, err := govpn.TAPListen(iface, peer.Timeout, peer.CPR) - if err != nil { - log.Println("Unable to create Eth", err) - return nil - } - state := PeerState{ - peer: peer, - tap: tap, - sink: sink, - ready: ready, - terminate: terminate, - } - return &state -} - -type EthEvent struct { - peer *govpn.Peer - data []byte - ready chan struct{} -} - func main() { flag.Parse() timeout := time.Second * time.Duration(govpn.TimeoutDefault) @@ -92,22 +47,22 @@ func main() { log.Println(govpn.VersionGet()) govpn.MTU = *mtu - govpn.PeersInit(*peersPath) + confInit() + knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer)) if *egdPath != "" { log.Println("Using", *egdPath, "EGD") govpn.EGDInit(*egdPath) } - sink := make(chan Pkt) switch *proto { case "udp": - startUDP(sink) + startUDP() case "tcp": - startTCP(sink) + startTCP() case "all": - startUDP(sink) - startTCP(sink) + startUDP() + startTCP() default: log.Fatalln("Unknown protocol specified") } @@ -118,22 +73,6 @@ func main() { hsHeartbeat := time.Tick(timeout) go func() { <-hsHeartbeat }() - var state *govpn.Handshake - var peerState *PeerState - var peer *govpn.Peer - var exists bool - states := make(map[string]*govpn.Handshake) - peers := make(map[string]*PeerState) - peerReadySink := make(chan PeerReadyEvent) - knownPeers := govpn.KnownPeers(make(map[string]**govpn.Peer)) - var peerReady PeerReadyEvent - var pkt Pkt - var ethEvent EthEvent - var peerId *govpn.PeerId - var peerConf *govpn.PeerConf - var handshakeProcessForce bool - ethSink := make(chan EthEvent) - log.Println("Max MTU on TAP interface:", govpn.TAPMaxMTU()) if *stats != "" { log.Println("Stats are going to listen on", *stats) @@ -144,10 +83,11 @@ func main() { go govpn.StatsProcessor(statsPort, &knownPeers) } if *proxy != "" { - go proxyStart(sink) + go proxyStart() } log.Println("Server started") + var needsDeletion bool MainCycle: for { select { @@ -155,128 +95,37 @@ MainCycle: break MainCycle case <-hsHeartbeat: now := time.Now() - for addr, hs := range states { + hsLock.Lock() + for addr, hs := range handshakes { if hs.LastPing.Add(timeout).Before(now) { log.Println("Deleting handshake state", addr) hs.Zero() - delete(states, addr) + delete(handshakes, addr) } } - for addr, state := range peers { - if state.peer.LastPing.Add(timeout).Before(now) { - log.Println("Deleting peer", state.peer) + peersLock.Lock() + peersByIdLock.Lock() + kpLock.Lock() + for addr, ps := range peers { + ps.peer.BusyR.Lock() + needsDeletion = ps.peer.LastPing.Add(timeout).Before(now) + ps.peer.BusyR.Unlock() + if needsDeletion { + log.Println("Deleting peer", ps.peer) delete(peers, addr) delete(knownPeers, addr) - downPath := path.Join( - govpn.PeersPath, - state.peer.Id.String(), - "down.sh", + delete(peersById, *ps.peer.Id) + go govpn.ScriptCall( + confs[*ps.peer.Id].Down, + ps.tap.Name, ) - go govpn.ScriptCall(downPath, state.tap.Name) - state.terminate <- struct{}{} - state.peer.Zero() + ps.terminator <- struct{}{} } } - case peerReady = <-peerReadySink: - for addr, state := range peers { - if state.tap.Name != peerReady.iface { - continue - } - delete(peers, addr) - delete(knownPeers, addr) - state.terminate <- struct{}{} - state.peer.Zero() - break - } - state := NewPeerState(peerReady.peer, peerReady.iface) - if state == nil { - continue - } - peers[peerReady.peer.Addr] = state - knownPeers[peerReady.peer.Addr] = &peerReady.peer - states[peerReady.peer.Addr].Zero() - delete(states, peerReady.peer.Addr) - log.Println("Registered interface", peerReady.iface, "with peer", peer) - go func(state *PeerState) { - for data := range state.sink { - ethSink <- EthEvent{ - peer: state.peer, - data: data, - ready: state.ready, - } - } - }(state) - case ethEvent = <-ethSink: - if s, exists := peers[ethEvent.peer.Addr]; !exists || s.peer != ethEvent.peer { - continue - } - ethEvent.peer.EthProcess(ethEvent.data, ethEvent.ready) - case pkt = <-sink: - if pkt.data == nil { - pkt.ready <- struct{}{} - continue - } - handshakeProcessForce = false - HandshakeProcess: - if _, exists = peers[pkt.addr]; handshakeProcessForce || !exists { - peerId = govpn.IDsCache.Find(pkt.data) - if peerId == nil { - log.Println("Unknown identity from", pkt.addr) - pkt.ready <- struct{}{} - continue - } - peerConf = peerId.Conf() - if peerConf == nil { - log.Println("Can not get peer configuration", peerId.String()) - pkt.ready <- struct{}{} - continue - } - state, exists = states[pkt.addr] - if !exists { - state = govpn.HandshakeNew(pkt.addr, pkt.conn, peerConf) - states[pkt.addr] = state - } - peer = state.Server(pkt.data) - if peer != nil { - log.Println("Peer handshake finished", peer) - if _, exists = peers[pkt.addr]; exists { - go func() { - peerReadySink <- PeerReadyEvent{ - peer, peers[pkt.addr].tap.Name, - } - }() - } else { - go func() { - upPath := path.Join(govpn.PeersPath, peer.Id.String(), "up.sh") - result, err := govpn.ScriptCall(upPath, "") - if err != nil { - return - } - sepIndex := bytes.Index(result, []byte{'\n'}) - if sepIndex < 0 { - sepIndex = len(result) - } - ifaceName := string(result[:sepIndex]) - peerReadySink <- PeerReadyEvent{peer, ifaceName} - }() - } - } - if !handshakeProcessForce { - pkt.ready <- struct{}{} - } - continue - } - peerState, exists = peers[pkt.addr] - if !exists { - pkt.ready <- struct{}{} - continue - } - // If it fails during processing, then try to work with it - // as with handshake packet - if !peerState.peer.PktProcess(pkt.data, peerState.tap, pkt.ready) { - handshakeProcessForce = true - goto HandshakeProcess - } + hsLock.Unlock() + peersLock.Unlock() + peersByIdLock.Unlock() + kpLock.Unlock() } } } diff --git a/src/govpn/cmd/govpn-server/proxy.go b/src/govpn/cmd/govpn-server/proxy.go index f1e8419..1a9f814 100644 --- a/src/govpn/cmd/govpn-server/proxy.go +++ b/src/govpn/cmd/govpn-server/proxy.go @@ -23,9 +23,7 @@ import ( "net/http" ) -type proxyHandler struct { - sink chan Pkt -} +type proxyHandler struct{} func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { conn, _, err := w.(http.Hijacker).Hijack() @@ -34,17 +32,14 @@ func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } conn.Write([]byte("HTTP/1.0 200 OK\n\n")) - ready := make(chan struct{}, 1) - go handleTCP(conn, p.sink, ready) - ready <- struct{}{} - + go handleTCP(conn) } -func proxyStart(sink chan Pkt) { - log.Println("HTTP proxy listening on:", *proxy) +func proxyStart() { + log.Println("HTTP proxy listening on:" + *proxy) s := &http.Server{ Addr: *proxy, - Handler: proxyHandler{sink}, + Handler: proxyHandler{}, } log.Println("HTTP proxy result:", s.ListenAndServe()) } diff --git a/src/govpn/cmd/govpn-server/tcp.go b/src/govpn/cmd/govpn-server/tcp.go index 42467d6..437b500 100644 --- a/src/govpn/cmd/govpn-server/tcp.go +++ b/src/govpn/cmd/govpn-server/tcp.go @@ -19,24 +19,15 @@ along with this program. If not, see . package main import ( - "encoding/binary" + "bytes" "log" "net" + "time" "govpn" ) -type TCPSender struct { - conn net.Conn -} - -func (c TCPSender) Write(data []byte) (int, error) { - size := make([]byte, 2) - binary.BigEndian.PutUint16(size, uint16(len(data))) - return c.conn.Write(append(size, data...)) -} - -func startTCP(sink chan Pkt) { +func startTCP() { bind, err := net.ResolveTCPAddr("tcp", *bindAddr) if err != nil { log.Fatalln("Can not resolve bind address:", err) @@ -45,62 +36,154 @@ func startTCP(sink chan Pkt) { if err != nil { log.Fatalln("Can not listen on TCP:", err) } - log.Println("Listening on TCP", *bindAddr) + log.Println("Listening on TCP:" + *bindAddr) go func() { for { - conn, _ := listener.AcceptTCP() - ready := make(chan struct{}, 1) - go handleTCP(conn, sink, ready) - ready <- struct{}{} + conn, err := listener.AcceptTCP() + if err != nil { + log.Println("Error accepting TCP:", err) + continue + } + go handleTCP(conn) } }() } -func handleTCP(conn net.Conn, sink chan Pkt, ready chan struct{}) { +func handleTCP(conn net.Conn) { addr := conn.RemoteAddr().String() - var err error - var n int - var sizeNbuf int - sizeBuf := make([]byte, 2) - var sizeNeed uint16 - var bufN uint16 buf := make([]byte, govpn.MTU) + var n int + var err error + var prev int + var hs *govpn.Handshake + var ps *PeerState + var peer *govpn.Peer + var tap *govpn.TAP + var conf *govpn.PeerConf for { - <-ready - if sizeNbuf != 2 { - n, err = conn.Read(sizeBuf[sizeNbuf:2]) - if err != nil { + if prev == govpn.MTU { + break + } + conn.SetReadDeadline(time.Now().Add(time.Duration(govpn.TimeoutDefault) * time.Second)) + n, err = conn.Read(buf[prev:]) + if err != nil { + // Either EOFed or timeouted + break + } + prev += n + peerId := idsCache.Find(buf[:prev]) + if peerId == nil { + continue + } + if hs == nil { + conf = confs[*peerId] + if conf == nil { + log.Println("Can not get peer configuration:", peerId.String()) break } - sizeNbuf += n - if sizeNbuf != 2 { - sink <- Pkt{ready: ready} - continue + hs = govpn.NewHandshake(addr, conn, conf) + } + peer = hs.Server(buf[:prev]) + prev = 0 + if peer == nil { + continue + } + hs.Zero() + log.Println("Peer handshake finished:", addr, peer.Id.String()) + peersByIdLock.RLock() + addrPrev, exists := peersById[*peer.Id] + peersByIdLock.RUnlock() + if exists { + peersLock.Lock() + peers[addrPrev].terminator <- struct{}{} + tap = peers[addrPrev].tap + ps = &PeerState{ + peer: peer, + tap: tap, + terminator: make(chan struct{}), } - sizeNeed = binary.BigEndian.Uint16(sizeBuf) - if int(sizeNeed) > govpn.MTU-2 { - log.Println("Invalid TCP size, skipping") - sizeNbuf = 0 - sink <- Pkt{ready: ready} - continue + go peerReady(*ps) + peersByIdLock.Lock() + kpLock.Lock() + delete(peers, addrPrev) + delete(knownPeers, addrPrev) + peers[addr] = ps + knownPeers[addr] = &peer + peersById[*peer.Id] = addr + peersLock.Unlock() + peersByIdLock.Unlock() + kpLock.Unlock() + log.Println("Rehandshake processed:", peer.Id.String()) + } else { + ifaceName, err := callUp(peer.Id) + if err != nil { + break } - bufN = 0 - } - ReadMore: - if sizeNeed != bufN { - n, err = conn.Read(buf[bufN:sizeNeed]) + tap, err = govpn.TAPListen(ifaceName) if err != nil { + log.Println("Unable to create TAP:", err) break } - bufN += uint16(n) - goto ReadMore + ps = &PeerState{ + peer: peer, + tap: tap, + terminator: make(chan struct{}, 1), + } + go peerReady(*ps) + peersLock.Lock() + peersByIdLock.Lock() + kpLock.Lock() + peers[addr] = ps + peersById[*peer.Id] = addr + knownPeers[addr] = &peer + peersLock.Unlock() + peersByIdLock.Unlock() + kpLock.Unlock() + log.Println("Peer created:", peer.Id.String()) + } + break + } + if hs != nil { + hs.Zero() + } + if peer == nil { + return + } + + nonceExpectation := make([]byte, govpn.NonceSize) + peer.NonceExpectation(nonceExpectation) + prev = 0 + var i int + for { + if prev == govpn.MTU { + break + } + conn.SetReadDeadline(time.Now().Add(conf.Timeout)) + n, err = conn.Read(buf[prev:]) + if err != nil { + // Either EOFed or timeouted + break + } + prev += n + CheckMore: + if prev < govpn.MinPktLength { + continue + } + i = bytes.Index(buf[:prev], nonceExpectation) + if i == -1 { + continue } - sizeNbuf = 0 - sink <- Pkt{ - addr, - TCPSender{conn}, - buf[:sizeNeed], - ready, + if !peer.PktProcess(buf[:i+govpn.NonceSize], tap, false) { + log.Println( + "Unauthenticated packet, dropping connection", + addr, peer.Id.String(), + ) + break } + peer.NonceExpectation(nonceExpectation) + copy(buf, buf[i+govpn.NonceSize:prev]) + prev = prev - i - govpn.NonceSize + goto CheckMore } + peer.Zero() } diff --git a/src/govpn/cmd/govpn-server/udp.go b/src/govpn/cmd/govpn-server/udp.go index c2d6e96..0f5e555 100644 --- a/src/govpn/cmd/govpn-server/udp.go +++ b/src/govpn/cmd/govpn-server/udp.go @@ -21,7 +21,6 @@ package main import ( "log" "net" - "time" "govpn" ) @@ -35,37 +34,161 @@ func (c UDPSender) Write(data []byte) (int, error) { return c.conn.WriteToUDP(data, c.addr) } -func startUDP(sink chan Pkt) { +var ( + // Buffers for UDP parallel processing + udpBufs chan []byte = make(chan []byte, 1<<8) +) + +func startUDP() { bind, err := net.ResolveUDPAddr("udp", *bindAddr) - ready := make(chan struct{}) if err != nil { log.Fatalln("Can not resolve bind address:", err) } - lconn, err := net.ListenUDP("udp", bind) + conn, err := net.ListenUDP("udp", bind) if err != nil { log.Fatalln("Can not listen on UDP:", err) } - log.Println("Listening on UDP", *bindAddr) + log.Println("Listening on UDP:" + *bindAddr) + + udpBufs <- make([]byte, govpn.MTU) go func() { - buf := make([]byte, govpn.MTU) - var n int + var buf []byte var raddr *net.UDPAddr + var addr string + var n int var err error + var ps *PeerState + var hs *govpn.Handshake + var addrPrev string + var exists bool + var peerId *govpn.PeerId + var peer *govpn.Peer + var conf *govpn.PeerConf for { - <-ready - lconn.SetReadDeadline(time.Now().Add(time.Second)) - n, raddr, err = lconn.ReadFromUDP(buf) + buf = <-udpBufs + n, raddr, err = conn.ReadFromUDP(buf) if err != nil { - sink <- Pkt{ready: ready} - continue + log.Println("Unexpected error when receiving", err) + break + } + addr = raddr.String() + + peersLock.RLock() + ps, exists = peers[addr] + peersLock.RUnlock() + if !exists { + goto CheckHandshake + } + go func(ps *govpn.Peer, tap *govpn.TAP, buf []byte, n int) { + peer.PktProcess(buf[:n], tap, true) + udpBufs <- buf + }(ps.peer, ps.tap, buf, n) + continue + CheckHandshake: + hsLock.RLock() + hs, exists = handshakes[addr] + hsLock.RUnlock() + if !exists { + goto CheckID + } + peer = hs.Server(buf[:n]) + if peer == nil { + goto Finished + } + + log.Println("Peer handshake finished:", addr, peer.Id.String()) + hs.Zero() + hsLock.Lock() + delete(handshakes, addr) + hsLock.Unlock() + + go func() { + udpBufs <- make([]byte, govpn.MTU) + udpBufs <- make([]byte, govpn.MTU) + }() + peersByIdLock.RLock() + addrPrev, exists = peersById[*peer.Id] + peersByIdLock.RUnlock() + if exists { + peersLock.Lock() + peers[addrPrev].terminator <- struct{}{} + ps = &PeerState{ + peer: peer, + tap: peers[addrPrev].tap, + terminator: make(chan struct{}), + } + go func(ps PeerState) { + peerReady(ps) + <-udpBufs + <-udpBufs + }(*ps) + peersByIdLock.Lock() + kpLock.Lock() + delete(peers, addrPrev) + delete(knownPeers, addrPrev) + peers[addr] = ps + knownPeers[addr] = &peer + peersById[*peer.Id] = addr + peersLock.Unlock() + peersByIdLock.Unlock() + kpLock.Unlock() + log.Println("Rehandshake processed:", peer.Id.String()) + } else { + go func(addr string, peer *govpn.Peer) { + ifaceName, err := callUp(peer.Id) + if err != nil { + return + } + tap, err := govpn.TAPListen(ifaceName) + if err != nil { + log.Println("Unable to create TAP:", err) + return + } + ps = &PeerState{ + peer: peer, + tap: tap, + terminator: make(chan struct{}), + } + go func(ps PeerState) { + peerReady(ps) + <-udpBufs + <-udpBufs + }(*ps) + peersLock.Lock() + peersByIdLock.Lock() + kpLock.Lock() + peers[addr] = ps + knownPeers[addr] = &peer + peersById[*peer.Id] = addr + peersLock.Unlock() + peersByIdLock.Unlock() + kpLock.Unlock() + log.Println("Peer created:", peer.Id.String()) + }(addr, peer) + } + goto Finished + CheckID: + peerId = idsCache.Find(buf[:n]) + if peerId == nil { + log.Println("Unknown identity from:", addr) + goto Finished } - sink <- Pkt{ - raddr.String(), - UDPSender{lconn, raddr}, - buf[:n], - ready, + conf = confs[*peerId] + if conf == nil { + log.Println("Unable to get peer configuration:", peerId.String()) + goto Finished } + hs = govpn.NewHandshake( + addr, + UDPSender{conn: conn, addr: raddr}, + conf, + ) + hs.Server(buf[:n]) + hsLock.Lock() + handshakes[addr] = hs + hsLock.Unlock() + Finished: + udpBufs <- buf } }() - ready <- struct{}{} } diff --git a/src/govpn/common.go b/src/govpn/common.go index fbafd56..898690d 100644 --- a/src/govpn/common.go +++ b/src/govpn/common.go @@ -23,6 +23,7 @@ import ( "os" "os/exec" "runtime" + "time" ) const ( @@ -52,13 +53,20 @@ func ScriptCall(path, ifaceName string) ([]byte, error) { return out, err } -// Zero each byte +// Zero each byte. func sliceZero(data []byte) { for i := 0; i < len(data); i++ { - data[i] = '\x00' + data[i] = 0 } } 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 new file mode 100644 index 0000000..0eff590 --- /dev/null +++ b/src/govpn/conf.go @@ -0,0 +1,42 @@ +/* +GoVPN -- simple secure free software virtual private network daemon +Copyright (C) 2014-2015 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package govpn + +import ( + "time" + + "github.com/agl/ed25519" +) + +type PeerConf struct { + Id *PeerId `json:"-"` + Name string `json:"name"` + Up string `json:"up"` + Down string `json:"down"` + TimeoutInt int `json:"timeout"` + Timeout time.Duration `json:"-"` + Noise bool `json:"noise"` + CPR int `json:"cpr"` + Verifier string `json:"verifier"` + + // This is passphrase verifier + DSAPub *[ed25519.PublicKeySize]byte `json:"-"` + // This field exists only on client's side + DSAPriv *[ed25519.PrivateKeySize]byte `json:"-"` +} diff --git a/src/govpn/govpn.go b/src/govpn/govpn.go index 9a7b114..996f31f 100644 --- a/src/govpn/govpn.go +++ b/src/govpn/govpn.go @@ -16,5 +16,5 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Simple secure free software virtual private network daemon +// Simple secure, DPI/censorship-resistant free software VPN daemon. package govpn diff --git a/src/govpn/handshake.go b/src/govpn/handshake.go index 3e3d0d6..0aa65bd 100644 --- a/src/govpn/handshake.go +++ b/src/govpn/handshake.go @@ -134,7 +134,7 @@ func dhKeyGen(priv, pub *[32]byte) *[32]byte { } // Create new handshake state. -func HandshakeNew(addr string, conn io.Writer, conf *PeerConf) *Handshake { +func NewHandshake(addr string, conn io.Writer, conf *PeerConf) *Handshake { state := Handshake{ addr: addr, conn: conn, @@ -162,8 +162,7 @@ func idTag(id *PeerId, data []byte) []byte { // for starting the handshake procedure. // First handshake packet // will be sent immediately. func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake { - state := HandshakeNew(addr, conn, conf) - + state := NewHandshake(addr, conn, conf) var dhPubRepr *[32]byte state.dhPriv, dhPubRepr = dhKeypairGen() @@ -171,8 +170,14 @@ func HandshakeStart(addr string, conn io.Writer, conf *PeerConf) *Handshake { if err := randRead(state.rNonce[:]); err != nil { log.Fatalln("Error reading random for nonce:", err) } - enc := make([]byte, 32) - salsa20.XORKeyStream(enc, dhPubRepr[:], state.rNonce[:], state.dsaPubH) + var enc []byte + if conf.Noise { + enc = make([]byte, MTU-xtea.BlockSize-RSize) + } else { + enc = make([]byte, 32) + } + copy(enc, dhPubRepr[:]) + 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) @@ -186,7 +191,7 @@ 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 len(data) == 48 && h.rNonce == nil { + if h.rNonce == nil { // Generate DH keypair var dhPubRepr *[32]byte h.dhPriv, dhPubRepr = dhKeypairGen() @@ -218,15 +223,21 @@ func (h *Handshake) Server(data []byte) *Peer { if err := randRead(h.sServer[:]); err != nil { log.Fatalln("Error reading random for S:", err) } - encRs := make([]byte, RSize+SSize) - salsa20.XORKeyStream(encRs, append(h.rServer[:], h.sServer[:]...), h.rNonce[:], h.key) + var encRs []byte + if h.Conf.Noise { + encRs = make([]byte, MTU-len(encPub)-xtea.BlockSize) + } else { + encRs = make([]byte, RSize+SSize) + } + copy(encRs, append(h.rServer[:], h.sServer[:]...)) + 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 len(data) == 120 && h.rClient == nil { + if h.rClient == nil { // Decrypted Rs compare rServer dec := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) salsa20.XORKeyStream( @@ -247,16 +258,22 @@ func (h *Handshake) Server(data []byte) *Peer { } // Send final answer to client - enc := make([]byte, RSize) - salsa20.XORKeyStream(enc, dec[RSize:RSize+RSize], h.rNonceNext(2), h.key) + var enc []byte + if h.Conf.Noise { + enc = make([]byte, MTU-xtea.BlockSize) + } else { + enc = make([]byte, RSize) + } + copy(enc, dec[RSize:RSize+RSize]) + salsa20.XORKeyStream(enc, enc, h.rNonceNext(2), h.key) h.conn.Write(append(enc, idTag(h.Conf.Id, enc)...)) // Switch peer peer := newPeer( + false, h.addr, h.conn, h.Conf, - 0, keyFromSecrets(h.sServer[:], dec[RSize+RSize:RSize+RSize+SSize])) h.LastPing = time.Now() return peer @@ -272,13 +289,8 @@ func (h *Handshake) Server(data []byte) *Peer { // will be created and used as a transport. If no mutually // authenticated Peer is ready, then return nil. func (h *Handshake) Client(data []byte) *Peer { - switch len(data) { - case 80: // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag - if h.key != nil { - log.Println("Invalid handshake stage from", h.addr) - return nil - } - + // ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag + if h.rServer == nil && h.key == nil { // Decrypt remote public key and compute shared key sDHRepr := new([32]byte) salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH) @@ -305,21 +317,24 @@ func (h *Handshake) Client(data []byte) *Peer { } sign := ed25519.Sign(h.Conf.DSAPriv, h.key[:]) - enc := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) - salsa20.XORKeyStream(enc, + var enc []byte + if h.Conf.Noise { + enc = make([]byte, MTU-xtea.BlockSize) + } else { + enc = make([]byte, RSize+RSize+SSize+ed25519.SignatureSize) + } + copy(enc, append(h.rServer[:], append(h.rClient[:], - append(h.sClient[:], sign[:]...)...)...), h.rNonceNext(1), h.key) + append(h.sClient[:], sign[:]...)...)...)) + 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() - case 16: // ENC(K, R+2, RC) + IDtag - if h.key == nil { - log.Println("Invalid handshake stage from", h.addr) - return nil - } - + } else + // ENC(K, R+2, RC) + IDtag + if h.key != nil { // Decrypt rClient dec := make([]byte, RSize) salsa20.XORKeyStream(dec, data[:RSize], h.rNonceNext(2), h.key) @@ -329,11 +344,17 @@ func (h *Handshake) Client(data []byte) *Peer { } // Switch peer - peer := newPeer(h.addr, h.conn, h.Conf, 1, keyFromSecrets(h.sServer[:], h.sClient[:])) + peer := newPeer( + true, + h.addr, + h.conn, + h.Conf, + keyFromSecrets(h.sServer[:], h.sClient[:]), + ) h.LastPing = time.Now() return peer - default: - log.Println("Invalid handshake message from", h.addr) + } else { + log.Println("Invalid handshake stage from", h.addr) } return nil } diff --git a/src/govpn/identify.go b/src/govpn/identify.go index c7675b6..a0b7d22 100644 --- a/src/govpn/identify.go +++ b/src/govpn/identify.go @@ -22,22 +22,14 @@ import ( "crypto/subtle" "encoding/hex" "errors" - "io/ioutil" "log" - "os" - "path" - "strconv" - "strings" "sync" - "time" - "github.com/agl/ed25519" "golang.org/x/crypto/xtea" ) const ( - IDSize = 128 / 8 - RefreshRate = 60 * time.Second + IDSize = 128 / 8 ) type PeerId [IDSize]byte @@ -46,187 +38,76 @@ func (id PeerId) String() string { return hex.EncodeToString(id[:]) } -// Return human readable name of the peer. -// It equals either to peers/PEER/name file contents or PEER's hex. -func (id PeerId) MarshalJSON() ([]byte, error) { - result := id.String() - if name, err := ioutil.ReadFile(path.Join(PeersPath, result, "name")); err == nil { - result = strings.TrimRight(string(name), "\n") +// Decode identification string. +// It must be 32 hexadecimal characters long. +func IDDecode(raw string) (*PeerId, error) { + if len(raw) != IDSize*2 { + return nil, errors.New("ID must be 32 characters long") + } + idDecoded, err := hex.DecodeString(raw) + if err != nil { + return nil, errors.New("ID must contain hexadecimal characters only") } - return []byte(`"` + result + `"`), nil + idP := new([IDSize]byte) + copy(idP[:], idDecoded) + id := PeerId(*idP) + return &id, nil } -type PeerConf struct { - Id *PeerId - Timeout time.Duration - NoiseEnable bool - CPR int - // This is passphrase verifier - DSAPub *[ed25519.PublicKeySize]byte - // This field exists only in dummy configuration on client's side - DSAPriv *[ed25519.PrivateKeySize]byte +type CipherCache struct { + c map[PeerId]*xtea.Cipher + l sync.RWMutex } -type cipherCache map[PeerId]*xtea.Cipher - -var ( - PeersPath string - IDsCache cipherCache - cipherCacheLock sync.RWMutex - dummyConf *PeerConf -) - -// Initialize (pre-cache) available peers info. -func PeersInit(path string) { - PeersPath = path - IDsCache = make(map[PeerId]*xtea.Cipher) - go func() { - for { - IDsCache.refresh() - time.Sleep(RefreshRate) - } - }() +func NewCipherCache(peerIds []PeerId) CipherCache { + cc := CipherCache{c: make(map[PeerId]*xtea.Cipher, len(peerIds))} + cc.Update(peerIds) + return cc } -// Initialize dummy cache for client-side usage. -func PeersInitDummy(id *PeerId, conf *PeerConf) { - IDsCache = make(map[PeerId]*xtea.Cipher) - cipher, err := xtea.NewCipher(id[:]) - if err != nil { - panic(err) - } - IDsCache[*id] = cipher - dummyConf = conf -} - -// Refresh IDsCache: remove disappeared keys, add missing ones with -// initialized ciphers. -func (cc cipherCache) refresh() { - dir, err := os.Open(PeersPath) - if err != nil { - panic(err) - } - peerIds, err := dir.Readdirnames(0) - if err != nil { - panic(err) - } - available := make(map[PeerId]bool) +// Remove disappeared keys, add missing ones with initialized ciphers. +func (cc CipherCache) Update(peerIds []PeerId) { + available := make(map[PeerId]struct{}) for _, peerId := range peerIds { - id, err := IDDecode(peerId) - if err != nil { - continue - } - available[*id] = true + available[peerId] = struct{}{} } - - cipherCacheLock.Lock() - // Cleanup deleted ones from cache - for k, _ := range cc { + cc.l.Lock() + for k, _ := range cc.c { if _, exists := available[k]; !exists { - delete(cc, k) - log.Println("Cleaning key: ", k) + log.Println("Cleaning key:", k) + delete(cc.c, k) } } - // Add missing ones for peerId, _ := range available { - if _, exists := cc[peerId]; !exists { + if _, exists := cc.c[peerId]; !exists { log.Println("Adding key", peerId) cipher, err := xtea.NewCipher(peerId[:]) if err != nil { panic(err) } - cc[peerId] = cipher + cc.c[peerId] = cipher } } - cipherCacheLock.Unlock() + cc.l.Unlock() } // 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 } buf := make([]byte, xtea.BlockSize) - cipherCacheLock.RLock() - for pid, cipher := range cc { + cc.l.RLock() + for pid, cipher := range cc.c { cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:]) if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 { ppid := PeerId(pid) - cipherCacheLock.RUnlock() + cc.l.RUnlock() return &ppid } } - cipherCacheLock.RUnlock() + cc.l.RUnlock() return nil } - -func readIntFromFile(path string) (int, error) { - data, err := ioutil.ReadFile(path) - if err != nil { - return 0, err - } - val, err := strconv.Atoi(strings.TrimRight(string(data), "\n")) - if err != nil { - return 0, err - } - return val, nil -} - -// Get peer related configuration. -func (id *PeerId) Conf() *PeerConf { - if dummyConf != nil { - return dummyConf - } - conf := PeerConf{Id: id, NoiseEnable: false, CPR: 0} - peerPath := path.Join(PeersPath, id.String()) - - verPath := path.Join(peerPath, "verifier") - keyData, err := ioutil.ReadFile(verPath) - if err != nil { - log.Println("Unable to read verifier:", verPath) - return nil - } - if len(keyData) < ed25519.PublicKeySize*2 { - log.Println("Verifier must be 64 hex characters long:", verPath) - return nil - } - keyDecoded, err := hex.DecodeString(string(keyData[:ed25519.PublicKeySize*2])) - if err != nil { - log.Println("Unable to decode the key:", err.Error(), verPath) - return nil - } - conf.DSAPub = new([ed25519.PublicKeySize]byte) - copy(conf.DSAPub[:], keyDecoded) - - timeout := TimeoutDefault - if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil { - timeout = val - } - conf.Timeout = time.Second * time.Duration(timeout) - - if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 { - conf.NoiseEnable = true - } - if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil { - conf.CPR = val - } - return &conf -} - -// Decode identification string. -// It must be 32 hexadecimal characters long. -func IDDecode(raw string) (*PeerId, error) { - if len(raw) != IDSize*2 { - return nil, errors.New("ID must be 32 characters long") - } - idDecoded, err := hex.DecodeString(raw) - if err != nil { - return nil, errors.New("ID must contain hexadecimal characters only") - } - idP := new([IDSize]byte) - copy(idP[:], idDecoded) - id := PeerId(*idP) - return &id, nil -} diff --git a/src/govpn/peer.go b/src/govpn/peer.go new file mode 100644 index 0000000..1076e74 --- /dev/null +++ b/src/govpn/peer.go @@ -0,0 +1,316 @@ +package govpn + +import ( + "encoding/binary" + "io" + "sync" + "sync/atomic" + "time" + + "golang.org/x/crypto/poly1305" + "golang.org/x/crypto/salsa20" + "golang.org/x/crypto/xtea" +) + +const ( + NonceSize = 8 + NonceBucketSize = 128 + TagSize = poly1305.TagSize + // S20BS is Salsa20's internal blocksize in bytes + 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 +) + +func newNonceCipher(key *[32]byte) *xtea.Cipher { + nonceKey := make([]byte, 16) + salsa20.XORKeyStream( + nonceKey, + make([]byte, 32), + make([]byte, xtea.BlockSize), + key, + ) + ciph, err := xtea.NewCipher(nonceKey) + if err != nil { + panic(err) + } + return ciph +} + +type Peer struct { + Addr string + Id *PeerId + Conn io.Writer + + // Traffic behaviour + NoiseEnable bool + CPR int + CPRCycle time.Duration `json:"-"` + + // Cryptography related + Key *[SSize]byte `json:"-"` + NonceCipher *xtea.Cipher `json:"-"` + nonceRecv uint64 + nonceLatest uint64 + nonceOur uint64 + NonceExpect uint64 `json:"-"` + nonceBucket0 map[uint64]struct{} + nonceBucket1 map[uint64]struct{} + nonceFound0 bool + nonceFound1 bool + nonceBucketN int32 + + // Timers + Timeout time.Duration `json:"-"` + Established time.Time + LastPing time.Time + LastSent time.Time + willSentCycle time.Time + + // Statistics + BytesIn int64 + BytesOut int64 + BytesPayloadIn int64 + BytesPayloadOut int64 + FramesIn int + FramesOut int + FramesUnauth int + FramesDup int + HeartbeatRecv int + HeartbeatSent int + + // Receiver + BusyR sync.Mutex `json:"-"` + bufR []byte + tagR *[TagSize]byte + keyAuthR *[SSize]byte + pktSizeR uint16 + + // Transmitter + BusyT sync.Mutex `json:"-"` + bufT []byte + tagT *[TagSize]byte + keyAuthT *[SSize]byte + frameT []byte + now time.Time +} + +func (p *Peer) String() string { + return p.Id.String() + ":" + p.Addr +} + +// Zero peer's memory state. +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[:]) + p.BusyT.Unlock() + p.BusyR.Unlock() +} + +func (p *Peer) NonceExpectation(buf []byte) { + binary.BigEndian.PutUint64(buf, p.NonceExpect) + p.NonceCipher.Encrypt(buf, buf) +} + +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) + noiseEnable := conf.Noise + if conf.CPR > 0 { + noiseEnable = true + timeout = cprCycle + } else { + timeout = timeout / TimeoutHeartbeat + } + + peer := Peer{ + Addr: addr, + Id: conf.Id, + Conn: conn, + + NoiseEnable: noiseEnable, + CPR: conf.CPR, + CPRCycle: cprCycle, + + Key: key, + NonceCipher: newNonceCipher(key), + nonceBucket0: make(map[uint64]struct{}, NonceBucketSize), + nonceBucket1: make(map[uint64]struct{}, NonceBucketSize), + + Timeout: timeout, + Established: now, + LastPing: now, + + bufR: make([]byte, S20BS+MTU+NonceSize), + bufT: make([]byte, S20BS+MTU+NonceSize), + tagR: new([TagSize]byte), + tagT: new([TagSize]byte), + keyAuthR: new([SSize]byte), + keyAuthT: new([SSize]byte), + } + if isClient { + peer.nonceOur = 1 + peer.NonceExpect = 0 + 2 + } else { + peer.nonceOur = 0 + peer.NonceExpect = 1 + 2 + } + return &peer + +} + +// Process incoming Ethernet packet. +// ready channel is TAPListen's synchronization channel used to tell him +// that he is free to receive new packets. Encrypted and authenticated +// packets will be sent to remote Peer side immediately. +func (p *Peer) EthProcess(data []byte) { + p.now = time.Now() + p.BusyT.Lock() + + // Zero size is a heartbeat packet + 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.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) + p.BytesPayloadOut += int64(len(data)) + } + + if p.NoiseEnable { + p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize] + } else { + p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize] + } + p.nonceOur += 2 + binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur) + p.NonceCipher.Encrypt( + p.frameT[len(p.frameT)-NonceSize:], + p.frameT[len(p.frameT)-NonceSize:], + ) + for i := 0; i < SSize; i++ { + p.bufT[i] = byte(0) + } + 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) { + p.willSentCycle = p.LastSent.Add(p.CPRCycle) + if p.willSentCycle.After(p.now) { + time.Sleep(p.willSentCycle.Sub(p.now)) + p.now = p.willSentCycle + } + } + + p.LastSent = p.now + p.Conn.Write(append(p.tagT[:], p.frameT...)) + p.BusyT.Unlock() +} + +func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool { + 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() + return false + } + + // Check if received nonce is known to us in either of two buckets. + // If yes, then this is ignored duplicate. + // Check from the oldest bucket, as in most cases this will result + // in constant time check. + // If Bucket0 is filled, then it becomes Bucket1. + p.NonceCipher.Decrypt( + data[len(data)-NonceSize:], + data[len(data)-NonceSize:], + ) + p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:]) + if reorderable { + _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv] + _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv] + if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest { + p.FramesDup++ + p.BusyR.Unlock() + return false + } + p.nonceBucket0[p.nonceRecv] = struct{}{} + p.nonceBucketN++ + if p.nonceBucketN == NonceBucketSize { + p.nonceBucket1 = p.nonceBucket0 + p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize) + p.nonceBucketN = 0 + } + } else { + if p.nonceRecv != p.NonceExpect { + p.FramesDup++ + p.BusyR.Unlock() + return false + } + p.NonceExpect += 2 + } + if p.nonceRecv > p.nonceLatest { + p.nonceLatest = p.nonceRecv + } + + p.FramesIn++ + atomic.AddInt64(&p.BytesIn, int64(len(data))) + p.LastPing = time.Now() + p.pktSizeR = binary.BigEndian.Uint16(p.bufR[S20BS : S20BS+PktSizeSize]) + + if p.pktSizeR == 0 { + p.HeartbeatRecv++ + p.BusyR.Unlock() + return true + } + p.BytesPayloadIn += int64(p.pktSizeR) + tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR]) + p.BusyR.Unlock() + return true +} diff --git a/src/govpn/transport_test.go b/src/govpn/peer_test.go similarity index 65% rename from src/govpn/transport_test.go rename to src/govpn/peer_test.go index 5809a9f..9bad8c6 100644 --- a/src/govpn/transport_test.go +++ b/src/govpn/peer_test.go @@ -8,13 +8,12 @@ import ( var ( peer *Peer plaintext []byte - ready chan struct{} ciphertext []byte peerId *PeerId conf *PeerConf ) -type Dummy struct{ +type Dummy struct { dst *[]byte } @@ -34,32 +33,29 @@ func init() { NoiseEnable: false, CPR: 0, } - peer = newPeer("foo", Dummy{&ciphertext}, conf, 128, new([SSize]byte)) + peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte)) plaintext = make([]byte, 789) - ready = make(chan struct{}) - go func() { - for { - <-ready - } - }() } func BenchmarkEnc(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - peer.NonceOur = 128 - peer.EthProcess(plaintext, ready) + peer.EthProcess(plaintext) } } func BenchmarkDec(b *testing.B) { - peer.EthProcess(plaintext, ready) - peer = newPeer("foo", Dummy{nil}, conf, 128, new([SSize]byte)) + 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) b.ResetTimer() for i := 0; i < b.N; i++ { peer.nonceBucket0 = make(map[uint64]struct{}, 1) peer.nonceBucket1 = make(map[uint64]struct{}, 1) - if !peer.PktProcess(ciphertext, Dummy{nil}, ready) { + copy(ciphertext, orig) + if !peer.PktProcess(ciphertext, Dummy{nil}, true) { b.Fail() } } diff --git a/src/govpn/tap.go b/src/govpn/tap.go index c5e3bf8..9c999b4 100644 --- a/src/govpn/tap.go +++ b/src/govpn/tap.go @@ -29,14 +29,18 @@ const ( ) type TAP struct { - Name string - dev io.ReadWriter - buf []byte - sink chan []byte - ready chan struct{} - synced bool + Name string + Sink chan []byte + dev io.ReadWriter + buf0 []byte + buf1 []byte + bufZ bool } +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 { @@ -50,23 +54,28 @@ func NewTAP(ifaceName string) (*TAP, error) { return nil, err } tap := TAP{ - Name: ifaceName, - dev: tapRaw, - buf: make([]byte, maxIfacePktSize), - sink: make(chan []byte), - ready: make(chan struct{}), - synced: false, + Name: ifaceName, + dev: tapRaw, + buf0: make([]byte, maxIfacePktSize), + buf1: make([]byte, maxIfacePktSize), + Sink: make(chan []byte), } go func() { var n int var err error + var buf []byte for { - <-tap.ready - n, err = tap.dev.Read(tap.buf) + if tap.bufZ { + buf = tap.buf0 + } else { + buf = tap.buf1 + } + tap.bufZ = !tap.bufZ + n, err = tap.dev.Read(buf) if err != nil { panic("Reading TAP:" + err.Error()) } - tap.sink <- tap.buf[:n] + tap.Sink <- buf[:n] } }() return &tap, nil @@ -75,3 +84,16 @@ func NewTAP(ifaceName string) (*TAP, error) { func (t *TAP) Write(data []byte) (n int, err error) { return t.dev.Write(data) } + +func TAPListen(ifaceName string) (*TAP, error) { + tap, exists := taps[ifaceName] + if exists { + return tap, nil + } + tap, err := NewTAP(ifaceName) + if err != nil { + return nil, err + } + taps[ifaceName] = tap + return tap, nil +} diff --git a/src/govpn/transport.go b/src/govpn/transport.go deleted file mode 100644 index 23f04dd..0000000 --- a/src/govpn/transport.go +++ /dev/null @@ -1,348 +0,0 @@ -/* -GoVPN -- simple secure free software virtual private network daemon -Copyright (C) 2014-2015 Sergey Matveev - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package govpn - -import ( - "encoding/binary" - "io" - "time" - - "golang.org/x/crypto/poly1305" - "golang.org/x/crypto/salsa20" - "golang.org/x/crypto/xtea" -) - -const ( - NonceSize = 8 - NonceBucketSize = 128 - // S20BS is Salsa20's internal blocksize in bytes - 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 -) - -type Peer struct { - Addr string - Id *PeerId - Conn io.Writer - - // Traffic behaviour - NoiseEnable bool - CPR int - CPRCycle time.Duration `json:"-"` - - // Cryptography related - Key *[SSize]byte `json:"-"` - NonceOur uint64 `json:"-"` - NonceRecv uint64 `json:"-"` - NonceCipher *xtea.Cipher `json:"-"` - nonceBucket0 map[uint64]struct{} - nonceBucket1 map[uint64]struct{} - nonceFound bool - nonceBucketN int32 - - // Timers - Timeout time.Duration `json:"-"` - Established time.Time - LastPing time.Time - LastSent time.Time - willSentCycle time.Time - - // This variables are initialized only once to relief GC - buf []byte - tag *[poly1305.TagSize]byte - keyAuth *[32]byte - nonceRecv uint64 - frame []byte - nonce []byte - pktSize uint64 - size int - now time.Time - - // Statistics - BytesIn int64 - BytesOut int64 - BytesPayloadIn int64 - BytesPayloadOut int64 - FramesIn int - FramesOut int - FramesUnauth int - FramesDup int - HeartbeatRecv int - HeartbeatSent int -} - -func (p *Peer) String() string { - return p.Id.String() + ":" + p.Addr -} - -// Zero peer's memory state. -func (p *Peer) Zero() { - sliceZero(p.Key[:]) - sliceZero(p.tag[:]) - sliceZero(p.keyAuth[:]) - sliceZero(p.buf) - sliceZero(p.frame) - sliceZero(p.nonce) -} - -var ( - Emptiness = make([]byte, 1<<14) - taps = make(map[string]*TAP) -) - -// Create TAP listening goroutine. -// This function takes required TAP interface name, opens it and allocates -// a buffer where all frame data will be written, channel where information -// about number of read bytes is sent to, synchronization channel (external -// processes tell that read buffer can be used again) and possible channel -// opening error. -func TAPListen(ifaceName string, timeout time.Duration, cpr int) (*TAP, chan []byte, chan struct{}, chan struct{}, error) { - var tap *TAP - var err error - tap, exists := taps[ifaceName] - if !exists { - tap, err = NewTAP(ifaceName) - if err != nil { - return nil, nil, nil, nil, err - } - taps[ifaceName] = tap - } - sink := make(chan []byte) - sinkReady := make(chan struct{}) - sinkTerminate := make(chan struct{}) - sinkSkip := make(chan struct{}) - - go func() { - cprCycle := cprCycleCalculate(cpr) - if cprCycle != time.Duration(0) { - timeout = cprCycle - } else { - timeout = timeout / TimeoutHeartbeat - } - heartbeat := time.Tick(timeout) - var pkt []byte - ListenCycle: - for { - select { - case <-sinkTerminate: - break ListenCycle - case <-heartbeat: - go func() { sink <- make([]byte, 0) }() - continue - case <-sinkSkip: - case <-sinkReady: - tap.ready <- struct{}{} - tap.synced = true - } - HeartbeatCatched: - select { - case <-heartbeat: - go func() { sink <- make([]byte, 0) }() - goto HeartbeatCatched - case <-sinkTerminate: - break ListenCycle - case pkt = <-tap.sink: - tap.synced = false - sink <- pkt - } - } - close(sink) - close(sinkReady) - close(sinkTerminate) - }() - if exists && tap.synced { - sinkSkip <- struct{}{} - } else { - sinkReady <- struct{}{} - } - return tap, sink, sinkReady, sinkTerminate, nil -} - -func newNonceCipher(key *[32]byte) *xtea.Cipher { - nonceKey := make([]byte, 16) - salsa20.XORKeyStream( - nonceKey, - make([]byte, 32), - make([]byte, xtea.BlockSize), - key, - ) - ciph, err := xtea.NewCipher(nonceKey) - if err != nil { - panic(err) - } - return ciph -} - -func cprCycleCalculate(rate int) time.Duration { - if rate == 0 { - return time.Duration(0) - } - return time.Second / time.Duration(rate*(1<<10)/MTU) -} - -func newPeer(addr string, conn io.Writer, conf *PeerConf, nonce int, key *[SSize]byte) *Peer { - now := time.Now() - timeout := conf.Timeout - cprCycle := cprCycleCalculate(conf.CPR) - noiseEnable := conf.NoiseEnable - if conf.CPR > 0 { - noiseEnable = true - timeout = cprCycle - } else { - timeout = timeout / TimeoutHeartbeat - } - peer := Peer{ - Addr: addr, - Conn: conn, - Timeout: timeout, - Established: now, - LastPing: now, - Id: conf.Id, - NoiseEnable: noiseEnable, - CPR: conf.CPR, - CPRCycle: cprCycle, - NonceOur: uint64(nonce), - NonceRecv: uint64(0), - nonceBucket0: make(map[uint64]struct{}, NonceBucketSize), - nonceBucket1: make(map[uint64]struct{}, NonceBucketSize), - Key: key, - NonceCipher: newNonceCipher(key), - buf: make([]byte, MTU+S20BS), - tag: new([poly1305.TagSize]byte), - keyAuth: new([SSize]byte), - nonce: make([]byte, NonceSize), - } - return &peer -} - -// Process incoming UDP packet. -// ConnListen'es synchronization channel used to tell him that he is -// free to receive new packets. Authenticated and decrypted packets -// will be written to the interface immediately (except heartbeat ones). -func (p *Peer) PktProcess(data []byte, tap io.Writer, ready chan struct{}) bool { - p.size = len(data) - copy(p.buf, Emptiness) - copy(p.tag[:], data[p.size-poly1305.TagSize:]) - copy(p.buf[S20BS:], data[NonceSize:p.size-poly1305.TagSize]) - salsa20.XORKeyStream( - p.buf[:S20BS+p.size-poly1305.TagSize], - p.buf[:S20BS+p.size-poly1305.TagSize], - data[:NonceSize], - p.Key, - ) - copy(p.keyAuth[:], p.buf[:SSize]) - if !poly1305.Verify(p.tag, data[:p.size-poly1305.TagSize], p.keyAuth) { - ready <- struct{}{} - p.FramesUnauth++ - return false - } - - // Check if received nonce is known to us in either of two buckets. - // If yes, then this is ignored duplicate. - // Check from the oldest bucket, as in most cases this will result - // in constant time check. - // If Bucket0 is filled, then it becomes Bucket1. - p.NonceCipher.Decrypt(p.buf, data[:NonceSize]) - ready <- struct{}{} - p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize]) - if _, p.nonceFound = p.nonceBucket1[p.NonceRecv]; p.nonceFound { - p.FramesDup++ - return false - } - if _, p.nonceFound = p.nonceBucket0[p.NonceRecv]; p.nonceFound { - p.FramesDup++ - return false - } - p.nonceBucket0[p.NonceRecv] = struct{}{} - p.nonceBucketN++ - if p.nonceBucketN == NonceBucketSize { - p.nonceBucket1 = p.nonceBucket0 - p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize) - p.nonceBucketN = 0 - } - - p.FramesIn++ - p.BytesIn += int64(p.size) - p.LastPing = time.Now() - p.NonceRecv = p.nonceRecv - p.pktSize, _ = binary.Uvarint(p.buf[S20BS : S20BS+PktSizeSize]) - if p.pktSize == 0 { - p.HeartbeatRecv++ - return true - } - p.frame = p.buf[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSize] - p.BytesPayloadIn += int64(p.pktSize) - tap.Write(p.frame) - return true -} - -// Process incoming Ethernet packet. -// ready channel is TAPListen's synchronization channel used to tell him -// that he is free to receive new packets. Encrypted and authenticated -// packets will be sent to remote Peer side immediately. -func (p *Peer) EthProcess(data []byte, ready chan struct{}) { - p.now = time.Now() - p.size = len(data) - // If this heartbeat is necessary - if p.size == 0 && !p.LastSent.Add(p.Timeout).Before(p.now) { - return - } - copy(p.buf, Emptiness) - if p.size > 0 { - copy(p.buf[S20BS+PktSizeSize:], data) - ready <- struct{}{} - binary.PutUvarint(p.buf[S20BS:S20BS+PktSizeSize], uint64(p.size)) - p.BytesPayloadOut += int64(p.size) - } else { - p.HeartbeatSent++ - } - - p.NonceOur += 2 - copy(p.nonce, Emptiness) - binary.PutUvarint(p.nonce, p.NonceOur) - p.NonceCipher.Encrypt(p.nonce, p.nonce) - - salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key) - copy(p.buf[S20BS-NonceSize:S20BS], p.nonce) - copy(p.keyAuth[:], p.buf[:SSize]) - if p.NoiseEnable { - p.frame = p.buf[S20BS-NonceSize : S20BS+MTU-NonceSize-poly1305.TagSize] - } else { - p.frame = p.buf[S20BS-NonceSize : S20BS+PktSizeSize+p.size] - } - poly1305.Sum(p.tag, p.frame, p.keyAuth) - - p.BytesOut += int64(len(p.frame) + poly1305.TagSize) - p.FramesOut++ - - if p.CPRCycle != time.Duration(0) { - p.willSentCycle = p.LastSent.Add(p.CPRCycle) - if p.willSentCycle.After(p.now) { - time.Sleep(p.willSentCycle.Sub(p.now)) - p.now = p.willSentCycle - } - } - p.LastSent = p.now - p.Conn.Write(append(p.frame, p.tag[:]...)) -} diff --git a/utils/makedist.sh b/utils/makedist.sh index 00ba9ac..10f18d9 100755 --- a/utils/makedist.sh +++ b/utils/makedist.sh @@ -1,5 +1,7 @@ #!/bin/sh -ex +[ -n "$SHA256" ] || SHA256=sha256 + cur=$(pwd) tmp=$(mktemp -d) release=$1 @@ -31,7 +33,7 @@ mv $tmp/golang.org src/ rm -fr $tmp/golang.org $tmp/includes cat > doc/download.texi < http://www.cypherpunks.ru/govpn/ +also available as Tor hidden service: http://vabu56j2ep2rwv3b.onion/govpn/ + +Source code and its signature for that version can be found here: + + http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz ($size KiB) + http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz.sig + +SHA256 hash: $hash +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/ +EOF + +cat < http://www.cypherpunks.ru/govpn/ +также доступна как скрытый сервис Tor: http://vabu56j2ep2rwv3b.onion/govpn/ + +Исходный код и его подпись для этой версии находится здесь: + + http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz ($size KiB) + http://www.cypherpunks.ru/govpn/download/govpn-${release}.tar.xz.sig + +SHA256 хэш: $hash +Идентификатор GPG ключа: 0xF2F59045FFE2F4A1 GoVPN release signing key +Отпечаток: D269 9B73 3C41 2068 D8DA 656E F2F5 9045 FFE2 F4A1 + +Пожалуйста все вопросы касающиеся использования GoVPN, отчёты об ошибках +и патчи отправляйте в govpn-devel почтовую рассылку: +https://lists.cypherpunks.ru/mailman/listinfo/govpn-devel/ +EOF diff --git a/utils/newclient.sh b/utils/newclient.sh index e741130..9e1cb35 100755 --- a/utils/newclient.sh +++ b/utils/newclient.sh @@ -1,16 +1,10 @@ #!/bin/sh -e -getrand() -{ - local size=$1 - dd if=/dev/urandom bs=$size count=1 2>/dev/null | hexdump -ve '"%02x"' -} - [ -n "$1" ] || { cat < EOF @@ -18,11 +12,41 @@ EOF } username=$1 -peerid=$(getrand 16) +peerid=$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | hexdump -ve '"%02x"') +[ $(echo -n $peerid | wc -c) = 32 ] || peerid=0"$peerid" umask 077 -mkdir -p peers/$peerid -echo '0000000000000000000000000000000000000000000000000000000000000000' > peers/$peerid/verifier -echo $username > peers/$peerid/name -echo '#!/bin/sh' > peers/$peerid/up.sh -chmod 700 peers/$peerid/up.sh -echo Place verifier to peers/$peerid/verifier +passphrase=$(mktemp) +$(dirname $0)/storekey.sh $passphrase +verifier=$(govpn-verifier -id $peerid -key $passphrase) +rm -f $passphrase +echo + +cat <