VPN: move certificate signing logit to api service to which controller delegates ops
diff --git a/core/nebula/api/main.go b/core/nebula/api/main.go
index 906a674..9127630 100644
--- a/core/nebula/api/main.go
+++ b/core/nebula/api/main.go
@@ -15,6 +15,7 @@
 	"io"
 	"io/ioutil"
 	"log"
+	"net"
 	"net/http"
 	"time"
 
@@ -262,8 +263,6 @@
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	fmt.Println("---- APPROVE")
-	fmt.Printf("%#v\n", req)
 	_, _, err := h.mgr.CreateNode(
 		*namespace,
 		req.Name,
@@ -280,6 +279,59 @@
 	}
 }
 
+type processCAReq struct {
+	Name string `json:"name"`
+}
+
+func (h *Handler) processCA(w http.ResponseWriter, r *http.Request) {
+	var req processCAReq
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	ca, err := CreateCertificateAuthority(req.Name)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(ca); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
+type processNodeReq struct {
+	CAPrivateKey  []byte `json:"ca_private_key"`
+	CACert        []byte `json:"ca_certificate"`
+	NodeName      string `json:"node_name"`
+	NodePublicKey []byte `json:"node_public_key,omitempty"`
+	NodeIPCidr    string `json:"node_ip_cidr"`
+}
+
+func (h *Handler) processNode(w http.ResponseWriter, r *http.Request) {
+	var req processNodeReq
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	_, ipNet, err := net.ParseCIDR(req.NodeIPCidr)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	node, err := SignNebulaNode(req.CAPrivateKey, req.CACert, req.NodeName, req.NodePublicKey, ipNet)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(node); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
 func loadConfigTemplate(path string) (map[string]interface{}, error) {
 	tmpl, err := ioutil.ReadFile(path)
 	if err != nil {
@@ -327,6 +379,8 @@
 	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("/api/process/authority", handler.processCA)
+	r.HandleFunc("/api/process/node", handler.processNode)
 	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("/", handler.handleIndex)
diff --git a/core/nebula/api/nebula.go b/core/nebula/api/nebula.go
new file mode 100644
index 0000000..b4649f1
--- /dev/null
+++ b/core/nebula/api/nebula.go
@@ -0,0 +1,125 @@
+package main
+
+import (
+	"crypto/ed25519"
+	"crypto/rand"
+	"errors"
+	"io"
+	"net"
+	"time"
+
+	"github.com/slackhq/nebula/cert"
+	"golang.org/x/crypto/curve25519"
+)
+
+type CertificateAuthority struct {
+	PrivateKey  []byte `json:"private_key"`
+	Certificate []byte `json:"certificate"`
+}
+
+func CreateCertificateAuthority(name string) (*CertificateAuthority, error) {
+	t := time.Now().Add(time.Duration(-1 * time.Second))
+	rawPub, rawPriv, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+	nc := cert.NebulaCertificate{
+		Details: cert.NebulaCertificateDetails{
+			Name:      name,
+			NotBefore: t,
+			NotAfter:  t.Add(time.Duration(8760 * time.Hour)),
+			PublicKey: rawPub,
+			IsCA:      true,
+		},
+	}
+	if err := nc.Sign(rawPriv); err != nil {
+		return nil, err
+	}
+	certSerialized, err := nc.MarshalToPEM()
+	if err != nil {
+		return nil, err
+	}
+	privKeySerialzied := cert.MarshalEd25519PrivateKey(rawPriv)
+	return &CertificateAuthority{
+		privKeySerialzied,
+		certSerialized,
+	}, nil
+}
+
+type NebulaNode struct {
+	PrivateKey  []byte `json:"private_key,omitempty"`
+	Certificate []byte `json:"certificate"`
+}
+
+func SignNebulaNode(rawCAPrivateKey []byte, rawCACert []byte, nodeName string, nodePublicKey []byte, ip *net.IPNet) (*NebulaNode, error) {
+	caKey, _, err := cert.UnmarshalEd25519PrivateKey(rawCAPrivateKey)
+	if err != nil {
+		return nil, err
+	}
+	caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCACert)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := caCert.VerifyPrivateKey(caKey); err != nil {
+		return nil, err
+	}
+	issuer, err := caCert.Sha256Sum()
+	if err != nil {
+		return nil, err
+	}
+	if caCert.Expired(time.Now()) {
+		return nil, errors.New("ca certificate is expired")
+	}
+	var pub, priv []byte
+	if nodePublicKey != nil {
+		var err error
+		pub, _, err = cert.UnmarshalX25519PublicKey(nodePublicKey)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		var rawPriv []byte
+		var err error
+		pub, rawPriv, err = x25519Keypair()
+		if err != nil {
+			return nil, err
+		}
+		priv = cert.MarshalX25519PrivateKey(rawPriv)
+	}
+	t := time.Now().Add(time.Duration(-1 * time.Second))
+	nc := cert.NebulaCertificate{
+		Details: cert.NebulaCertificateDetails{
+			Name:      nodeName,
+			Ips:       []*net.IPNet{ip},
+			NotBefore: t,
+			NotAfter:  caCert.Details.NotAfter.Add(time.Duration(-1 * time.Second)),
+			PublicKey: pub,
+			IsCA:      false,
+			Issuer:    issuer,
+		},
+	}
+	if err := nc.CheckRootConstrains(caCert); err != nil {
+		return nil, err
+	}
+	if err := nc.Sign(caKey); err != nil {
+		return nil, err
+	}
+	certSerialized, err := nc.MarshalToPEM()
+	if err != nil {
+		return nil, err
+	}
+	return &NebulaNode{
+		PrivateKey:  priv,
+		Certificate: certSerialized,
+	}, 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
+}