blob: 38c55ffe55dc0388a843d46b047a11e6d03bdd89 [file] [log] [blame]
giolekva313ee2b2021-12-15 15:17:29 +04001package main
2
3import (
giolekvaf58a7692021-12-15 18:05:39 +04004 "bytes"
giolekva3f0dcda2021-12-22 23:32:49 +04005 "crypto/rand"
6 "crypto/rsa"
7 "crypto/x509"
giolekvaf58a7692021-12-15 18:05:39 +04008 "encoding/json"
giolekva3f0dcda2021-12-22 23:32:49 +04009 "errors"
giolekva313ee2b2021-12-15 15:17:29 +040010 "flag"
11 "fmt"
giolekvaf58a7692021-12-15 18:05:39 +040012 "image"
13 "image/png"
giolekva1026d2d2021-12-19 19:09:15 +040014 "strings"
giolekva52da88a2021-12-17 18:08:25 +040015 "time"
giolekva313ee2b2021-12-15 15:17:29 +040016
17 "gioui.org/app"
giolekva96202c52021-12-18 12:45:34 +040018 "gioui.org/font/gofont"
giolekva313ee2b2021-12-15 15:17:29 +040019 "gioui.org/io/system"
20 "gioui.org/layout"
21 "gioui.org/op"
22 "gioui.org/unit"
giolekva96202c52021-12-18 12:45:34 +040023 "gioui.org/widget/material"
giolekvaf58a7692021-12-15 18:05:39 +040024 "github.com/skip2/go-qrcode"
giolekva313ee2b2021-12-15 15:17:29 +040025)
26
giolekvaf58a7692021-12-15 18:05:39 +040027var vpnApiAddr = flag.String("vpn-api-addr", "", "VPN API server address")
giolekva313ee2b2021-12-15 15:17:29 +040028
giolekvaf58a7692021-12-15 18:05:39 +040029var p *processor
30
giolekva3f0dcda2021-12-22 23:32:49 +040031type ScanQRFor int
32
33const (
34 ScanQRForJoin ScanQRFor = 0
35 ScanQRForApprove = 1
36)
37
giolekvaf58a7692021-12-15 18:05:39 +040038type processor struct {
39 vc VPNClient
40 app App
giolekva8d6a0ca2021-12-19 17:42:25 +040041 st Storage
giolekvaf58a7692021-12-15 18:05:39 +040042 ui *UI
43
giolekva3f0dcda2021-12-22 23:32:49 +040044 scanQRFor ScanQRFor
45 inviteQrCh chan image.Image
46 qrScannedCh chan []byte
47 joinQrCh chan image.Image
giolekva52da88a2021-12-17 18:08:25 +040048
49 onConnectCh chan interface{}
50 onDisconnectCh chan interface{}
giolekva8d6a0ca2021-12-19 17:42:25 +040051
52 onConfigCh chan struct{}
giolekva313ee2b2021-12-15 15:17:29 +040053}
54
giolekvaf58a7692021-12-15 18:05:39 +040055func newProcessor() *processor {
giolekva96202c52021-12-18 12:45:34 +040056 th := material.NewTheme(gofont.Collection())
giolekva8d6a0ca2021-12-19 17:42:25 +040057 app := createApp()
giolekvaf58a7692021-12-15 18:05:39 +040058 return &processor{
giolekva3f0dcda2021-12-22 23:32:49 +040059 vc: NewDirectVPNClient(*vpnApiAddr),
60 app: app,
61 st: app.CreateStorage(),
62 ui: NewUI(th, app.Capabilities()),
63 inviteQrCh: make(chan image.Image, 1),
64 qrScannedCh: make(chan []byte, 1),
65 joinQrCh: make(chan image.Image, 1),
66 onConnectCh: make(chan interface{}, 1),
67 onDisconnectCh: make(chan interface{}, 1),
68 onConfigCh: make(chan struct{}, 1),
giolekvaf58a7692021-12-15 18:05:39 +040069 }
70}
71
giolekva3f0dcda2021-12-22 23:32:49 +040072func (p *processor) QRCodeScanned(code []byte) {
73 p.qrScannedCh <- code
giolekvaf58a7692021-12-15 18:05:39 +040074}
75
giolekva52da88a2021-12-17 18:08:25 +040076func (p *processor) ConnectRequested(service interface{}) {
77 go func() {
78 time.Sleep(1 * time.Second)
79 p.onConnectCh <- service
80 }()
81}
82
83func (p *processor) DisconnectRequested(service interface{}) {
84 p.onDisconnectCh <- service
85}
86
giolekva3f0dcda2021-12-22 23:32:49 +040087func (p *processor) generatePublicPrivateKey() error {
88 config, err := p.st.Get()
89 if err != nil {
90 return err
91 }
92 if config.Enc.PrivateKey != nil {
93 return nil
94 }
95 privKey, err := rsa.GenerateKey(rand.Reader, 2048)
96 if err != nil {
97 panic(err)
98 }
99 config.Enc.PrivateKey = x509.MarshalPKCS1PrivateKey(privKey)
100 config.Enc.PublicKey = x509.MarshalPKCS1PublicKey(&privKey.PublicKey)
101 config.Network.PublicKey, config.Network.PrivateKey, err = x25519Keypair()
102 if err != nil {
103 return err
104 }
105 return p.st.Store(config)
106}
107
giolekvaf58a7692021-12-15 18:05:39 +0400108func (p *processor) run() error {
giolekva3f0dcda2021-12-22 23:32:49 +0400109 if err := p.generatePublicPrivateKey(); err != nil {
110 return err
111 }
giolekva313ee2b2021-12-15 15:17:29 +0400112 w := app.NewWindow(
113 app.Size(unit.Px(1500), unit.Px(1500)),
114 app.Title("PCloud"),
115 )
116 var ops op.Ops
117 for {
118 select {
119 case e := <-w.Events():
120 switch e := e.(type) {
121 case app.ViewEvent:
giolekvaf58a7692021-12-15 18:05:39 +0400122 if err := p.app.OnView(e); err != nil {
giolekva313ee2b2021-12-15 15:17:29 +0400123 return err
124 } else {
125 w.Invalidate()
126 }
giolekva8d6a0ca2021-12-19 17:42:25 +0400127 if config, err := p.st.Get(); err != nil {
128 return err
129 } else {
giolekva3f0dcda2021-12-22 23:32:49 +0400130 if config.Network.Config != nil {
giolekva8d6a0ca2021-12-19 17:42:25 +0400131 p.onConfigCh <- struct{}{}
132 }
133 }
giolekva313ee2b2021-12-15 15:17:29 +0400134 case *system.CommandEvent:
135 if e.Type == system.CommandBack {
giolekvaf58a7692021-12-15 18:05:39 +0400136 if p.ui.OnBack() {
giolekva313ee2b2021-12-15 15:17:29 +0400137 e.Cancel = true
138 w.Invalidate()
139 }
140 }
141 case system.FrameEvent:
142 gtx := layout.NewContext(&ops, e)
giolekvaf58a7692021-12-15 18:05:39 +0400143 events := p.ui.Layout(gtx)
giolekva313ee2b2021-12-15 15:17:29 +0400144 e.Frame(&ops)
giolekvaf58a7692021-12-15 18:05:39 +0400145 if err := p.processUIEvents(events); err != nil {
giolekva313ee2b2021-12-15 15:17:29 +0400146 return err
147 }
148 }
giolekvaf58a7692021-12-15 18:05:39 +0400149 case img := <-p.inviteQrCh:
150 p.ui.InviteQRGenerated(img)
151 w.Invalidate()
giolekva3f0dcda2021-12-22 23:32:49 +0400152 case code := <-p.qrScannedCh:
153 switch p.scanQRFor {
154 case ScanQRForJoin:
155 if err := p.JoinAndGetNetworkConfig(code); err != nil {
156 return err
157 }
158 case ScanQRForApprove:
159 if err := p.ApproveOther(code); err != nil {
160 return err
161 }
162 default:
163 return errors.New("Must not reach!")
giolekva52da88a2021-12-17 18:08:25 +0400164 }
giolekva3f0dcda2021-12-22 23:32:49 +0400165 case img := <-p.joinQrCh:
166 p.ui.JoinQRGenerated(img)
167 w.Invalidate()
168 go func() {
169 cnt := 0
170 for {
171 fmt.Println(cnt)
172 cnt++
173 if cnt > 5 {
174 break
175 }
176 time.Sleep(time.Second)
177 config, err := p.st.Get()
178 if err != nil {
179 continue
180 }
181 privKey, err := x509.ParsePKCS1PrivateKey(config.Enc.PrivateKey)
182 if err != nil {
183 continue
184 }
185 hostname, err := p.app.GetHostname()
186 if err != nil {
187 continue
188 }
189 hostname = sanitizeHostname(hostname)
190 network, err := p.vc.Get("https://vpn.lekva.me", hostname, privKey, config.Network.PrivateKey)
191 if err != nil {
192 continue
193 }
194 config.ApiAddr = "https://vpn.lekva.me"
195 config.Network.Config = network
196 if err := p.st.Store(config); err != nil {
197 continue
198 }
199 p.onConfigCh <- struct{}{}
200 break
201 }
202 }()
giolekva8d6a0ca2021-12-19 17:42:25 +0400203 case <-p.onConfigCh:
204 if err := p.app.TriggerService(); err != nil {
205 return err
206 }
207 case s := <-p.onConnectCh:
208 if err := p.app.UpdateService(s); err != nil {
209 return err
210 }
211 if config, err := p.st.Get(); err != nil {
212 return err
213 } else {
214 if err := p.app.Connect(config); err != nil {
215 return err
216 }
217 }
giolekva313ee2b2021-12-15 15:17:29 +0400218 }
219 }
220 return nil
221}
222
giolekvaf58a7692021-12-15 18:05:39 +0400223func (p *processor) processUIEvents(events []UIEvent) error {
224 for _, e := range events {
225 switch e.(type) {
226 case EventGetInviteQRCode:
227 go func() {
228 if img, err := p.generateInviteQRCode(); err == nil {
229 p.inviteQrCh <- img
230 } else {
231 // TODO(giolekva): do not panic
232 panic(err)
233 }
234 }()
giolekva3f0dcda2021-12-22 23:32:49 +0400235 case EventGetJoinQRCode:
236 go func() {
237 if img, err := p.generateJoinQRCode(); err == nil {
238 p.joinQrCh <- img
239 } else {
240 // TODO(giolekva): do not panic
241 panic(err)
242 }
243 }()
244 case EventApproveOther:
245 p.scanQRFor = ScanQRForApprove
246 if err := p.app.LaunchBarcodeScanner(); err != nil {
247 return err
248 }
giolekvaf58a7692021-12-15 18:05:39 +0400249 case EventScanBarcode:
giolekva3f0dcda2021-12-22 23:32:49 +0400250 p.scanQRFor = ScanQRForJoin
251 if err := p.app.LaunchBarcodeScanner(); err != nil {
252 return err
253 }
giolekvaf58a7692021-12-15 18:05:39 +0400254 default:
255 return fmt.Errorf("Unhandled event: %#v", e)
256 }
257 }
258 return nil
259}
260
giolekva3f0dcda2021-12-22 23:32:49 +0400261type inviteQrCodeData struct {
giolekvaf58a7692021-12-15 18:05:39 +0400262 VPNApiAddr string `json:"vpn_api_addr"`
263 Message []byte `json:"message"`
264 Signature []byte `json:"signature"`
265}
266
giolekva3f0dcda2021-12-22 23:32:49 +0400267type joinQrCodeData struct {
268 EncPublicKey []byte `json:"enc_public_key"`
269 Name string `json:"name"`
270 NetPublicKey []byte `json:"net_public_key"`
271 IPCidr string `json:"ip_cidr"`
272}
273
giolekvaf58a7692021-12-15 18:05:39 +0400274func (p *processor) generateInviteQRCode() (image.Image, error) {
giolekva8d6a0ca2021-12-19 17:42:25 +0400275 config, err := p.st.Get()
276 if err != nil {
277 return nil, err
278 }
giolekvaf58a7692021-12-15 18:05:39 +0400279 message := []byte("Hello PCloud")
giolekva8d6a0ca2021-12-19 17:42:25 +0400280 signature, err := p.vc.Sign(config.ApiAddr, message)
giolekvaf58a7692021-12-15 18:05:39 +0400281 if err != nil {
282 return nil, err
283 }
giolekva3f0dcda2021-12-22 23:32:49 +0400284 c := inviteQrCodeData{
giolekva8d6a0ca2021-12-19 17:42:25 +0400285 config.ApiAddr,
giolekvaf58a7692021-12-15 18:05:39 +0400286 message,
287 signature,
288 }
289 var data bytes.Buffer
290 if err := json.NewEncoder(&data).Encode(c); err != nil {
291 return nil, err
292 }
293 qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
294 if err != nil {
295 return nil, err
296 }
297 img, err := png.Decode(bytes.NewReader(qr))
298 if err != nil {
299 return nil, err
300 }
301 return img, nil
302}
303
giolekva3f0dcda2021-12-22 23:32:49 +0400304func (p *processor) generateJoinQRCode() (image.Image, error) {
305 config, err := p.st.Get()
306 if err != nil {
307 return nil, err
308 }
309 hostname, err := p.app.GetHostname()
310 if err != nil {
311 return nil, err
312 }
313 hostname = sanitizeHostname(hostname)
314 c := joinQrCodeData{
315 EncPublicKey: config.Enc.PublicKey,
316 Name: hostname,
317 NetPublicKey: config.Network.PublicKey,
318 IPCidr: "111.0.0.14/24",
319 }
320 var data bytes.Buffer
321 if err := json.NewEncoder(&data).Encode(c); err != nil {
322 return nil, err
323 }
324 qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
325 if err != nil {
326 return nil, err
327 }
328 img, err := png.Decode(bytes.NewReader(qr))
329 if err != nil {
330 return nil, err
331 }
332 return img, nil
333}
334
giolekva8d6a0ca2021-12-19 17:42:25 +0400335func (p *processor) JoinAndGetNetworkConfig(code []byte) error {
giolekva3f0dcda2021-12-22 23:32:49 +0400336 config, err := p.st.Get()
337 if err != nil {
338 return err
339 }
340 var invite inviteQrCodeData
giolekvaf58a7692021-12-15 18:05:39 +0400341 if err := json.NewDecoder(bytes.NewReader(code)).Decode(&invite); err != nil {
giolekva1026d2d2021-12-19 19:09:15 +0400342 return err
giolekvaf58a7692021-12-15 18:05:39 +0400343 }
giolekva1026d2d2021-12-19 19:09:15 +0400344 hostname, err := p.app.GetHostname()
345 if err != nil {
346 return err
347 }
giolekva3f0dcda2021-12-22 23:32:49 +0400348 hostname = sanitizeHostname(hostname)
349 network, err := p.vc.Join(invite.VPNApiAddr, hostname, config.Network.PublicKey, config.Network.PrivateKey, invite.Message, invite.Signature)
giolekvaf58a7692021-12-15 18:05:39 +0400350 if err != nil {
giolekva8d6a0ca2021-12-19 17:42:25 +0400351 return err
giolekvaf58a7692021-12-15 18:05:39 +0400352 }
giolekva3f0dcda2021-12-22 23:32:49 +0400353 config.ApiAddr = invite.VPNApiAddr
354 config.Network.Config = network
355 if err := p.st.Store(config); err != nil {
giolekva8d6a0ca2021-12-19 17:42:25 +0400356 return err
giolekva52da88a2021-12-17 18:08:25 +0400357 }
giolekva8d6a0ca2021-12-19 17:42:25 +0400358 p.onConfigCh <- struct{}{}
359 return nil
giolekvaf58a7692021-12-15 18:05:39 +0400360}
361
giolekva3f0dcda2021-12-22 23:32:49 +0400362func (p *processor) ApproveOther(code []byte) error {
363 config, err := p.st.Get()
364 if err != nil {
365 return err
366 }
367 var approve joinQrCodeData
368 if err := json.NewDecoder(bytes.NewReader(code)).Decode(&approve); err != nil {
369 return err
370 }
371 return p.vc.Approve(config.ApiAddr, approve.Name, approve.IPCidr, approve.EncPublicKey, approve.NetPublicKey)
372}
373
374func sanitizeHostname(hostname string) string {
375 return strings.ToLower(
376 strings.ReplaceAll(
377 strings.ReplaceAll(hostname, " ", "-"),
378 ".", "-"))
379}
380
giolekva313ee2b2021-12-15 15:17:29 +0400381func main() {
382 flag.Parse()
giolekvaf58a7692021-12-15 18:05:39 +0400383 p = newProcessor()
giolekva313ee2b2021-12-15 15:17:29 +0400384 go func() {
giolekvaf58a7692021-12-15 18:05:39 +0400385 if err := p.run(); err != nil {
giolekva313ee2b2021-12-15 15:17:29 +0400386 panic(err)
387 }
388 }()
389 app.Main()
390}