@table @code
@item -proto
-@ref{Network, network protocol} 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
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
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.
@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 <<EOF
-562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55
-EOF
-@end example
+@strong{Prepare the server}. Add this entry to @code{peers.json}
+configuration file.
@strong{Prepare network on GNU/Linux IPv4 server}:
@example
-server% echo "echo tap10" >> 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
@example
client% govpn-client \
-key key.txt \
- -id 6d4ac605ce8dc37c2f0bf21cb542a713 \
+ -id 906e34b98750c4f686d6c5489508763c \
-iface tap10 \
-remote 192.168.0.1:1194 \
-mtu 1472
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
@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.
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
@table @code
@item -proto
-@ref{Network, network protocol} to use. Can be @emph{udp},
+@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
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}
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:
timeout int
firstUpCall bool = true
knownPeers govpn.KnownPeers
+ idsCache govpn.CipherCache
)
func main() {
DSAPub: pub,
DSAPriv: priv,
}
- govpn.PeersInitDummy(id, conf)
+ idsCache = govpn.NewCipherCache([]govpn.PeerId{*id})
log.Println(govpn.VersionGet())
tap, err = govpn.TAPListen(*ifaceName)
}
prev += n
- peerId := govpn.IDsCache.Find(buf[:prev])
+ peerId := idsCache.Find(buf[:prev])
if peerId == nil {
continue
}
}
continue
}
- if govpn.IDsCache.Find(buf[:n]) == nil {
+ if idsCache.Find(buf[:n]) == nil {
log.Println("Invalid identity in handshake packet")
continue
}
import (
"bytes"
- "path"
"sync"
"time"
}
func callUp(peerId *govpn.PeerId) (string, error) {
- upPath := path.Join(govpn.PeersPath, peerId.String(), "up.sh")
- result, err := govpn.ScriptCall(upPath, "")
+ result, err := govpn.ScriptCall(confs[*peerId].Up, "")
if err != nil {
return "", err
}
--- /dev/null
+/*
+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 (
+ "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()
+ }
+ }()
+}
"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")
)
func main() {
log.Println(govpn.VersionGet())
govpn.MTU = *mtu
- govpn.PeersInit(*peersPath)
+ confInit()
knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer))
if *egdPath != "" {
delete(peers, addr)
delete(knownPeers, addr)
delete(peersById, *ps.peer.Id)
- downPath := path.Join(
- govpn.PeersPath,
- ps.peer.Id.String(),
- "down.sh",
+ go govpn.ScriptCall(
+ confs[*ps.peer.Id].Down,
+ ps.tap.Name,
)
- go govpn.ScriptCall(downPath, ps.tap.Name)
ps.terminator <- struct{}{}
}
}
break
}
prev += n
- peerId := govpn.IDsCache.Find(buf[:prev])
+ peerId := idsCache.Find(buf[:prev])
if peerId == nil {
continue
}
if hs == nil {
- conf = peerId.Conf()
+ conf = confs[*peerId]
if conf == nil {
log.Println("Can not get peer configuration:", peerId.String())
break
}
goto Finished
CheckID:
- peerId = govpn.IDsCache.Find(buf[:n])
+ peerId = idsCache.Find(buf[:n])
if peerId == nil {
log.Println("Unknown identity from:", addr)
goto Finished
}
- conf = peerId.Conf()
+ conf = confs[*peerId]
if conf == nil {
log.Println("Unable to get peer configuration:", peerId.String())
goto Finished
"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
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 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)
- }
- }()
+type CipherCache struct {
+ c map[PeerId]*xtea.Cipher
+ l sync.RWMutex
}
-// 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
+func NewCipherCache(peerIds []PeerId) CipherCache {
+ cc := CipherCache{c: make(map[PeerId]*xtea.Cipher, len(peerIds))}
+ cc.Update(peerIds)
+ return cc
}
-// 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
-}
#!/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
Example script for creating new user peer for GoVPN.
-It just creates directory with random peer ID, dummy verifier,
-dummy up.sh executable script and saves username in it.
+It generates random client's identity, ask for passphrase, generates
+verifier and shows you example JSON entry for server configuration.
Usage: $0 <username>
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 <<EOF
+Your id is: $peerid
+
+Place the following JSON configuration entry on the server's side:
+
+ "$peerid": {
+ "name": "$username",
+ "up": "/path/to/up.sh",
+ "verifier": "$verifier"
+ }
+
+Verifier was generated with:
+
+ $(dirname $0)/storekey.sh /tmp/passphrase
+ govpn-verifier -id $peerid -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:
+
+ % umask 077
+ % ed /path/to/up.sh
+ a
+ #!/bin/sh
+ echo tap0
+ .
+ wq
+ 20
+ % chmod +x /path/to/up.sh
+EOF