From 5c3f0ea94230bfdedcdab4c4a7ae2d47db2a9c93 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 30 Nov 2020 00:08:05 +0100 Subject: [PATCH] Add signer backend This commit adds a simple go backend calling openssl ca to sign CRS coming from the client. The JavaScript code in src/index.html has been extended to send requests to the sign endpoint and display the resulting certificate in a separate div element. A script setup_example_ca.sh and an openssl configuration file ca.cnf has been added to allow quick setup of a simple example CA. --- .gitignore | 2 + ca.cnf | 31 ++++++++++++ go.mod | 3 ++ main.go | 112 ++++++++++++++++++++++++++++++++++++++++++ setup_example_ca.sh | 11 +++++ src/index.html | 117 ++++++++++++++++++++++++++++---------------- 6 files changed, 233 insertions(+), 43 deletions(-) create mode 100644 ca.cnf create mode 100644 go.mod create mode 100644 main.go create mode 100755 setup_example_ca.sh diff --git a/.gitignore b/.gitignore index d11df8d..07605af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +*.pem .*.swp /.idea/ +/exampleca/ /node_modules/ /public/ diff --git a/ca.cnf b/ca.cnf new file mode 100644 index 0000000..09c4e18 --- /dev/null +++ b/ca.cnf @@ -0,0 +1,31 @@ +extensions = v3_ext + +[ca] +default_ca = EXAMPLECA + +[EXAMPLECA] +dir = ./exampleca +certs = $dir/certs +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir/newcerts +serial = $dir/serial +crl = $dir/crl.pem +certificate = $dir/ca.crt.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/private/ca.key.pem +RANDFILE = $dir/private/.rand +unique_subject = no +email_in_dn = no +default_md = sha256 + +[policy_match] +commonName = supplied + +[client_ext] +basicConstraints = critical,CA:false +keyUsage = keyEncipherment,digitalSignature,nonRepudiation +extendedKeyUsage = clientAuth,emailProtection +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c5f02ed --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.dittberner.info/jan/browser_csr_generation + +go 1.13 diff --git a/main.go b/main.go new file mode 100644 index 0000000..001c4ee --- /dev/null +++ b/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os/exec" + "time" +) + +type signCertificate struct{} + +type requestData struct { + Csr string `json:"csr"` + CommonName string `json:"commonName"` +} + +type responseData struct { + Certificate string `json:"certificate"` +} + +func (h *signCertificate) sign(csrPem string, commonName string) (certPem string, err error) { + log.Printf("received CSR for %s:\n\n%s", commonName, csrPem) + subjectDN := fmt.Sprintf("/CN=%s", commonName) + err = ioutil.WriteFile("in.pem", []byte(csrPem), 0644) + if err != nil { + log.Print(err) + return + } + opensslCommand := exec.Command( + "openssl", "ca", "-config", "ca.cnf", "-days", "365", + "-policy", "policy_match", "-extensions", "client_ext", + "-batch", "-subj", subjectDN, "-utf8", "-rand_serial", "-in", "in.pem") + var out, cmdErr bytes.Buffer + opensslCommand.Stdout = &out + opensslCommand.Stderr = &cmdErr + err = opensslCommand.Run() + if err != nil { + log.Print(err) + log.Print(cmdErr.String()) + return + } + certPem = out.String() + return +} + +func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Only POST requests support", http.StatusMethodNotAllowed) + return + } + if r.Header.Get("content-type") != "application/json" { + http.Error(w, "Only JSON content is accepted", http.StatusNotAcceptable) + return + } + var err error + var requestBody requestData + var certificate string + + if err = json.NewDecoder(r.Body).Decode(&requestBody); err != nil { + log.Print(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + certificate, err = h.sign(requestBody.Csr, requestBody.CommonName) + if err != nil { + http.Error(w, "Could not sign certificate", http.StatusInternalServerError) + return + } + + var jsonBytes []byte + if jsonBytes, err = json.Marshal(&responseData{Certificate: certificate}); err != nil { + log.Print(err) + } + + if _, err = w.Write(jsonBytes); err != nil { + log.Print(err) + } +} + +func main() { + tlsConfig := &tls.Config{ + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + NextProtos: []string{"h2"}, + PreferServerCipherSuites: true, + MinVersion: tls.VersionTLS12, + } + mux := http.NewServeMux() + mux.Handle("/", http.FileServer(http.Dir("public"))) + mux.Handle("/sign/", &signCertificate{}) + server := http.Server{ + Addr: ":8000", + Handler: mux, + TLSConfig: tlsConfig, + ReadTimeout: 20 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 30 * time.Second, + } + err := server.ListenAndServeTLS("server.crt.pem", "server.key.pem") + if err != nil { + log.Fatal(err) + } +} diff --git a/setup_example_ca.sh b/setup_example_ca.sh new file mode 100755 index 0000000..2d7da67 --- /dev/null +++ b/setup_example_ca.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +if [ ! -d "exampleca" ]; then + mkdir -p exampleca/newcerts + touch exampleca/index.txt + umask 077 + mkdir exampleca/private + openssl req -new -x509 -keyout exampleca/private/ca.key.pem -out exampleca/ca.crt.pem -days 3650 \ + -subj "/CN=Example CA" -nodes -newkey rsa:3072 -addext "basicConstraints=critical,CA:true,pathlen:0" + chmod +r exampleca/ca.crt.pem +fi \ No newline at end of file diff --git a/src/index.html b/src/index.html index 9e5a4ad..01acf34 100644 --- a/src/index.html +++ b/src/index.html @@ -41,7 +41,7 @@ An RSA key pair will be generated in your browser. Longer key sizes provide better security but take longer to generate. - + @@ -55,58 +55,89 @@

     

+    

+    
 
 
 
 
 
 
-
-
+
\ No newline at end of file