]> Cypherpunks.ru repositories - govpn.git/blob - identify.go
Per-peer timeout, noncediff, noise, cpr settings
[govpn.git] / 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         "io/ioutil"
25         "log"
26         "os"
27         "path"
28         "strconv"
29         "strings"
30         "sync"
31         "time"
32
33         "golang.org/x/crypto/xtea"
34 )
35
36 const (
37         IDSize      = 128 / 8
38         RefreshRate = 60 * time.Second
39 )
40
41 type PeerId [IDSize]byte
42
43 func (id PeerId) String() string {
44         return hex.EncodeToString(id[:])
45 }
46
47 // Return human readable name of the peer.
48 // It equals either to peers/PEER/name file contents or PEER's hex.
49 func (id PeerId) MarshalJSON() ([]byte, error) {
50         result := id.String()
51         if name, err := ioutil.ReadFile(path.Join(PeersPath, result, "name")); err == nil {
52                 result = strings.TrimRight(string(name), "\n")
53         }
54         return []byte(`"` + result + `"`), nil
55 }
56
57 type PeerConf struct {
58         Id          *PeerId
59         Timeout     time.Duration
60         Noncediff   int
61         NoiseEnable bool
62         CPR         int
63 }
64
65 type cipherCache map[PeerId]*xtea.Cipher
66
67 var (
68         PeersPath       string
69         IDsCache        cipherCache
70         cipherCacheLock sync.RWMutex
71         dummyConf       *PeerConf
72 )
73
74 // Initialize (pre-cache) available peers info.
75 func PeersInit(path string) {
76         PeersPath = path
77         IDsCache = make(map[PeerId]*xtea.Cipher)
78         go func() {
79                 for {
80                         IDsCache.refresh()
81                         time.Sleep(RefreshRate)
82                 }
83         }()
84 }
85
86 // Initialize dummy cache for client-side usage.
87 func PeersInitDummy(id *PeerId, conf PeerConf) {
88         IDsCache = make(map[PeerId]*xtea.Cipher)
89         cipher, err := xtea.NewCipher(id[:])
90         if err != nil {
91                 panic(err)
92         }
93         IDsCache[*id] = cipher
94         dummyConf = &conf
95 }
96
97 // Refresh IDsCache: remove disappeared keys, add missing ones with
98 // initialized ciphers.
99 func (cc cipherCache) refresh() {
100         dir, err := os.Open(PeersPath)
101         if err != nil {
102                 panic(err)
103         }
104         peerIds, err := dir.Readdirnames(0)
105         if err != nil {
106                 panic(err)
107         }
108         available := make(map[PeerId]bool)
109         for _, peerId := range peerIds {
110                 id := IDDecode(peerId)
111                 if id == nil {
112                         continue
113                 }
114                 available[*id] = true
115         }
116
117         cipherCacheLock.Lock()
118         // Cleanup deleted ones from cache
119         for k, _ := range cc {
120                 if _, exists := available[k]; !exists {
121                         delete(cc, k)
122                         log.Println("Cleaning key: ", k)
123                 }
124         }
125         // Add missing ones
126         for peerId, _ := range available {
127                 if _, exists := cc[peerId]; !exists {
128                         log.Println("Adding key", peerId)
129                         cipher, err := xtea.NewCipher(peerId[:])
130                         if err != nil {
131                                 panic(err)
132                         }
133                         cc[peerId] = cipher
134                 }
135         }
136         cipherCacheLock.Unlock()
137 }
138
139 // Try to find peer's identity (that equals to an encryption key)
140 // by taking first blocksize sized bytes from data at the beginning
141 // as plaintext and last bytes as cyphertext.
142 func (cc cipherCache) Find(data []byte) *PeerId {
143         if len(data) < xtea.BlockSize*2 {
144                 return nil
145         }
146         buf := make([]byte, xtea.BlockSize)
147         cipherCacheLock.RLock()
148         for pid, cipher := range cc {
149                 cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
150                 if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
151                         ppid := PeerId(pid)
152                         cipherCacheLock.RUnlock()
153                         return &ppid
154                 }
155         }
156         cipherCacheLock.RUnlock()
157         return nil
158 }
159
160 func readIntFromFile(path string) (int, error) {
161         data, err := ioutil.ReadFile(path)
162         if err != nil {
163                 return 0, err
164         }
165         val, err := strconv.Atoi(strings.TrimRight(string(data), "\n"))
166         if err != nil {
167                 return 0, err
168         }
169         return val, nil
170 }
171
172 // Get peer related configuration.
173 func (id *PeerId) ConfGet() *PeerConf {
174         if dummyConf != nil {
175                 return dummyConf
176         }
177         conf := PeerConf{Id: id, Noncediff: 1, NoiseEnable: false, CPR: 0}
178         peerPath := path.Join(PeersPath, id.String())
179
180         timeout := TimeoutDefault
181         if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil {
182                 timeout = val
183         }
184         conf.Timeout = time.Second * time.Duration(timeout)
185
186         if val, err := readIntFromFile(path.Join(peerPath, "noncediff")); err == nil {
187                 conf.Noncediff = val
188         }
189         if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
190                 conf.NoiseEnable = true
191         }
192         if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil {
193                 conf.CPR = val
194         }
195         return &conf
196 }
197
198 // Decode identification string.
199 // It must be 32 hexadecimal characters long.
200 // If it is not the valid one, then return nil.
201 func IDDecode(raw string) *PeerId {
202         if len(raw) != IDSize*2 {
203                 return nil
204         }
205         idDecoded, err := hex.DecodeString(raw)
206         if err != nil {
207                 return nil
208         }
209         idP := new([IDSize]byte)
210         copy(idP[:], idDecoded)
211         id := PeerId(*idP)
212         return &id
213 }