ovpn-certman/handlers/cert.go

277 lines
7 KiB
Go
Raw Normal View History

2018-01-26 08:43:53 +01:00
package handlers
import (
2018-02-01 09:31:06 +01:00
"bytes"
2018-01-26 08:43:53 +01:00
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
2018-02-03 18:14:47 +01:00
"html/template"
2018-01-26 08:43:53 +01:00
"io/ioutil"
"log"
"math/big"
"net/http"
"os"
2018-02-03 18:14:47 +01:00
"strings"
2018-01-26 08:43:53 +01:00
"time"
2018-01-29 09:18:19 +01:00
"git.klink.asia/paul/certman/models"
"git.klink.asia/paul/certman/services"
2018-02-01 09:31:06 +01:00
"github.com/go-chi/chi"
2018-01-26 08:43:53 +01:00
2018-01-29 09:18:19 +01:00
"git.klink.asia/paul/certman/views"
2018-01-26 08:43:53 +01:00
)
2018-02-01 09:31:06 +01:00
func ListClientsHandler(p *services.Provider) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
v := views.NewWithSession(req, p.Sessions)
2018-02-01 09:31:06 +01:00
username := p.Sessions.GetUsername(req)
2018-02-03 18:14:47 +01:00
clients, _ := p.ClientCollection.ListClientsForUser(username)
2018-02-01 09:31:06 +01:00
v.Vars["Clients"] = clients
v.Render(w, "client_list")
2018-01-29 09:18:19 +01:00
}
}
2018-01-26 08:43:53 +01:00
func CreateCertHandler(p *services.Provider) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
2018-02-01 09:31:06 +01:00
username := p.Sessions.GetUsername(req)
certname := req.FormValue("certname")
2018-02-03 18:14:47 +01:00
// Validate certificate Name
if !IsByteLength(certname, 2, 64) || !IsDNSName(certname) {
2018-02-01 09:31:06 +01:00
p.Sessions.Flash(w, req,
services.Flash{
Type: "danger",
Message: "The certificate name can only contain letters and numbers",
},
)
http.Redirect(w, req, "/certs", http.StatusFound)
return
}
2018-02-03 18:14:47 +01:00
// lowercase the certificate name, to avoid problems with the case
// insensitive matching inside OpenVPN
certname = strings.ToLower(certname)
// Load CA master certificate
caCert, caKey, err := loadX509KeyPair("ca.crt", "ca.key")
if err != nil {
log.Fatalf("error loading ca keyfiles: %s", err)
panic(err.Error())
}
// Generate Keypair
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatalf("Could not generate keypair: %s", err)
2018-02-01 09:31:06 +01:00
p.Sessions.Flash(w, req,
services.Flash{
Type: "danger",
Message: "The certificate key could not be generated",
},
)
http.Redirect(w, req, "/certs", http.StatusFound)
return
}
// Generate Certificate
2018-02-01 09:31:06 +01:00
commonName := fmt.Sprintf("%s@%s", username, certname)
derBytes, err := CreateCertificate(commonName, key, caCert, caKey)
// Initialize new client config
client := models.Client{
Name: certname,
2018-02-03 18:14:47 +01:00
CreatedAt: time.Now(),
PrivateKey: x509.MarshalPKCS1PrivateKey(key),
Cert: derBytes,
2018-02-01 09:31:06 +01:00
User: username,
}
// Insert client into database
2018-02-03 18:14:47 +01:00
if err := p.ClientCollection.CreateClient(&client); err != nil {
2018-02-01 09:31:06 +01:00
log.Println(err.Error())
p.Sessions.Flash(w, req,
services.Flash{
Type: "danger",
Message: "The certificate could not be added to the database",
},
)
http.Redirect(w, req, "/certs", http.StatusFound)
return
}
p.Sessions.Flash(w, req,
services.Flash{
Type: "success",
2018-02-01 09:31:06 +01:00
Message: "The certificate was created successfully",
},
)
http.Redirect(w, req, "/certs", http.StatusFound)
2018-01-26 08:43:53 +01:00
}
2018-01-29 09:18:19 +01:00
}
2018-02-03 18:14:47 +01:00
func DeleteCertHandler(p *services.Provider) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
v := views.New(req)
// detemine own username
username := p.Sessions.GetUsername(req)
name := chi.URLParam(req, "name")
client, err := p.ClientCollection.GetClientByNameUser(name, username)
if err != nil {
v.RenderError(w, http.StatusNotFound)
return
}
err = p.ClientCollection.DeleteClient(client.ID)
if err != nil {
p.Sessions.Flash(w, req,
services.Flash{
Type: "danger",
Message: "Failed to delete certificate",
},
)
http.Redirect(w, req, "/certs", http.StatusFound)
}
p.Sessions.Flash(w, req,
services.Flash{
Type: "success",
Message: template.HTML(fmt.Sprintf("Successfully deleted client <strong>%s</strong>", client.Name)),
},
)
http.Redirect(w, req, "/certs", http.StatusFound)
}
}
func DownloadCertHandler(p *services.Provider) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
2018-02-01 09:31:06 +01:00
v := views.New(req)
// detemine own username
username := p.Sessions.GetUsername(req)
name := chi.URLParam(req, "name")
2018-02-03 18:14:47 +01:00
client, err := p.ClientCollection.GetClientByNameUser(name, username)
2018-02-01 09:31:06 +01:00
if err != nil {
v.RenderError(w, http.StatusNotFound)
return
}
// cbuf and kbuf are buffers in which the PEM certificates are
// rendered into
var cbuf = new(bytes.Buffer)
var kbuf = new(bytes.Buffer)
pem.Encode(cbuf, &pem.Block{Type: "CERTIFICATE", Bytes: client.Cert})
pem.Encode(kbuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: client.PrivateKey})
ca, err := ioutil.ReadFile("ca.crt")
if err != nil {
log.Printf("Error loading ca file: %s", err)
v.RenderError(w, http.StatusInternalServerError)
return
}
ta, err := ioutil.ReadFile("ta.key")
if err != nil {
log.Printf("Error loading ta file: %s", err)
v.RenderError(w, http.StatusInternalServerError)
return
}
2018-02-01 09:31:06 +01:00
vars := map[string]string{
"CA": string(ca),
"TA": string(ta),
"Cert": cbuf.String(),
"Key": kbuf.String(),
"User": username,
"Name": name,
"Dev": os.Getenv("VPN_DEV"),
"Host": os.Getenv("VPN_HOST"),
"Port": os.Getenv("VPN_PORT"),
"Proto": os.Getenv("VPN_PROTO"),
2018-02-01 09:31:06 +01:00
}
t, err := views.GetTemplate("config.ovpn")
if err != nil {
log.Printf("Error loading certificate template: %s", err)
v.RenderError(w, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/x-openvpn-profile")
w.Header().Set("Content-Disposition", "attachment; filename=\"config.ovpn\"")
w.WriteHeader(http.StatusOK)
t.Execute(w, vars)
return
}
2018-01-26 08:43:53 +01:00
}
func loadX509KeyPair(certFile, keyFile string) (*x509.Certificate, *rsa.PrivateKey, error) {
cf, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, nil, err
}
kf, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, nil, err
}
2018-02-03 18:14:47 +01:00
cpb, _ := pem.Decode(cf)
kpb, _ := pem.Decode(kf)
2018-01-26 08:43:53 +01:00
crt, err := x509.ParseCertificate(cpb.Bytes)
if err != nil {
return nil, nil, err
}
key, err := x509.ParsePKCS1PrivateKey(kpb.Bytes)
if err != nil {
return nil, nil, err
}
return crt, key, nil
}
// CreateCertificate creates a CA-signed certificate
2018-02-01 09:31:06 +01:00
func CreateCertificate(commonName string, key interface{}, caCert *x509.Certificate, caKey interface{}) ([]byte, error) {
2018-01-26 08:43:53 +01:00
subj := caCert.Subject
// .. except for the common name
2018-02-01 09:31:06 +01:00
subj.CommonName = commonName
2018-01-26 08:43:53 +01:00
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Obscure error in cert serial number generation: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: subj,
2018-02-03 18:14:47 +01:00
NotBefore: time.Now().Add(-5 * time.Minute), // account for clock shift
NotAfter: time.Now().Add(24 * time.Hour * 356 * 5), // 5 years ought to be enough!
2018-01-26 08:43:53 +01:00
2018-02-03 18:14:47 +01:00
SignatureAlgorithm: x509.SHA256WithRSA,
2018-01-26 08:43:53 +01:00
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
return x509.CreateCertificate(rand.Reader, &template, caCert, publicKey(key), caKey)
}
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}