[submodule "src/golang.org/x/crypto"]
path = src/golang.org/x/crypto
url = https://go.googlesource.com/crypto
+[submodule "src/github.com/magical/argon2"]
+ path = src/github.com/magical/argon2
+ url = https://github.com/magical/argon2.git
+[submodule "src/github.com/dchest/blake2b"]
+ path = src/github.com/dchest/blake2b
+ url = https://github.com/dchest/blake2b.git
GoVPN это простой демон виртуальных частных сетей, код которого нацелен
на лёгкость чтения и анализа, безопасность, устойчивость к DPI/цензуре.
-@itemize @bullet
+@itemize
@item
Свободное программное обеспечение, копилефт: лицензировано под условиями
@url{https://www.gnu.org/licenses/gpl-3.0.ru.html, GPLv3+}.
Encrypted Key Exchange)).
@item
Несбалансированные аутентификационные токены устойчивые к внесетевым
-(offline) атакам по словарю. Злоумышленник не может замаскироваться под
+(offline) атакам по словарю. Используют усиленный по CPU и памяти
+алгоритм хэширования. Злоумышленник не может замаскироваться под
клиента даже скомпрометировав базу данных токенов сервера.
@item
Зашифрованный и аутентифицируемый транспортный протокол передачи данных
aimed to be reviewable, secure and
@url{https://en.wikipedia.org/wiki/Deep_packet_inspection, DPI}/censorship-resistant.
-@itemize @bullet
+@itemize
@item
Copylefted free software: licenced under
@url{https://www.gnu.org/licenses/gpl-3.0.html, GPLv3+}.
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.
+offline dictionary attacks. They use CPU and memory hardened hashing
+algorithm. 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
@item -iface
TAP interface name.
-@item -id
-Our client's @ref{Identity} (hexadecimal string).
+@item -verifier
+Our client's @ref{Verifier}.
@item -key
Path to the file with the passphrase. See @ref{Verifier} for
and @url{http://ed25519.cr.yp.to/, Ed25519}.
@item DH elliptic-curve point encoding for public keys
@url{http://elligator.cr.yp.to/, Elligator}.
-@item Key derivation function for verifier generation
-@url{https://en.wikipedia.org/wiki/PBKDF2, PBKDF2} based on
-@url{https://en.wikipedia.org/wiki/SHA-2, SHA-512}.
+@item Verifier password hashing algorithm
+@url{https://password-hashing.net/#argon2, Argon2d}.
@item Packet overhead
26 bytes per packet.
@item Handshake overhead
@multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
@headitem Version @tab Size @tab Tarball @tab SHA256 checksum
+@item 4.0 @tab 183 KiB
+@tab @url{download/govpn-4.0.tar.xz, link} @url{download/govpn-4.0.tar.xz.sig, sign}
+@tab @code{a791c3569c01dea8b18aa2f21d27b797ded76f2c33a8d96c2db864a9abf2615b}
+
@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}
Let's assume that there is some insecure link between your computer and
WiFi-reachable gateway.
-@itemize @bullet
+@itemize
@item You have got @code{wlan0} NIC with 192.168.0/24 network on it.
@item You want to create virtual encrypted and authenticated 172.16.0/24
network and use it as a default transport.
@strong{Install}. At first you must @ref{Installation, install} this
software: download, @ref{Integrity, check the signature}, compile.
-@strong{Prepare the client}. Generate client's identity and verifier for
-Alice as an example:
+@strong{Prepare the client}. Generate client's verifier for Alice as an
+example:
+
@verbatim
client% ./utils/newclient.sh Alice
Enter passphrase:
-Your id is: 7012df29deee2170594119df5091d4a2
+Your client verifier is: $argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg
Place the following JSON configuration entry on the server's side:
- "7012df29deee2170594119df5091d4a2": {
- "name": "Alice",
+ "Alice": {
"up": "/path/to/up.sh",
- "verifier": "fb43255ca3fe5bd884e364e5eae0cd37ad14774930a027fd38d8938fd0b57425"
+ "verifier": "$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg$KCNIqfS4DGsBTtVytamAzcISgrlEWvNxan1UfBrFu10"
}
Verifier was generated with:
./utils/storekey.sh /tmp/passphrase
- govpn-verifier -id 7012df29deee2170594119df5091d4a2 -key /tmp/passphrase
+ govpn-verifier -key /tmp/passphrase
@end verbatim
@strong{Prepare the server}. Add this entry to @code{peers.json}
@example
client% govpn-client \
-key key.txt \
- -id 906e34b98750c4f686d6c5489508763c \
+ -verifier '$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg' \
-iface tap10 \
-remote 192.168.0.1:1194 \
-mtu 1472
client% route -6 add default fc00::1
client% govpn-client \
-key key.txt \
- -id 906e34b98750c4f686d6c5489508763c \
+ -verifier '$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg' \
-iface tap10 \
-remote "[fe80::1%me0]":1194
@end example
@verb{|R + enc(H(DSAPub), R, El(CDHPub)) + IDtag -> Server|} [48 bytes]
@item
-@itemize @bullet
+@itemize
@item Server remembers client address.
@item Decrypts @code{El(CDHPub)}.
@item Inverts @code{El()} encoding and gets @code{CDHPub}.
@verb{|enc(H(DSAPub), R+1, El(SDHPub)) + enc(K, R, RS + SS) + IDtag -> Client|} [80 bytes]
@item
-@itemize @bullet
+@itemize
@item Client decrypts @code{El(SDHPub)}.
@item Inverts @code{El()} encoding and gets @code{SDHPub}.
@item Computes @code{K}.
@verb{|enc(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag -> Server|} [120 bytes]
@item
-@itemize @bullet
+@itemize
@item Server decrypts @code{RS}, @code{RC}, @code{SC},
@code{Sign(DSAPriv, K)}.
@verb{|ENC(K, R+2, RC) + IDtag -> Client|} [16 bytes]
@item
-@itemize @bullet
+@itemize
@item Client decrypts @code{RC}
@item Compares with its own one sent before.
@item Computes final session encryption key as server did.
Client's identity is 128-bit string. It is not secret, so can be
transmitted and stored in the clear. However handshake applies PRP on it
-to make DPI and deanonymization much harder to success.
-
-@example
-% ./utils/newclient.sh doesnotmatter
-Your id is: 7012df29deee2170594119df5091d4a2
-@end example
-
-@code{7012df29deee2170594119df5091d4a2} is client's identity.
+to make DPI and deanonymization much harder to success. It is used as a
+salt in verifier.
Possibly GoVPN already exists in your distribution:
-@itemize @bullet
+@itemize
@item @url{https://aur.archlinux.org/packages/govpn/, AUR}
@item @url{http://www.freshports.org/security/govpn/, FreeBSD ports}
@end itemize
@headitem Library @tab Platform @tab Licence
@item @code{golang.org/x/crypto} @tab All @tab BSD 3-Clause
@item @code{github.com/agl/ed25519} @tab All @tab BSD 3-Clause
+@item @code{github.com/dchest/blake2b} @tab All @tab CC0 1.0
+@item @code{github.com/magical/argon2} @tab All @tab BSD 2-Clause
@item @code{github.com/bigeagle/water} @tab GNU/Linux @tab BSD 3-Clause
@end multitable
@node Media
@unnumbered In the media
-@itemize @bullet
+@itemize
@item @url{http://habrahabr.ru/company/ivi/blog/256365/, Реализуем безопасный VPN-протокол} (on russian)
@item @url{http://habrahabr.ru/company/ivi/blog/257431/, Реализуем ещё более безопасный VPN-протокол} (on russian)
@item @url{http://www.linuxspace.org/archives/9449, Установка и настройка безопасного VPN-демона GoVPN 3.2} (on russian)
@table @strong
+@item Release 4.1
+@itemize
+@item @url{https://password-hashing.net/#argon2, Argon2d} is used instead
+of PBKDF2 for password verifier hashing.
+@item Client's identity is stored inside the verifier, so it simplifies
+server-side configuration and the code.
+@end itemize
+
@item Release 4.0
-@itemize @bullet
+@itemize
@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.
@end itemize
@item Release 3.5
-@itemize @bullet
+@itemize
@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)
@end itemize
@item Release 3.4
-@itemize @bullet
+@itemize
@item Ability to use external @ref{EGD}-compatible PRNGs. Now you are
able to use GoVPN even on systems with the bad @code{/dev/random},
providing higher quality entropy from external sources.
@end itemize
@item Release 3.3
-@itemize @bullet
+@itemize
@item Compatibility with an old GNU Make 3.x. Previously only BSD Make
and GNU Make 4.x were supported.
@item /dev/urandom is used for correct client identity generation under
@end itemize
@item Release 3.2
-@itemize @bullet
+@itemize
@item
Deterministic building: dependent libraries source code commits are
fixed in our makefiles.
@end itemize
@item Release 3.1
-@itemize @bullet
+@itemize
@item
Diffie-Hellman public keys are encoded with Elligator algorithm when
sending over the wire, making them indistinguishable from the random
@end itemize
@item Release 3.0
-@itemize @bullet
+@itemize
@item
EKE protocol is replaced by Augmented-EKE and static symmetric (both
sides have it) pre-shared key replaced with server-side verifier. This
@end itemize
@item Release 2.4
-@itemize @bullet
+@itemize
@item
Added ability to optionally run built-in HTTP-server responding with
JSON of all known connected peers information. Real-time client's
@end itemize
@item Release 2.3
-@itemize @bullet
+@itemize
@item
Handshake packets became indistinguishable from the random.
Now all GoVPN's traffic is the noise for men in the middle.
@end itemize
@item Release 2.2
-@itemize @bullet
+@itemize
@item Fixed several possible channel deadlocks.
@end itemize
@item Release 2.1
-@itemize @bullet
+@itemize
@item Fixed Linux-related building.
@end itemize
@item Release 2.0
-@itemize @bullet
+@itemize
@item Added clients identification.
@item Simultaneous several clients support by server.
@item Per-client up/down scripts.
@end itemize
@item Release 1.5
-@itemize @bullet
+@itemize
@item Nonce obfuscation/encryption.
@end itemize
@item Release 1.4
-@itemize @bullet
+@itemize
@item Performance optimizations.
@end itemize
@item Release 1.3
-@itemize @bullet
+@itemize
@item Heartbeat feature.
@item Rehandshake feature.
@item up- and down- optinal scripts.
@end itemize
@item Release 1.1
-@itemize @bullet
+@itemize
@item FreeBSD support.
@end itemize
@item Release 1.0
-@itemize @bullet
+@itemize
@item Initial stable release.
@end itemize
client-server authentication. Is is secure, but not convenient for some
user use-cases:
-@itemize @bullet
+@itemize
@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
entropy source.
Passphrases are entered directly by the human on the client side. Server
-side stores previously shared so-called @ref{Verifier}. Verifier contains
-dictionary attack resistant password derivative. Attacker can not use it
-to act as a client.
+side stores previously shared so-called @ref{Verifier, verifier}. Verifier
+contains dictionary attack resistant password derivative. Attacker can not
+use it to act as a client.
We use password (passphrase) authentication, so overall security fully
depends on its strength. You @strong{should} use long, high-entropy
passphrases. Also remember to keep passphrase in temporary file and read
-it securely as described in @ref{Verifier}.
+it securely as described in @ref{Verifier, verifier}.
@item
You must @strong{never} use the same key for multiple clients.
@verbatim
{
- "9b40701bdaf522f2b291cb039490312": { <-- Peer identifier
- "name": "stargrave", <-- OPTIONAL human readable name
+ "stargrave": { <-- Peer human readable name
"up": "./stargrave-up.sh", <-- up-script
"down": "./stargrave-down.sh", <-- OPTIONAL down-script
"timeout": 60, <-- OPTIONAL overriden timeout
(default: false)
"cpr": 64, <-- OPTIONAL constant packet
rate in KiB/sec
- "verifier": "2c15bbdffc73193bea56db412bce1143c68ccbdaa9e2eade53a684497646a685"
+ "verifier": "$argon2d..." <-- verifier received from client
},
[...]
}
@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
@verbatim
% ./utils/newclient.sh Alice
[...]
-Your id is: 7012df29deee2170594119df5091d4a2
+Your client verifier is: $argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg
Place the following JSON configuration entry on the server's side:
- "906e34b98750c4f686d6c5489508763c": {
- "name": "Alice",
+ "Alice": {
"up": "/path/to/up.sh",
- "verifier": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "verifier": "$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg$KCNIqfS4DGsBTtVytamAzcISgrlEWvNxan1UfBrFu10"
}
[...]
@end verbatim
@multitable @columnfractions .50 .50
@headitem Software/library @tab Mirror
@item @code{govpn} @tab @url{https://github.com/stargrave/govpn.git}
-@item @code{golang.org/x/crypto} @tab @url{git://git.cypherpunks.ru/crypto.git}
@item @code{github.com/agl/ed25519} @tab @url{git://git.cypherpunks.ru/ed25519.git}
@item @code{github.com/bigeagle/water} @tab @url{git://git.cypherpunks.ru/water.git}
+@item @code{github.com/dchest/blake2b} @tab @url{git://git.cypherpunks.ru/blake2b.git}
+@item @code{github.com/magical/argon2} @tab @url{git://git.cypherpunks.ru/argon2.git}
+@item @code{golang.org/x/crypto} @tab @url{git://git.cypherpunks.ru/crypto.git}
@end multitable
Thanks for contributions and suggestions to:
-@itemize @bullet
+@itemize
@item
@url{https://www.schneier.com/books/applied_cryptography/, Applied Cryptography}
@copyright{} 1996 Bruce Schneier.
@item @url{http://ed25519.cr.yp.to/, Ed25519: high-speed high-security signatures}.
@item @email{watsonbladd@@gmail.com, Watson Ladd} for suggestion of
@url{http://elligator.cr.yp.to/, Elligator} encoding.
+@item @url{https://password-hashing.net/#argon2, PHC for Argon2}.
@end itemize
@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 Randomize ports usage.
@example
% utils/storekey.sh mypass.txt
Enter passphrase:[hello world]
-% govpn-verifier -id 9da9bf91112d0e4483c135b12d5b48de -key mypass.txt
-210e3878542828901a3af9b4aa00b004de530410eef5c1ba2ffb6d04504371b2
+% govpn-verifier -key mypass.txt
+$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg$KCNIqfS4DGsBTtVytamAzcISgrlEWvNxan1UfBrFu10
+$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg
@end example
-Store @code{210...1b2} string on the server's side in corresponding
+First line is the verifier for the server side. Second line is for the
+client -- it lacks generated public key. However you can server's one
+on the client side too.
+
+Store @code{$argon2d...u10} 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:
@example
-% govpn-verifier -id 9da9bf91112d0e4483c135b12d5b48de -key mypass.txt -verifier verifier
+% govpn-verifier -key mypass.txt -verifier '$argon2d...'
true
@end example
its verifying).
@verbatim
-SOURCE = PBKDF2(SALT=PeerId, PASSWORD, 1<<16, SHA512)
+SOURCE = Argon2d(m, t, p, SALT=PeerId, PASSWORD)
PUB, PRIV = Ed25519.Generate(SOURCE)
@end verbatim
-Verifier is @code{PUB} public key of Ed25519 generated from the PBKDF2
-of the passphrase in hexadecimal encoding. @code{PeerId} is used as a
-128-bit salt. Server stores and knows only verifier. Client can compute
-the whole keypair every time he makes handshake.
+Verifier is serialized representation of public data above:
+@verbatim
+$argon2d$m=m,t=t,p=p$Base64(SALT)$Base64(PUB)
+@end verbatim
+
+m, t and p parameters are Argon2d-specific: memory, iterations and
+parallelizm parameters.
+
+Server stores and knows only verifier. Client can compute the whole
+keypair every time he makes handshake.
--- /dev/null
+Subproject commit 3c8c640cd7bea3ca78209d812b5854442ab92fed
--- /dev/null
+Subproject commit 82d59eb7dab9a6268371a8c6de2100a2c7357bc6
)
var (
- remoteAddr = flag.String("remote", "", "Remote server address")
- proto = flag.String("proto", "udp", "Protocol to use: udp or tcp")
- ifaceName = flag.String("iface", "tap0", "TAP network interface")
- IDRaw = flag.String("id", "", "Client identification")
- keyPath = flag.String("key", "", "Path to passphrase file")
- upPath = flag.String("up", "", "Path to up-script")
- downPath = flag.String("down", "", "Path to down-script")
- stats = flag.String("stats", "", "Enable stats retrieving on host:port")
- proxyAddr = flag.String("proxy", "", "Use HTTP proxy on host:port")
- proxyAuth = flag.String("proxy-auth", "", "user:password Basic proxy auth")
- mtu = flag.Int("mtu", 1452, "MTU for outgoing packets")
- timeoutP = flag.Int("timeout", 60, "Timeout seconds")
- noisy = flag.Bool("noise", false, "Enable noise appending")
- cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
- egdPath = flag.String("egd", "", "Optional path to EGD socket")
+ remoteAddr = flag.String("remote", "", "Remote server address")
+ proto = flag.String("proto", "udp", "Protocol to use: udp or tcp")
+ ifaceName = flag.String("iface", "tap0", "TAP network interface")
+ verifierRaw = flag.String("verifier", "", "Verifier")
+ keyPath = flag.String("key", "", "Path to passphrase file")
+ upPath = flag.String("up", "", "Path to up-script")
+ downPath = flag.String("down", "", "Path to down-script")
+ stats = flag.String("stats", "", "Enable stats retrieving on host:port")
+ proxyAddr = flag.String("proxy", "", "Use HTTP proxy on host:port")
+ proxyAuth = flag.String("proxy-auth", "", "user:password Basic proxy auth")
+ mtu = flag.Int("mtu", 1452, "MTU for outgoing packets")
+ timeoutP = flag.Int("timeout", 60, "Timeout seconds")
+ noisy = flag.Bool("noise", false, "Enable noise appending")
+ cpr = flag.Int("cpr", 0, "Enable constant KiB/sec out traffic rate")
+ egdPath = flag.String("egd", "", "Optional path to EGD socket")
conf *govpn.PeerConf
tap *govpn.TAP
govpn.MTU = *mtu
- id, err := govpn.IDDecode(*IDRaw)
- if err != nil {
- log.Fatalln(err)
- }
-
if *egdPath != "" {
log.Println("Using", *egdPath, "EGD")
govpn.EGDInit(*egdPath)
}
- pub, priv := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath))
+ verifier, err := govpn.VerifierFromString(*verifierRaw)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ priv := verifier.PasswordApply(govpn.StringFromFile(*keyPath))
conf = &govpn.PeerConf{
- Id: id,
- Timeout: time.Second * time.Duration(timeout),
- Noise: *noisy,
- CPR: *cpr,
- DSAPub: pub,
- DSAPriv: priv,
+ Id: verifier.Id,
+ Timeout: time.Second * time.Duration(timeout),
+ Noise: *noisy,
+ CPR: *cpr,
+ Verifier: verifier,
+ DSAPriv: priv,
}
- idsCache = govpn.NewCipherCache([]govpn.PeerId{*id})
+ idsCache = govpn.NewCipherCache([]govpn.PeerId{*verifier.Id})
log.Println(govpn.VersionGet())
tap, err = govpn.TAPListen(*ifaceName)
timeouted := make(chan struct{})
rehandshaking := make(chan struct{})
termination := make(chan struct{})
+ if *proxyAddr != "" {
+ *proto = "tcp"
+ }
switch *proto {
case "udp":
go startUDP(timeouted, rehandshaking, termination)
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
package main
import (
package main
import (
- "encoding/hex"
"encoding/json"
"io/ioutil"
"log"
"time"
- "github.com/agl/ed25519"
-
"govpn"
)
}
confs := make(map[govpn.PeerId]*govpn.PeerConf, len(*confsRaw))
- for peerIdRaw, pc := range *confsRaw {
- peerId, err := govpn.IDDecode(peerIdRaw)
+ for name, pc := range *confsRaw {
+ verifier, err := govpn.VerifierFromString(pc.VerifierRaw)
if err != nil {
- log.Fatalln("Invalid peer ID:", peerIdRaw, err)
+ log.Fatalln("Unable to decode the key:", err.Error(), pc.VerifierRaw)
}
conf := govpn.PeerConf{
- Id: peerId,
- Name: pc.Name,
- Up: pc.Up,
- Down: pc.Down,
- Noise: pc.Noise,
- CPR: pc.CPR,
+ Verifier: verifier,
+ Id: verifier.Id,
+ Name: 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
+ confs[*verifier.Id] = &conf
}
return confs
}
package main
import (
+ "crypto/rand"
"crypto/subtle"
- "encoding/hex"
"flag"
"fmt"
"log"
)
var (
- IDRaw = flag.String("id", "", "Client identification")
- keyPath = flag.String("key", "", "Path to passphrase file")
- verifierPath = flag.String("verifier", "", "Optional path to verifier")
+ keyPath = flag.String("key", "", "Path to passphrase file")
+ verifier = flag.String("verifier", "", "Optional verifier")
+ mOpt = flag.Int("m", govpn.DefaultM, "Argon2d memory parameter (KiBs)")
+ tOpt = flag.Int("t", govpn.DefaultT, "Argon2d iteration parameter")
+ pOpt = flag.Int("p", govpn.DefaultP, "Argon2d parallelizm parameter")
)
func main() {
flag.Parse()
- id, err := govpn.IDDecode(*IDRaw)
- if err != nil {
- log.Fatalln(err)
- }
- pub, _ := govpn.NewVerifier(id, govpn.StringFromFile(*keyPath))
- if *verifierPath == "" {
- fmt.Println(hex.EncodeToString(pub[:]))
- } else {
- verifier, err := hex.DecodeString(govpn.StringFromFile(*verifierPath))
- if err != nil {
- log.Fatalln("Can not decode verifier:", err)
+ if *verifier == "" {
+ id := new([govpn.IDSize]byte)
+ if _, err := rand.Read(id[:]); err != nil {
+ log.Fatalln(err)
}
- fmt.Println(subtle.ConstantTimeCompare(verifier[:], pub[:]) == 1)
+ pid := govpn.PeerId(*id)
+ v := govpn.VerifierNew(*mOpt, *tOpt, *pOpt, &pid)
+ v.PasswordApply(govpn.StringFromFile(*keyPath))
+ fmt.Println(v.LongForm())
+ fmt.Println(v.ShortForm())
+ return
+ }
+ v, err := govpn.VerifierFromString(*verifier)
+ if err != nil {
+ log.Fatalln("Can not decode verifier", err)
}
+ pub := *v.Pub
+ v.PasswordApply(govpn.StringFromFile(*keyPath))
+ fmt.Println(subtle.ConstantTimeCompare(v.Pub[:], pub[:]) == 1)
}
)
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"`
+ 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"`
+ VerifierRaw string `json:"verifier"`
// This is passphrase verifier
- DSAPub *[ed25519.PublicKeySize]byte `json:"-"`
+ Verifier *Verifier
// This field exists only on client's side
DSAPriv *[ed25519.PrivateKeySize]byte `json:"-"`
}
Conf: conf,
}
state.dsaPubH = new([ed25519.PublicKeySize]byte)
- copy(state.dsaPubH[:], state.Conf.DSAPub[:])
+ copy(state.dsaPubH[:], state.Conf.Verifier.Pub[:])
HApply(state.dsaPubH)
return &state
}
// authenticated Peer is ready, then return nil.
func (h *Handshake) Server(data []byte) *Peer {
// R + ENC(H(DSAPub), R, El(CDHPub)) + IDtag
- if h.rNonce == nil {
+ if h.rNonce == nil && len(data) >= 48 {
// Generate DH keypair
var dhPubRepr *[32]byte
h.dhPriv, dhPubRepr = dhKeypairGen()
h.LastPing = time.Now()
} else
// ENC(K, R+1, RS + RC + SC + Sign(DSAPriv, K)) + IDtag
- if h.rClient == nil {
+ if h.rClient == nil && len(data) >= 120 {
// Decrypted Rs compare rServer
dec := make([]byte, RSize+RSize+SSize+ed25519.SignatureSize)
salsa20.XORKeyStream(
}
sign := new([ed25519.SignatureSize]byte)
copy(sign[:], dec[RSize+RSize+SSize:])
- if !ed25519.Verify(h.Conf.DSAPub, h.key[:], sign) {
+ if !ed25519.Verify(h.Conf.Verifier.Pub, h.key[:], sign) {
log.Println("Invalid signature from", h.addr)
return nil
}
// authenticated Peer is ready, then return nil.
func (h *Handshake) Client(data []byte) *Peer {
// ENC(H(DSAPub), R+1, El(SDHPub)) + ENC(K, R, RS + SS) + IDtag
- if h.rServer == nil && h.key == nil {
+ if h.rServer == nil && h.key == nil && len(data) >= 80 {
// Decrypt remote public key and compute shared key
sDHRepr := new([32]byte)
salsa20.XORKeyStream(sDHRepr[:], data[:32], h.rNonceNext(1), h.dsaPubH)
h.LastPing = time.Now()
} else
// ENC(K, R+2, RC) + IDtag
- if h.key != nil {
+ if h.key != nil && len(data) >= 16 {
// Decrypt rClient
dec := make([]byte, RSize)
salsa20.XORKeyStream(dec, data[:RSize], h.rNonceNext(2), h.key)
import (
"crypto/subtle"
"encoding/hex"
- "errors"
"log"
"sync"
return hex.EncodeToString(id[:])
}
-// 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
-}
-
type CipherCache struct {
c map[PeerId]*xtea.Cipher
l sync.RWMutex
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
package govpn
import (
}
func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
+ if len(data) < MinPktLength {
+ return false
+ }
p.BusyR.Lock()
for i := 0; i < SSize; i++ {
p.bufR[i] = byte(0)
p.BusyR.Unlock()
return true
}
+ if int(p.pktSizeR) > len(data)-MinPktLength {
+ return false
+ }
p.BytesPayloadIn += int64(p.pktSizeR)
tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR])
p.BusyR.Unlock()
func init() {
MTU = 1500
- peerId, _ = IDDecode("ffffffffffffffffffffffffffffffff")
+ id := new([IDSize]byte)
+ peerId := PeerId(*id)
conf = &PeerConf{
- Id: peerId,
- Timeout: time.Second * time.Duration(TimeoutDefault),
- NoiseEnable: false,
- CPR: 0,
+ Id: &peerId,
+ Timeout: time.Second * time.Duration(TimeoutDefault),
+ Noise: false,
+ CPR: 0,
}
peer = newPeer(true, "foo", Dummy{&ciphertext}, conf, new([SSize]byte))
plaintext = make([]byte, 789)
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
package govpn
import (
import (
"bytes"
- "crypto/sha512"
+ "encoding/base64"
+ "errors"
+ "fmt"
"io/ioutil"
"log"
"strings"
"github.com/agl/ed25519"
- "golang.org/x/crypto/pbkdf2"
+ "github.com/magical/argon2"
)
const (
- PBKDF2Iters = 1 << 16
+ DefaultM = 1 << 12
+ DefaultT = 1 << 7
+ DefaultP = 1
)
-// Create verifier from supplied password for given PeerId.
-func NewVerifier(id *PeerId, password string) (*[ed25519.PublicKeySize]byte, *[ed25519.PrivateKeySize]byte) {
- r := pbkdf2.Key(
- []byte(password),
- id[:],
- PBKDF2Iters,
- ed25519.PrivateKeySize,
- sha512.New,
- )
+type Verifier struct {
+ M int
+ T int
+ P int
+ Id *PeerId
+ Pub *[ed25519.PublicKeySize]byte
+}
+
+// Generate new verifier for given peer, with specified password and
+// hashing parameters.
+func VerifierNew(m, t, p int, id *PeerId) *Verifier {
+ return &Verifier{M: m, T: t, P: p, Id: id}
+}
+
+// Apply the password: create Ed25519 keypair based on it, save public
+// key in verifier.
+func (v *Verifier) PasswordApply(password string) *[ed25519.PrivateKeySize]byte {
+ r, err := argon2.Key([]byte(password), v.Id[:], v.T, v.P, int64(v.M), 32)
+ if err != nil {
+ log.Fatalln("Unable to apply Argon2d", err)
+ }
defer sliceZero(r)
src := bytes.NewBuffer(r)
- pub, priv, err := ed25519.GenerateKey(src)
+ pub, prv, err := ed25519.GenerateKey(src)
if err != nil {
log.Fatalln("Unable to generate Ed25519 keypair", err)
}
- return pub, priv
+ v.Pub = pub
+ return prv
+}
+
+// Parse either short or long verifier form.
+func VerifierFromString(input string) (*Verifier, error) {
+ s := strings.Split(input, "$")
+ if !(len(s) != 4 || len(s) != 5) || s[1] != "argon2d" {
+ return nil, errors.New("Invalid verifier structure")
+ }
+ var m, t, p int
+ n, err := fmt.Sscanf(s[2], "m=%d,t=%d,p=%d", &m, &t, &p)
+ if n != 3 || err != nil {
+ return nil, errors.New("Invalid verifier parameters")
+ }
+ salt, err := base64.RawStdEncoding.DecodeString(s[3])
+ if err != nil {
+ return nil, err
+ }
+ v := Verifier{M: m, T: t, P: p}
+ id := new([IDSize]byte)
+ copy(id[:], salt)
+ pid := PeerId(*id)
+ v.Id = &pid
+ if len(s) == 5 {
+ pub, err := base64.RawStdEncoding.DecodeString(s[4])
+ if err != nil {
+ return nil, err
+ }
+ v.Pub = new([ed25519.PublicKeySize]byte)
+ copy(v.Pub[:], pub)
+ }
+ return &v, nil
+}
+
+// Short verifier string form -- it is useful for the client.
+// Does not include public key.
+func (v *Verifier) ShortForm() string {
+ return fmt.Sprintf(
+ "$argon2d$m=%d,t=%d,p=%d$%s",
+ v.M, v.T, v.P, base64.RawStdEncoding.EncodeToString(v.Id[:]),
+ )
+}
+
+// Long verifier string form -- it is useful for the server.
+// Includes public key.
+func (v *Verifier) LongForm() string {
+ return fmt.Sprintf(
+ "%s$%s", v.ShortForm(),
+ base64.RawStdEncoding.EncodeToString(v.Pub[:]),
+ )
}
// Read string from the file, trimming newline.
[ -n "$release" ]
git clone . $tmp/govpn-$release
-for repo in src/github.com/bigeagle/water src/github.com/agl/ed25519 src/golang.org/x/crypto; do
+for repo in src/github.com/bigeagle/water src/github.com/agl/ed25519 src/github.com/magical/argon2 src/github.com/dchest/blake2b src/golang.org/x/crypto; do
git clone $repo $tmp/govpn-$release/$repo
done
cd $tmp/govpn-$release
----------------8<-----------------8<-----------------8<----------------
Домашняя страница GoVPN: http://govpn.info -> http://www.cypherpunks.ru/govpn/
+Коротко о демоне: http://www.cypherpunks.ru/govpn/About-RU.html
также доступна как скрытый сервис Tor: http://vabu56j2ep2rwv3b.onion/govpn/
Исходный код и его подпись для этой версии находится здесь:
[ -n "$1" ] || {
cat <<EOF
Example script for creating new user peer for GoVPN.
-It generates random client's identity, ask for passphrase, generates
-verifier and shows you example JSON entry for server configuration.
+It asks for passphrase, generates verifier and shows you example
+JSON entry for server configuration.
Usage: $0 <username>
EOF
}
username=$1
-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
passphrase=$(mktemp)
$(dirname $0)/storekey.sh $passphrase
-verifier=$(govpn-verifier -id $peerid -key $passphrase)
+verifier=$(govpn-verifier -key $passphrase)
rm -f $passphrase
+verifierS=$(echo $verifier | sed 's/^\(.*\) .*$/\1/')
+verifierC=$(echo $verifier | sed 's/^.* \(.*\)$/\1/')
echo
cat <<EOF
-Your id is: $peerid
+Your client verifier is: $verifierC
Place the following JSON configuration entry on the server's side:
- "$peerid": {
- "name": "$username",
+ "$username": {
"up": "/path/to/up.sh",
- "verifier": "$verifier"
+ "verifier": "$verifierS"
}
Verifier was generated with:
$(dirname $0)/storekey.sh /tmp/passphrase
- govpn-verifier -id $peerid -key /tmp/passphrase
+ govpn-verifier -key /tmp/passphrase
Create up.sh script that will output on the first line TAP interface
name that must be used for the peer. For example: