vpn client + api: add feature to approve one device from another
diff --git a/core/nebula/api/main.go b/core/nebula/api/main.go
index 1b74d8b..906a674 100644
--- a/core/nebula/api/main.go
+++ b/core/nebula/api/main.go
@@ -1,12 +1,18 @@
 package main
 
 import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha256"
 	"embed"
 	"encoding/base64"
 	"encoding/json"
 	"flag"
 	"fmt"
 	"html/template"
+	"io"
 	"io/ioutil"
 	"log"
 	"net/http"
@@ -83,35 +89,6 @@
 	w.Write(qr)
 }
 
-func (h *Handler) handleSignNode(w http.ResponseWriter, r *http.Request) {
-	if err := r.ParseForm(); err != nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
-		return
-	}
-	_, _, err := h.mgr.CreateNode(
-		r.FormValue("node-namespace"),
-		r.FormValue("node-name"),
-		r.FormValue("ca-namespace"),
-		r.FormValue("ca-name"),
-		r.FormValue("ip-cidr"),
-		r.FormValue("pub-key"),
-	)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	http.Redirect(w, r, "/", http.StatusSeeOther)
-}
-
-func (h *Handler) getNextIP(w http.ResponseWriter, r *http.Request) {
-	ip, err := h.mgr.getNextIP()
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	fmt.Fprint(w, ip)
-}
-
 type signReq struct {
 	Message []byte `json:"message"`
 }
@@ -149,9 +126,6 @@
 	IPCidr    string `json:"ip_cidr"`
 }
 
-type joinResp struct {
-}
-
 func (h *Handler) join(w http.ResponseWriter, r *http.Request) {
 	var req joinReq
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -174,6 +148,7 @@
 		*caName,
 		req.IPCidr,
 		string(req.PublicKey),
+		nil,
 	)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -200,6 +175,111 @@
 	}
 }
 
+type getResp struct {
+	Key   []byte `json:"key"`
+	Nonce []byte `json:"nonce"`
+	Data  []byte `json:"data"`
+}
+
+func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
+	fmt.Println("##### GET")
+	vars := mux.Vars(r)
+	pubKey, err := h.mgr.GetNodeEncryptionPublicKey(*namespace, vars["name"])
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	fmt.Println("Got key")
+	key := make([]byte, 32)
+	if _, err := io.ReadFull(rand.Reader, key); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	aesgcm, err := cipher.NewGCM(block)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	nonce := make([]byte, 12)
+	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
+		panic(err.Error())
+	}
+	for {
+		time.Sleep(1 * time.Second)
+		cfg, err := h.mgr.GetNodeConfig(*namespace, vars["name"])
+		if err != nil {
+			fmt.Println(err.Error())
+			continue
+		}
+		cfgBytes, err := yaml.Marshal(cfg)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		cfgEnc := aesgcm.Seal(nil, nonce, cfgBytes, nil)
+		keyEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, key, []byte(""))
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		nonceEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, nonce, []byte(""))
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		resp := getResp{
+			Key:   keyEnc,
+			Nonce: nonceEnc,
+			Data:  cfgEnc,
+		}
+		if err := json.NewEncoder(w).Encode(&resp); err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		break
+	}
+}
+
+type approveReq struct {
+	EncPublicKey []byte `json:"enc_public_key"`
+	Name         string `json:"name"`
+	NetPublicKey []byte `json:"net_public_key"`
+	IPCidr       string `json:"ip_cidr"`
+}
+
+type approveResp struct {
+}
+
+func (h *Handler) approve(w http.ResponseWriter, r *http.Request) {
+	var req approveReq
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	fmt.Println("---- APPROVE")
+	fmt.Printf("%#v\n", req)
+	_, _, err := h.mgr.CreateNode(
+		*namespace,
+		req.Name,
+		*namespace,
+		*caName,
+		req.IPCidr,
+		string(req.NetPublicKey),
+		req.EncPublicKey,
+	)
+	if err != nil {
+		fmt.Println(err.Error())
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
 func loadConfigTemplate(path string) (map[string]interface{}, error) {
 	tmpl, err := ioutil.ReadFile(path)
 	if err != nil {
@@ -243,12 +323,12 @@
 		tmpls: t,
 	}
 	r := mux.NewRouter()
-	r.HandleFunc("/api/ip", handler.getNextIP)
 	r.HandleFunc("/api/sign", handler.sign)
 	r.HandleFunc("/api/join", handler.join)
+	r.HandleFunc("/api/approve", handler.approve)
+	r.HandleFunc("/api/get/{name:[a-zA-z0-9-]+}", handler.get)
 	r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
 	r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
-	r.HandleFunc("/sign-node", handler.handleSignNode)
 	r.HandleFunc("/", handler.handleIndex)
 	http.Handle("/", r)
 	fmt.Printf("Starting HTTP server on port: %d\n", *port)