]> Cypherpunks.ru repositories - govpn.git/blob - src/govpn/identify.go
663afe73599bf14b8fc40acf7e2b738c9ae6d7e6
[govpn.git] / src / govpn / identify.go
1 /*
2 GoVPN -- simple secure free software virtual private network daemon
3 Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package govpn
20
21 import (
22         "crypto/subtle"
23         "encoding/hex"
24         "errors"
25         "io/ioutil"
26         "log"
27         "os"
28         "path"
29         "strconv"
30         "strings"
31         "sync"
32         "time"
33
34         "github.com/agl/ed25519"
35         "golang.org/x/crypto/xtea"
36 )
37
38 const (
39         IDSize      = 128 / 8
40         RefreshRate = 60 * time.Second
41 )
42
43 type PeerId [IDSize]byte
44
45 func (id PeerId) String() string {
46         return hex.EncodeToString(id[:])
47 }
48
49 // Return human readable name of the peer.
50 // It equals either to peers/PEER/name file contents or PEER's hex.
51 func (id PeerId) MarshalJSON() ([]byte, error) {
52         result := id.String()
53         if name, err := ioutil.ReadFile(path.Join(PeersPath, result, "name")); err == nil {
54                 result = strings.TrimRight(string(name), "\n")
55         }
56         return []byte(`"` + result + `"`), nil
57 }
58
59 type PeerConf struct {
60         Id          *PeerId
61         Timeout     time.Duration
62         Noncediff   int
63         NoiseEnable bool
64         CPR         int
65         // This is passphrase verifier
66         DSAPub *[ed25519.PublicKeySize]byte
67         // This field exists only in dummy configuration on client's side
68         DSAPriv *[ed25519.PrivateKeySize]byte
69 }
70
71 type cipherCache map[PeerId]*xtea.Cipher
72
73 var (
74         PeersPath       string
75         IDsCache        cipherCache
76         cipherCacheLock sync.RWMutex
77         dummyConf       *PeerConf
78 )
79
80 // Initialize (pre-cache) available peers info.
81 func PeersInit(path string) {
82         PeersPath = path
83         IDsCache = make(map[PeerId]*xtea.Cipher)
84         go func() {
85                 for {
86                         IDsCache.refresh()
87                         time.Sleep(RefreshRate)
88                 }
89         }()
90 }
91
92 // Initialize dummy cache for client-side usage.
93 func PeersInitDummy(id *PeerId, conf *PeerConf) {
94         IDsCache = make(map[PeerId]*xtea.Cipher)
95         cipher, err := xtea.NewCipher(id[:])
96         if err != nil {
97                 panic(err)
98         }
99         IDsCache[*id] = cipher
100         dummyConf = conf
101 }
102
103 // Refresh IDsCache: remove disappeared keys, add missing ones with
104 // initialized ciphers.
105 func (cc cipherCache) refresh() {
106         dir, err := os.Open(PeersPath)
107         if err != nil {
108                 panic(err)
109         }
110         peerIds, err := dir.Readdirnames(0)
111         if err != nil {
112                 panic(err)
113         }
114         available := make(map[PeerId]bool)
115         for _, peerId := range peerIds {
116                 id, err := IDDecode(peerId)
117                 if err != nil {
118                         continue
119                 }
120                 available[*id] = true
121         }
122
123         cipherCacheLock.Lock()
124         // Cleanup deleted ones from cache
125         for k, _ := range cc {
126                 if _, exists := available[k]; !exists {
127                         delete(cc, k)
128                         log.Println("Cleaning key: ", k)
129                 }
130         }
131         // Add missing ones
132         for peerId, _ := range available {
133                 if _, exists := cc[peerId]; !exists {
134                         log.Println("Adding key", peerId)
135                         cipher, err := xtea.NewCipher(peerId[:])
136                         if err != nil {
137                                 panic(err)
138                         }
139                         cc[peerId] = cipher
140                 }
141         }
142         cipherCacheLock.Unlock()
143 }
144
145 // Try to find peer's identity (that equals to an encryption key)
146 // by taking first blocksize sized bytes from data at the beginning
147 // as plaintext and last bytes as cyphertext.
148 func (cc cipherCache) Find(data []byte) *PeerId {
149         if len(data) < xtea.BlockSize*2 {
150                 return nil
151         }
152         buf := make([]byte, xtea.BlockSize)
153         cipherCacheLock.RLock()
154         for pid, cipher := range cc {
155                 cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
156                 if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
157                         ppid := PeerId(pid)
158                         cipherCacheLock.RUnlock()
159                         return &ppid
160                 }
161         }
162         cipherCacheLock.RUnlock()
163         return nil
164 }
165
166 func readIntFromFile(path string) (int, error) {
167         data, err := ioutil.ReadFile(path)
168         if err != nil {
169                 return 0, err
170         }
171         val, err := strconv.Atoi(strings.TrimRight(string(data), "\n"))
172         if err != nil {
173                 return 0, err
174         }
175         return val, nil
176 }
177
178 // Get peer related configuration.
179 func (id *PeerId) Conf() *PeerConf {
180         if dummyConf != nil {
181                 return dummyConf
182         }
183         conf := PeerConf{Id: id, Noncediff: 1, NoiseEnable: false, CPR: 0}
184         peerPath := path.Join(PeersPath, id.String())
185
186         verPath := path.Join(peerPath, "verifier")
187         keyData, err := ioutil.ReadFile(verPath)
188         if err != nil {
189                 log.Println("Unable to read verifier:", verPath)
190                 return nil
191         }
192         if len(keyData) < ed25519.PublicKeySize*2 {
193                 log.Println("Verifier must be 64 hex characters long:", verPath)
194                 return nil
195         }
196         keyDecoded, err := hex.DecodeString(string(keyData[:ed25519.PublicKeySize*2]))
197         if err != nil {
198                 log.Println("Unable to decode the key:", err.Error(), verPath)
199                 return nil
200         }
201         conf.DSAPub = new([ed25519.PublicKeySize]byte)
202         copy(conf.DSAPub[:], keyDecoded)
203
204         timeout := TimeoutDefault
205         if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil {
206                 timeout = val
207         }
208         conf.Timeout = time.Second * time.Duration(timeout)
209
210         if val, err := readIntFromFile(path.Join(peerPath, "noncediff")); err == nil {
211                 conf.Noncediff = val
212         }
213         if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
214                 conf.NoiseEnable = true
215         }
216         if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil {
217                 conf.CPR = val
218         }
219         return &conf
220 }
221
222 // Decode identification string.
223 // It must be 32 hexadecimal characters long.
224 func IDDecode(raw string) (*PeerId, error) {
225         if len(raw) != IDSize*2 {
226                 return nil, errors.New("ID must be 32 characters long")
227         }
228         idDecoded, err := hex.DecodeString(raw)
229         if err != nil {
230                 return nil, errors.New("ID must contain hexadecimal characters only")
231         }
232         idP := new([IDSize]byte)
233         copy(idP[:], idDecoded)
234         id := PeerId(*idP)
235         return &id, nil
236 }