Client: send join request with signed message to verify validity
diff --git a/core/client/Makefile b/core/client/Makefile
index 93b2ccf..4c39648 100644
--- a/core/client/Makefile
+++ b/core/client/Makefile
@@ -1,6 +1,7 @@
-run:
- go run github.com/giolekva/pcloud/core/client/cmd/pcloud
+build:
+ go build -o pcloud github.com/giolekva/pcloud/core/client/cmd/pcloud
aar: export ANDROID_SDK_ROOT=/Users/lekva/Library/Android/sdk/
aar:
+ mkdir -p android/app/libs
go run gioui.org/cmd/gogio -target android -buildmode archive -o android/app/libs/pcloud.aar github.com/giolekva/pcloud/core/client/cmd/pcloud
diff --git a/core/client/android/app/libs/pcloud.aar b/core/client/android/app/libs/pcloud.aar
new file mode 100644
index 0000000..051cdeb
--- /dev/null
+++ b/core/client/android/app/libs/pcloud.aar
Binary files differ
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
index 3e737d4..ec1c76d 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
@@ -27,7 +27,6 @@
super.onCreate(savedInstanceState);
view = new GioView(this);
setContentView(view);
- qrcodeScanned("yayaya");
}
@Override
@@ -70,7 +69,7 @@
public String launchBarcodeScanner() {
ScanOptions options = new ScanOptions();
options.setDesiredBarcodeFormats(ScanOptions.QR_CODE);
- options.setPrompt("Join PCloud network");
+ options.setPrompt("Join PCloud mesh");
options.setCameraId(0); // Use a specific camera of the device
options.setBeepEnabled(true);
options.setBarcodeImageEnabled(false);
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
index d8b5723..1e2b64a 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
@@ -7,9 +7,7 @@
public class PCloudApp extends Application {
@Override
public void onCreate() {
- System.out.println("fooo");
super.onCreate();
Gio.init(this);
- System.out.println("bar");
}
}
diff --git a/core/client/cmd/pcloud/callbacks_android.go b/core/client/cmd/pcloud/callbacks_android.go
index 107b998..df399f9 100644
--- a/core/client/cmd/pcloud/callbacks_android.go
+++ b/core/client/cmd/pcloud/callbacks_android.go
@@ -3,7 +3,6 @@
// JNI implementations of Java native callback methods.
import (
- "fmt"
"unsafe"
"github.com/giolekva/pcloud/core/client/jni"
@@ -16,5 +15,5 @@
func Java_me_lekva_pcloud_PCloudActivity_qrcodeScanned(env *C.JNIEnv, this C.jobject, contents C.jobject) {
jenv := (*jni.Env)(unsafe.Pointer(env))
code := jni.GoString(jenv, jni.String(contents))
- fmt.Printf("!!!! QRCODE SCANNED: %s\n", code)
+ p.InviteQRCodeScanned([]byte(code))
}
diff --git a/core/client/cmd/pcloud/client.go b/core/client/cmd/pcloud/client.go
index 0d1fed4..aa1a3c3 100644
--- a/core/client/cmd/pcloud/client.go
+++ b/core/client/cmd/pcloud/client.go
@@ -2,15 +2,20 @@
import (
"bytes"
+ "crypto/rand"
"crypto/tls"
"encoding/json"
- "fmt"
+ "errors"
+ "io"
"net/http"
+
+ "golang.org/x/crypto/curve25519"
)
type VPNClient interface {
Address() string
Sign(message []byte) ([]byte, error)
+ Join(apiAddr string, message, signature []byte) (interface{}, error)
}
type directVPNClient struct {
@@ -46,7 +51,6 @@
}
r, err := client.Post(c.addr+"/api/sign", "application/json", &data)
if err != nil {
- fmt.Println(111111)
return nil, err
}
resp := &signResp{}
@@ -55,3 +59,60 @@
}
return resp.Signature, nil
}
+
+type joinReq struct {
+ Message []byte `json:"message"`
+ Signature []byte `json:"signature"`
+ Name string `json:"name"`
+ PublicKey []byte `json:"public_key"`
+ IPCidr string `json:"ip_cidr"`
+}
+
+type joinResp struct {
+}
+
+func (c *directVPNClient) Join(apiAddr string, message, signature []byte) (interface{}, error) {
+ if c.addr != "" {
+ return nil, errors.New("Already joined")
+ }
+ c.addr = apiAddr
+ pubKey, _, err := x25519Keypair()
+ if err != nil {
+ return nil, err
+ }
+ req := joinReq{
+ message,
+ signature,
+ "test",
+ pubKey,
+ "111.0.0.13/24",
+ }
+ var data bytes.Buffer
+ if err := json.NewEncoder(&data).Encode(req); err != nil {
+ return nil, err
+ }
+ client := &http.Client{
+ // TODO(giolekva): remove, for some reason valid certificates are not accepted on gioui android.
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ },
+ }
+ r, err := client.Post(c.addr+"/api/join", "application/json", &data)
+ if err != nil {
+ return nil, err
+ }
+ resp := &joinResp{}
+ if err := json.NewDecoder(r.Body).Decode(resp); err != nil {
+ return nil, err
+ }
+ return nil, nil
+}
+
+func x25519Keypair() ([]byte, []byte, error) {
+ var pubkey, privkey [32]byte
+ if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
+ return nil, nil, err
+ }
+ curve25519.ScalarBaseMult(&pubkey, &privkey)
+ return pubkey[:], privkey[:], nil
+}
diff --git a/core/client/cmd/pcloud/main.go b/core/client/cmd/pcloud/main.go
index 16fa940..e85fc42 100644
--- a/core/client/cmd/pcloud/main.go
+++ b/core/client/cmd/pcloud/main.go
@@ -1,34 +1,49 @@
package main
import (
+ "bytes"
+ "encoding/json"
"flag"
"fmt"
+ "image"
+ "image/png"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
+ "github.com/skip2/go-qrcode"
)
-var vpnApiAddr = flag.String("vpn-api-addr", "https://vpn.lekva.me", "VPN API server address")
+var vpnApiAddr = flag.String("vpn-api-addr", "", "VPN API server address")
-func processUIEvents(a App, events []UIEvent) error {
- for _, e := range events {
- switch e.(type) {
- case EventScanBarcode:
- return a.LaunchBarcodeScanner()
- default:
- return fmt.Errorf("Unhandled event: %#v", e)
- }
- }
- return nil
+var p *processor
+
+type processor struct {
+ vc VPNClient
+ app App
+ ui *UI
+
+ inviteQrCh chan image.Image
+ inviteQrScannedCh chan []byte
}
-func run() error {
- a := createApp()
- vc := NewDirectVPNClient(*vpnApiAddr)
- ui := NewUI(vc)
+func newProcessor() *processor {
+ return &processor{
+ vc: NewDirectVPNClient(*vpnApiAddr),
+ app: createApp(),
+ ui: NewUI(),
+ inviteQrCh: make(chan image.Image, 1),
+ inviteQrScannedCh: make(chan []byte, 1),
+ }
+}
+
+func (p *processor) InviteQRCodeScanned(code []byte) {
+ p.inviteQrScannedCh <- code
+}
+
+func (p *processor) run() error {
w := app.NewWindow(
app.Size(unit.Px(1500), unit.Px(1500)),
app.Title("PCloud"),
@@ -39,35 +54,109 @@
case e := <-w.Events():
switch e := e.(type) {
case app.ViewEvent:
- if err := a.OnView(e); err != nil {
+ if err := p.app.OnView(e); err != nil {
return err
} else {
w.Invalidate()
}
case *system.CommandEvent:
if e.Type == system.CommandBack {
- if ui.OnBack() {
+ if p.ui.OnBack() {
e.Cancel = true
w.Invalidate()
}
}
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
- events := ui.Layout(gtx)
+ events := p.ui.Layout(gtx)
e.Frame(&ops)
- if err := processUIEvents(a, events); err != nil {
+ if err := p.processUIEvents(events); err != nil {
return err
}
}
+ case img := <-p.inviteQrCh:
+ p.ui.InviteQRGenerated(img)
+ w.Invalidate()
+ case code := <-p.inviteQrScannedCh:
+ go func() {
+ p.JoinNetworkAndConnect(code)
+ }()
}
}
return nil
}
+func (p *processor) processUIEvents(events []UIEvent) error {
+ for _, e := range events {
+ switch e.(type) {
+ case EventGetInviteQRCode:
+ go func() {
+ if img, err := p.generateInviteQRCode(); err == nil {
+ p.inviteQrCh <- img
+ } else {
+ // TODO(giolekva): do not panic
+ panic(err)
+ }
+ }()
+ case EventScanBarcode:
+ return p.app.LaunchBarcodeScanner()
+ default:
+ return fmt.Errorf("Unhandled event: %#v", e)
+ }
+ }
+ return nil
+}
+
+type qrCodeData struct {
+ VPNApiAddr string `json:"vpn_api_addr"`
+ Message []byte `json:"message"`
+ Signature []byte `json:"signature"`
+}
+
+func (p *processor) generateInviteQRCode() (image.Image, error) {
+ message := []byte("Hello PCloud")
+ signature, err := p.vc.Sign(message)
+ if err != nil {
+ return nil, err
+ }
+ c := qrCodeData{
+ p.vc.Address(),
+ message,
+ signature,
+ }
+ var data bytes.Buffer
+ if err := json.NewEncoder(&data).Encode(c); err != nil {
+ return nil, err
+ }
+ qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
+ if err != nil {
+ return nil, err
+ }
+ img, err := png.Decode(bytes.NewReader(qr))
+ if err != nil {
+ return nil, err
+ }
+ return img, nil
+}
+
+func (p *processor) JoinNetworkAndConnect(code []byte) {
+ var invite qrCodeData
+ if err := json.NewDecoder(bytes.NewReader(code)).Decode(&invite); err != nil {
+ panic(err)
+ }
+ config, err := p.vc.Join(invite.VPNApiAddr, invite.Message, invite.Signature)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("-- VPN CONFIG %#v\n", config)
+
+}
+
func main() {
flag.Parse()
+ p = newProcessor()
go func() {
- if err := run(); err != nil {
+ if err := p.run(); err != nil {
panic(err)
}
}()
diff --git a/core/client/cmd/pcloud/ui.go b/core/client/cmd/pcloud/ui.go
index 2a0a382..50e74ff 100644
--- a/core/client/cmd/pcloud/ui.go
+++ b/core/client/cmd/pcloud/ui.go
@@ -1,18 +1,14 @@
package main
import (
- "bytes"
- "encoding/json"
"image"
"image/color"
- "image/png"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
- "github.com/skip2/go-qrcode"
)
type (
@@ -21,8 +17,6 @@
)
type UI struct {
- vc VPNClient
-
invite struct {
open widget.Clickable
show bool
@@ -36,10 +30,12 @@
}
}
-func NewUI(vc VPNClient) *UI {
- return &UI{
- vc: vc,
- }
+func NewUI() *UI {
+ return &UI{}
+}
+
+func (ui *UI) InviteQRGenerated(img image.Image) {
+ ui.invite.qr = img
}
func (ui *UI) OnBack() bool {
@@ -59,9 +55,10 @@
ui.join.show = false
ui.invite.show = true
ui.invite.qr = nil
+ events = append(events, EventGetInviteQRCode{})
} else if ui.join.open.Clicked() {
- ui.invite.show = false
- ui.join.show = true
+ // ui.invite.show = false
+ // ui.join.show = true
events = append(events, EventScanBarcode{})
}
if ui.invite.show {
@@ -114,11 +111,7 @@
func (ui *UI) layoutInvite(gtx C) D {
if ui.invite.qr == nil {
- img, err := prepareConfigQRCode(ui.vc)
- if err != nil {
- panic(err)
- }
- ui.invite.qr = img
+ return ColorBox(gtx, gtx.Constraints.Max, color.NRGBA{})
}
d := ui.invite.qr.Bounds().Max.Sub(ui.invite.qr.Bounds().Min)
return layout.Inset{
@@ -137,37 +130,3 @@
}
return ColorBox(gtx, gtx.Constraints.Min, color.NRGBA{R: 255, G: 255, B: 255, A: 255})
}
-
-// helpers
-
-type qrCodeData struct {
- VPNApiAddr string `json:"vpn_api_addr"`
- Message []byte `json:"message"`
- Signature []byte `json:"signature"`
-}
-
-func prepareConfigQRCode(vc VPNClient) (image.Image, error) {
- message := []byte("Hello PCloud")
- signature, err := vc.Sign(message)
- if err != nil {
- return nil, err
- }
- c := qrCodeData{
- vc.Address(),
- message,
- signature,
- }
- var data bytes.Buffer
- if err := json.NewEncoder(&data).Encode(c); err != nil {
- return nil, err
- }
- qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
- if err != nil {
- return nil, err
- }
- img, err := png.Decode(bytes.NewReader(qr))
- if err != nil {
- return nil, err
- }
- return img, nil
-}
diff --git a/core/client/cmd/pcloud/ui_events.go b/core/client/cmd/pcloud/ui_events.go
index 044abbf..581c5d4 100644
--- a/core/client/cmd/pcloud/ui_events.go
+++ b/core/client/cmd/pcloud/ui_events.go
@@ -3,3 +3,5 @@
type UIEvent interface{}
type EventScanBarcode struct{}
+
+type EventGetInviteQRCode struct{}
diff --git a/core/client/go.mod b/core/client/go.mod
index 4f10753..1460cca 100644
--- a/core/client/go.mod
+++ b/core/client/go.mod
@@ -7,5 +7,6 @@
gioui.org/cmd v0.0.0-20211214104907-f5c9d2725c7b
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/slackhq/nebula v1.5.1
+ golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 // indirect
)
diff --git a/core/client/pcloud b/core/client/pcloud
new file mode 100755
index 0000000..76ca2c2
--- /dev/null
+++ b/core/client/pcloud
Binary files differ