From: Sergey Matveev Date: Tue, 3 Nov 2015 21:46:47 +0000 (+0300) Subject: Use Argon2d PHC winner instead of PBKDF2 X-Git-Tag: 4.1^2~1 X-Git-Url: http://www.git.cypherpunks.ru/?p=govpn.git;a=commitdiff_plain;h=9364defa689e91c6fb54651876fbf2d02eec35ec Use Argon2d PHC winner instead of PBKDF2 Signed-off-by: Sergey Matveev --- diff --git a/.gitmodules b/.gitmodules index f2d6ae4..ffbe442 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [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 diff --git a/VERSION b/VERSION index 5186d07..7d5c902 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.0 +4.1 diff --git a/doc/about.ru.texi b/doc/about.ru.texi index 2054c43..3b2fda6 100644 --- a/doc/about.ru.texi +++ b/doc/about.ru.texi @@ -15,7 +15,8 @@ GoVPN это простой демон виртуальных частных с Encrypted Key Exchange)). @item Несбалансированные аутентификационные токены устойчивые к внесетевым -(offline) атакам по словарю. Злоумышленник не может замаскироваться под +(offline) атакам по словарю. Используют усиленный по CPU и памяти +алгоритм хэширования. Злоумышленник не может замаскироваться под клиента даже скомпрометировав базу данных токенов сервера. @item Зашифрованный и аутентифицируемый транспортный протокол передачи данных diff --git a/doc/about.texi b/doc/about.texi index 7d956ae..6e25072 100644 --- a/doc/about.texi +++ b/doc/about.texi @@ -13,8 +13,9 @@ 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. +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 diff --git a/doc/client.texi b/doc/client.texi index bab6a47..40f310f 100644 --- a/doc/client.texi +++ b/doc/client.texi @@ -24,8 +24,8 @@ Address (@code{host:port} format) of remote server we need to connect to. @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 diff --git a/doc/developer.texi b/doc/developer.texi index 6035225..d9ec24f 100644 --- a/doc/developer.texi +++ b/doc/developer.texi @@ -15,9 +15,8 @@ DH-A-EKE powered by @url{http://cr.yp.to/ecdh.html, Curve25519} and @url{http://ed25519.cr.yp.to/, Ed25519}. @item DH elliptic-curve point encoding for public keys @url{http://elligator.cr.yp.to/, Elligator}. -@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 diff --git a/doc/example.texi b/doc/example.texi index b6daf92..6fd62df 100644 --- a/doc/example.texi +++ b/doc/example.texi @@ -17,25 +17,25 @@ is 1432. @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} @@ -77,7 +77,7 @@ client% ip route add default via 172.16.0.1 @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 @@ -97,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 906e34b98750c4f686d6c5489508763c \ + -verifier '$argon2d$m=4096,t=128,p=1$bwR5VjeCYIQaa8SeaI3rqg' \ -iface tap10 \ -remote "[fe80::1%me0]":1194 @end example diff --git a/doc/identity.texi b/doc/identity.texi index 46a6889..7e33b33 100644 --- a/doc/identity.texi +++ b/doc/identity.texi @@ -3,11 +3,5 @@ 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. diff --git a/doc/installation.texi b/doc/installation.texi index 8b41977..d8d9587 100644 --- a/doc/installation.texi +++ b/doc/installation.texi @@ -24,6 +24,8 @@ Included required libraries: @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 diff --git a/doc/news.texi b/doc/news.texi index 35ee26d..c2d06b8 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,14 @@ @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 @item Handshake messages can be noised: their messages lengths are diff --git a/doc/pake.texi b/doc/pake.texi index 0b52770..fa8a322 100644 --- a/doc/pake.texi +++ b/doc/pake.texi @@ -25,6 +25,6 @@ they are memorable. Because of their quantity, they acts as a high 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. diff --git a/doc/precautions.texi b/doc/precautions.texi index fad882d..fbf45d1 100644 --- a/doc/precautions.texi +++ b/doc/precautions.texi @@ -6,7 +6,7 @@ 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. diff --git a/doc/server.texi b/doc/server.texi index 49a09fb..a00c452 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -25,8 +25,7 @@ Configuration file is JSON file with following example structure: @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 @@ -34,14 +33,12 @@ Configuration file is JSON file with following example structure: (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 @@ -62,14 +59,13 @@ creation: @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 diff --git a/doc/thanks.texi b/doc/thanks.texi index db82ca1..5b9dba3 100644 --- a/doc/thanks.texi +++ b/doc/thanks.texi @@ -20,4 +20,5 @@ File Compromise @copyright{} Steven M. Belloving, Michael Merrit. @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 diff --git a/doc/todo.texi b/doc/todo.texi index f7d00f3..520192c 100644 --- a/doc/todo.texi +++ b/doc/todo.texi @@ -2,9 +2,6 @@ @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. diff --git a/doc/verifier.texi b/doc/verifier.texi index 61b1e44..07ba198 100644 --- a/doc/verifier.texi +++ b/doc/verifier.texi @@ -9,18 +9,23 @@ silently. @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 diff --git a/doc/verifierstruct.texi b/doc/verifierstruct.texi index 65a4e08..792506d 100644 --- a/doc/verifierstruct.texi +++ b/doc/verifierstruct.texi @@ -6,11 +6,17 @@ dictionary attacks and can not be used for authentication (only 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. diff --git a/src/github.com/dchest/blake2b b/src/github.com/dchest/blake2b new file mode 160000 index 0000000..3c8c640 --- /dev/null +++ b/src/github.com/dchest/blake2b @@ -0,0 +1 @@ +Subproject commit 3c8c640cd7bea3ca78209d812b5854442ab92fed diff --git a/src/github.com/magical/argon2 b/src/github.com/magical/argon2 new file mode 160000 index 0000000..82d59eb --- /dev/null +++ b/src/github.com/magical/argon2 @@ -0,0 +1 @@ +Subproject commit 82d59eb7dab9a6268371a8c6de2100a2c7357bc6 diff --git a/src/govpn/cmd/govpn-client/main.go b/src/govpn/cmd/govpn-client/main.go index 8ebb78d..09b06ca 100644 --- a/src/govpn/cmd/govpn-client/main.go +++ b/src/govpn/cmd/govpn-client/main.go @@ -31,21 +31,21 @@ import ( ) 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 @@ -63,26 +63,25 @@ func main() { 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) diff --git a/src/govpn/cmd/govpn-server/conf.go b/src/govpn/cmd/govpn-server/conf.go index 4136fab..76d56cd 100644 --- a/src/govpn/cmd/govpn-server/conf.go +++ b/src/govpn/cmd/govpn-server/conf.go @@ -19,14 +19,11 @@ along with this program. If not, see . package main import ( - "encoding/hex" "encoding/json" "io/ioutil" "log" "time" - "github.com/agl/ed25519" - "govpn" ) @@ -51,35 +48,25 @@ func confRead() map[govpn.PeerId]*govpn.PeerConf { } 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 } diff --git a/src/govpn/cmd/govpn-verifier/main.go b/src/govpn/cmd/govpn-verifier/main.go index ede2de7..1757e1e 100644 --- a/src/govpn/cmd/govpn-verifier/main.go +++ b/src/govpn/cmd/govpn-verifier/main.go @@ -20,8 +20,8 @@ along with this program. If not, see . package main import ( + "crypto/rand" "crypto/subtle" - "encoding/hex" "flag" "fmt" "log" @@ -30,25 +30,32 @@ import ( ) 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) } diff --git a/src/govpn/conf.go b/src/govpn/conf.go index 0eff590..d9e7481 100644 --- a/src/govpn/conf.go +++ b/src/govpn/conf.go @@ -25,18 +25,18 @@ import ( ) 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:"-"` } diff --git a/src/govpn/handshake.go b/src/govpn/handshake.go index 24a0f1f..f0a8e65 100644 --- a/src/govpn/handshake.go +++ b/src/govpn/handshake.go @@ -142,7 +142,7 @@ func NewHandshake(addr string, conn io.Writer, conf *PeerConf) *Handshake { 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 } @@ -252,7 +252,7 @@ func (h *Handshake) Server(data []byte) *Peer { } 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 } diff --git a/src/govpn/identify.go b/src/govpn/identify.go index a0b7d22..54c87d3 100644 --- a/src/govpn/identify.go +++ b/src/govpn/identify.go @@ -21,7 +21,6 @@ package govpn import ( "crypto/subtle" "encoding/hex" - "errors" "log" "sync" @@ -38,22 +37,6 @@ func (id PeerId) String() string { 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 diff --git a/src/govpn/peer.go b/src/govpn/peer.go index 1097eac..45b5dd7 100644 --- a/src/govpn/peer.go +++ b/src/govpn/peer.go @@ -330,7 +330,7 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool { p.BusyR.Unlock() return true } - if int(p.pktSizeR) > len(data) - MinPktLength { + if int(p.pktSizeR) > len(data)-MinPktLength { return false } p.BytesPayloadIn += int64(p.pktSizeR) diff --git a/src/govpn/peer_test.go b/src/govpn/peer_test.go index 0b8b917..c9c2647 100644 --- a/src/govpn/peer_test.go +++ b/src/govpn/peer_test.go @@ -26,9 +26,10 @@ func (d Dummy) Write(b []byte) (int, error) { func init() { MTU = 1500 - peerId, _ = IDDecode("ffffffffffffffffffffffffffffffff") + id := new([IDSize]byte) + peerId := PeerId(*id) conf = &PeerConf{ - Id: peerId, + Id: &peerId, Timeout: time.Second * time.Duration(TimeoutDefault), Noise: false, CPR: 0, diff --git a/src/govpn/verifier.go b/src/govpn/verifier.go index 8954047..fe09653 100644 --- a/src/govpn/verifier.go +++ b/src/govpn/verifier.go @@ -20,35 +20,101 @@ package govpn 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. diff --git a/utils/makedist.sh b/utils/makedist.sh index 5c8b5db..89bd8c8 100755 --- a/utils/makedist.sh +++ b/utils/makedist.sh @@ -8,7 +8,7 @@ release=$1 [ -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 diff --git a/utils/newclient.sh b/utils/newclient.sh index 9e1cb35..e0d8c85 100755 --- a/utils/newclient.sh +++ b/utils/newclient.sh @@ -3,8 +3,8 @@ [ -n "$1" ] || { cat < EOF @@ -12,30 +12,29 @@ 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 <