Add CA chain to download, improve UI
This commit is contained in:
parent
a960a60ecd
commit
b748050de3
6 changed files with 222 additions and 97 deletions
|
@ -6,6 +6,14 @@ other = "Zertifikats-Signier-Anfrage erzeugen"
|
||||||
hash = "sha1-f1a8f21b12fe51250da4a11f1c6ab28eab69b69d"
|
hash = "sha1-f1a8f21b12fe51250da4a11f1c6ab28eab69b69d"
|
||||||
other = "CSR-Erzeugung im Browser"
|
other = "CSR-Erzeugung im Browser"
|
||||||
|
|
||||||
|
[DownloadDescription]
|
||||||
|
hash = "sha1-f4a7826398e5c57c7feb4709ee939ea655f05469"
|
||||||
|
other = "Dein Schlüsselmaterial ist bereit zum Herunterladen. Die herunterladbare Datei enthält deinen privaten Schlüssel und dein Zertifikat verschlüsselt mit deinem Passwort. Du kannst die Datei jetzt verwenden, um dein Zertifikat in deinem Browser oder anderen Anwendungen zu installieren."
|
||||||
|
|
||||||
|
[DownloadLabel]
|
||||||
|
hash = "sha1-a479c9c34e878d07b4d67a73a48f432ad7dc53c8"
|
||||||
|
other = "Herunterladen"
|
||||||
|
|
||||||
["JavaScript.KeyGen.Generated"]
|
["JavaScript.KeyGen.Generated"]
|
||||||
hash = "sha1-34cdfcdc837e3fc052733a3588cc3923b793103e"
|
hash = "sha1-34cdfcdc837e3fc052733a3588cc3923b793103e"
|
||||||
other = "Schlüssel in __seconds__ Sekunden erzeugt"
|
other = "Schlüssel in __seconds__ Sekunden erzeugt"
|
||||||
|
@ -50,10 +58,6 @@ other = "In Deinem Browser wird ein RSA-Schlüsselpaar erzeugt. Größere Schlü
|
||||||
hash = "sha1-bd446df78ad62000d6516a95594a24b98688e1fa"
|
hash = "sha1-bd446df78ad62000d6516a95594a24b98688e1fa"
|
||||||
other = "RSA-Schlüssellänge"
|
other = "RSA-Schlüssellänge"
|
||||||
|
|
||||||
[SendCSRButtonLabel]
|
|
||||||
hash = "sha1-376b8bd1617b2c9d54272604677b1d75d3e6f477"
|
|
||||||
other = "Signieranfrage abschicken"
|
|
||||||
|
|
||||||
[StatusLoading]
|
[StatusLoading]
|
||||||
hash = "sha1-530afa5bce434b05e3a10e83ff2567f7f8622af9"
|
hash = "sha1-530afa5bce434b05e3a10e83ff2567f7f8622af9"
|
||||||
other = "Lade ..."
|
other = "Lade ..."
|
||||||
|
|
|
@ -6,6 +6,14 @@ other = "Generate signing request"
|
||||||
hash = "sha1-f1a8f21b12fe51250da4a11f1c6ab28eab69b69d"
|
hash = "sha1-f1a8f21b12fe51250da4a11f1c6ab28eab69b69d"
|
||||||
other = "CSR generation in browser"
|
other = "CSR generation in browser"
|
||||||
|
|
||||||
|
[DownloadDescription]
|
||||||
|
hash = "sha1-f4a7826398e5c57c7feb4709ee939ea655f05469"
|
||||||
|
other = "Your key material is ready for download. The downloadable file contains your private key and your certificate encrypted with your password. You can now use the file to install your certificate in your browser or other applications."
|
||||||
|
|
||||||
|
[DownloadLabel]
|
||||||
|
hash = "sha1-a479c9c34e878d07b4d67a73a48f432ad7dc53c8"
|
||||||
|
other = "Download"
|
||||||
|
|
||||||
["JavaScript.KeyGen.Generated"]
|
["JavaScript.KeyGen.Generated"]
|
||||||
hash = "sha1-34cdfcdc837e3fc052733a3588cc3923b793103e"
|
hash = "sha1-34cdfcdc837e3fc052733a3588cc3923b793103e"
|
||||||
other = "key generated in __seconds__ seconds"
|
other = "key generated in __seconds__ seconds"
|
||||||
|
@ -50,10 +58,6 @@ other = "An RSA key pair will be generated in your browser. Longer key sizes pro
|
||||||
hash = "sha1-bd446df78ad62000d6516a95594a24b98688e1fa"
|
hash = "sha1-bd446df78ad62000d6516a95594a24b98688e1fa"
|
||||||
other = "RSA Key Size"
|
other = "RSA Key Size"
|
||||||
|
|
||||||
[SendCSRButtonLabel]
|
|
||||||
hash = "sha1-376b8bd1617b2c9d54272604677b1d75d3e6f477"
|
|
||||||
other = "Send signing request"
|
|
||||||
|
|
||||||
[StatusLoading]
|
[StatusLoading]
|
||||||
hash = "sha1-530afa5bce434b05e3a10e83ff2567f7f8622af9"
|
hash = "sha1-530afa5bce434b05e3a10e83ff2567f7f8622af9"
|
||||||
other = "Loading ..."
|
other = "Loading ..."
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
CSRButtonLabel = "Generate signing request"
|
CSRButtonLabel = "Generate signing request"
|
||||||
CSRGenTitle = "CSR generation in browser"
|
CSRGenTitle = "CSR generation in browser"
|
||||||
|
DownloadDescription = "Your key material is ready for download. The downloadable file contains your private key and your certificate encrypted with your password. You can now use the file to install your certificate in your browser or other applications."
|
||||||
|
DownloadLabel = "Download"
|
||||||
"JavaScript.KeyGen.Generated" = "key generated in __seconds__ seconds"
|
"JavaScript.KeyGen.Generated" = "key generated in __seconds__ seconds"
|
||||||
"JavaScript.KeyGen.Running" = "key generation running for __seconds__ seconds"
|
"JavaScript.KeyGen.Running" = "key generation running for __seconds__ seconds"
|
||||||
"JavaScript.KeyGen.Started" = "started key generation"
|
"JavaScript.KeyGen.Started" = "started key generation"
|
||||||
|
@ -11,5 +13,4 @@ RSA3072Label = "3072 Bit"
|
||||||
RSA4096Label = "4096 Bit"
|
RSA4096Label = "4096 Bit"
|
||||||
RSAHelpText = "An RSA key pair will be generated in your browser. Longer key sizes provide better security but take longer to generate."
|
RSAHelpText = "An RSA key pair will be generated in your browser. Longer key sizes provide better security but take longer to generate."
|
||||||
RSAKeySizeLabel = "RSA Key Size"
|
RSAKeySizeLabel = "RSA Key Size"
|
||||||
SendCSRButtonLabel = "Send signing request"
|
|
||||||
StatusLoading = "Loading ..."
|
StatusLoading = "Loading ..."
|
||||||
|
|
|
@ -5,7 +5,6 @@ const rename = require('gulp-rename');
|
||||||
const replace = require('gulp-replace');
|
const replace = require('gulp-replace');
|
||||||
const sass = require('gulp-sass');
|
const sass = require('gulp-sass');
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
const sriHash = require('gulp-sri-hash');
|
|
||||||
const uglify = require('gulp-uglify');
|
const uglify = require('gulp-uglify');
|
||||||
|
|
||||||
sass.compiler = require('node-sass');
|
sass.compiler = require('node-sass');
|
||||||
|
@ -48,7 +47,7 @@ function publishAssets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function publish() {
|
function publish() {
|
||||||
return src('src/*.html').pipe(sriHash()).pipe(replace('../public/', '')).pipe(dest('public'));
|
return src('src/*.html').pipe(replace('../public/', '')).pipe(dest('public'));
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.default = series(
|
exports.default = series(
|
||||||
|
|
133
main.go
133
main.go
|
@ -4,14 +4,19 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
@ -30,9 +35,12 @@ type requestData struct {
|
||||||
|
|
||||||
type responseData struct {
|
type responseData struct {
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
|
CAChain []string `json:"ca_chain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *signCertificate) sign(csrPem string, commonName string) (certPem string, err error) {
|
var caCertificates []*x509.Certificate
|
||||||
|
|
||||||
|
func (h *signCertificate) sign(csrPem string, commonName string) (certPem string, caChain []string, err error) {
|
||||||
log.Printf("received CSR for %s:\n\n%s", commonName, csrPem)
|
log.Printf("received CSR for %s:\n\n%s", commonName, csrPem)
|
||||||
subjectDN := fmt.Sprintf("/CN=%s", commonName)
|
subjectDN := fmt.Sprintf("/CN=%s", commonName)
|
||||||
var csrFile *os.File
|
var csrFile *os.File
|
||||||
|
@ -68,7 +76,23 @@ func (h *signCertificate) sign(csrPem string, commonName string) (certPem string
|
||||||
log.Print(cmdErr.String())
|
log.Print(cmdErr.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
certPem = out.String()
|
|
||||||
|
var block *pem.Block
|
||||||
|
if block, _ = pem.Decode(out.Bytes()); block == nil {
|
||||||
|
err = fmt.Errorf("could not decode pem")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var certificate *x509.Certificate
|
||||||
|
if certificate, err = x509.ParseCertificate(block.Bytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
certPem = string(pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certificate.Raw,
|
||||||
|
}))
|
||||||
|
|
||||||
|
caChain, err = h.getCAChain(certificate)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +107,8 @@ func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var requestBody requestData
|
var requestBody requestData
|
||||||
var certificate string
|
|
||||||
|
var responseData responseData
|
||||||
|
|
||||||
if err = json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
|
if err = json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
@ -91,14 +116,14 @@ func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
certificate, err = h.sign(requestBody.Csr, requestBody.CommonName)
|
responseData.Certificate, responseData.CAChain, err = h.sign(requestBody.Csr, requestBody.CommonName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Could not sign certificate", http.StatusInternalServerError)
|
http.Error(w, "Could not sign certificate", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonBytes []byte
|
var jsonBytes []byte
|
||||||
if jsonBytes, err = json.Marshal(&responseData{Certificate: certificate}); err != nil {
|
if jsonBytes, err = json.Marshal(&responseData); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +132,39 @@ func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*signCertificate) getCAChain(certificate *x509.Certificate) ([]string, error) {
|
||||||
|
result := make([]string, 0)
|
||||||
|
|
||||||
|
appendCert := func(cert *x509.Certificate) {
|
||||||
|
result = append(
|
||||||
|
result,
|
||||||
|
string(pem.EncodeToMemory(&pem.Block{Bytes: cert.Raw, Type: "CERTIFICATE"})))
|
||||||
|
log.Debugf("added %s to cachain", result[len(result)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
var previous *x509.Certificate
|
||||||
|
for {
|
||||||
|
if len(caCertificates) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
for _, caCert := range caCertificates {
|
||||||
|
if previous == nil {
|
||||||
|
if bytes.Equal(caCert.RawSubject, certificate.RawIssuer) {
|
||||||
|
previous = caCert
|
||||||
|
appendCert(caCert)
|
||||||
|
}
|
||||||
|
} else if bytes.Equal(previous.RawSubject, previous.RawIssuer) {
|
||||||
|
return result, nil
|
||||||
|
} else if bytes.Equal(caCert.RawSubject, previous.RawIssuer) {
|
||||||
|
previous = caCert
|
||||||
|
appendCert(caCert)
|
||||||
|
} else {
|
||||||
|
log.Debugf("skipped certificate %s", caCert.Subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type indexHandler struct {
|
type indexHandler struct {
|
||||||
Bundle *i18n.Bundle
|
Bundle *i18n.Bundle
|
||||||
}
|
}
|
||||||
|
@ -158,9 +216,15 @@ func (i *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
ID: "StatusLoading",
|
ID: "StatusLoading",
|
||||||
Other: "Loading ...",
|
Other: "Loading ...",
|
||||||
}})
|
}})
|
||||||
sendCSRButtonLabel := localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{
|
downloadLabel := localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{
|
||||||
ID: "SendCSRButtonLabel",
|
ID: "DownloadLabel",
|
||||||
Other: "Send signing request",
|
Other: "Download",
|
||||||
|
}})
|
||||||
|
downloadDescription := localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{
|
||||||
|
ID: "DownloadDescription",
|
||||||
|
Other: "Your key material is ready for download. The downloadable file contains your private key and your" +
|
||||||
|
" certificate encrypted with your password. You can now use the file to install your certificate in your" +
|
||||||
|
" browser or other applications.",
|
||||||
}})
|
}})
|
||||||
|
|
||||||
t := template.Must(template.ParseFiles("templates/index.html"))
|
t := template.Must(template.ParseFiles("templates/index.html"))
|
||||||
|
@ -176,7 +240,8 @@ func (i *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
"RSAHelpText": rsaHelpText,
|
"RSAHelpText": rsaHelpText,
|
||||||
"CSRButtonLabel": csrButtonLabel,
|
"CSRButtonLabel": csrButtonLabel,
|
||||||
"StatusLoading": statusLoading,
|
"StatusLoading": statusLoading,
|
||||||
"SendCSRButtonLabel": sendCSRButtonLabel,
|
"DownloadDescription": downloadDescription,
|
||||||
|
"DownloadLabel": downloadLabel,
|
||||||
csrf.TemplateTag: csrf.TemplateField(r),
|
csrf.TemplateTag: csrf.TemplateField(r),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -238,6 +303,26 @@ func generateRandomBytes(count int) []byte {
|
||||||
return randomBytes
|
return randomBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
caCertificates = make([]*x509.Certificate, 2)
|
||||||
|
for index, certFile := range []string{"example_ca/sub/ca.crt.pem", "example_ca/root/ca.crt.pem"} {
|
||||||
|
var certBytes []byte
|
||||||
|
if certBytes, err = ioutil.ReadFile(certFile); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
var block *pem.Block
|
||||||
|
if block, _ = pem.Decode(certBytes); block == nil {
|
||||||
|
log.Panicf("no PEM data found in %s", certFile)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if caCertificates[index], err = x509.ParseCertificate(block.Bytes); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Infof("read %d CA certificates", len(caCertificates))
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
CipherSuites: []uint16{
|
CipherSuites: []uint16{
|
||||||
|
@ -258,7 +343,21 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
csrfKey := generateRandomBytes(32)
|
|
||||||
|
var csrfKey []byte = nil
|
||||||
|
|
||||||
|
if csrfB64, exists := os.LookupEnv("CSRF_KEY"); exists {
|
||||||
|
csrfKey, _ = base64.RawStdEncoding.DecodeString(csrfB64)
|
||||||
|
log.Info("read CSRF key from environment variable")
|
||||||
|
}
|
||||||
|
if csrfKey == nil {
|
||||||
|
csrfKey = generateRandomBytes(32)
|
||||||
|
log.Infof(
|
||||||
|
"generated new random CSRF key, set environment variable CSRF_KEY to %s to "+
|
||||||
|
"keep the same key for new sessions",
|
||||||
|
base64.RawStdEncoding.EncodeToString(csrfKey))
|
||||||
|
}
|
||||||
|
|
||||||
mux.Handle("/sign/", &signCertificate{})
|
mux.Handle("/sign/", &signCertificate{})
|
||||||
mux.Handle("/", &indexHandler{Bundle: bundle})
|
mux.Handle("/", &indexHandler{Bundle: bundle})
|
||||||
fileServer := http.FileServer(http.Dir("./public"))
|
fileServer := http.FileServer(http.Dir("./public"))
|
||||||
|
@ -274,8 +373,22 @@ func main() {
|
||||||
WriteTimeout: 30 * time.Second,
|
WriteTimeout: 30 * time.Second,
|
||||||
IdleTimeout: 30 * time.Second,
|
IdleTimeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
err := server.ListenAndServeTLS("server.crt.pem", "server.key.pem")
|
err := server.ListenAndServeTLS("server.crt.pem", "server.key.pem")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
var hostPort string
|
||||||
|
if strings.HasPrefix(server.Addr, ":") {
|
||||||
|
hostPort = fmt.Sprintf("localhost%s", server.Addr)
|
||||||
|
} else {
|
||||||
|
hostPort = server.Addr
|
||||||
|
}
|
||||||
|
log.Infof("started web server on https://%s/", hostPort)
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
s := <-c
|
||||||
|
log.Infof("received %s, shutting down", s)
|
||||||
|
_ = server.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link rel="stylesheet" href="css/styles.min.css"
|
<link rel="stylesheet" href="css/styles.min.css">
|
||||||
integrity="sha384-vKuz4xd0kXa+x9wRdibDAVE8gXC/1up2T9QVSas8Rk07AZhzOzbwFdj00XUjOO4i" crossorigin="anonymous">
|
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
|
@ -45,40 +44,39 @@
|
||||||
</div>
|
</div>
|
||||||
<small id="keySizeHelp" class="form-text text-muted">{{ .RSAHelpText }}</small>
|
<small id="keySizeHelp" class="form-text text-muted">{{ .RSAHelpText }}</small>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<button type="submit" id="gen-csr-button" class="btn btn-primary">{{ .CSRButtonLabel }}</button>
|
<button type="submit" id="action-button" class="btn btn-primary">{{ .CSRButtonLabel }}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="status-block" class="d-none row">
|
<div class="row d-none" id="status-block">
|
||||||
<div class="col-12">
|
<div class="col-12 py-3">
|
||||||
<div class="d-flex align-items-center">
|
<div class="progress" style="height: 2rem">
|
||||||
<strong id="status-text">{{ .StatusLoading }}</strong>
|
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0"
|
||||||
<div class="spinner-border ml-auto" id="status-spinner" role="status" aria-hidden="true"></div>
|
aria-valuemax="4">{{ .StatusLoading }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-12 d-none" id="download-wrapper">
|
||||||
<div class="col-12">
|
<p class="text-info">{{ .DownloadDescription }}</p>
|
||||||
<div id="result">
|
<a href="#" class="btn btn-success" id="download-link">
|
||||||
<button type="button" disabled id="send-button" class="btn btn-default disabled">
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor"
|
||||||
{{ .SendCSRButtonLabel }}
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
</button>
|
<path fill-rule="evenodd"
|
||||||
|
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||||
|
</svg>
|
||||||
|
{{ .DownloadLabel }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<pre id="key" class="d-none"></pre>
|
||||||
|
<pre id="csr" class="d-none"></pre>
|
||||||
|
<pre id="crt" class="d-none"></pre>
|
||||||
</div>
|
</div>
|
||||||
<pre id="key"></pre>
|
<script src="js/jquery.min.js"></script>
|
||||||
<pre id="csr"></pre>
|
<script src="js/forge.all.min.js"></script>
|
||||||
<pre id="crt"></pre>
|
<script src="js/bootstrap.bundle.min.js"></script>
|
||||||
</div>
|
<script src="js/i18next.min.js"></script>
|
||||||
<script src="js/jquery.min.js" integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="js/forge.all.min.js" integrity="sha384-VfWVy4csHnuL0Tq/vQkZtIpDf4yhSLNf3aBffGj3wKUmyn1UPNx4v0Pzo9chiHu1"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="js/bootstrap.bundle.min.js"
|
|
||||||
integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="js/i18next.min.js" integrity="sha384-Juj1kpjwKBUTV6Yp9WHG4GdeoMxCmx0zBN9SkwlyrAh5QYWb3l4WrfG7oTv/b00a"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script>
|
<script>
|
||||||
async function postData(url = '', data = {}, csrfToken) {
|
async function postData(url = '', data = {}, csrfToken) {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
|
@ -98,7 +96,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
i18n.init({fallbackLng: 'en', debug: true}, (err) => {
|
i18n.init({fallbackLng: 'en', debug: true, useCookie: false}, (err) => {
|
||||||
if (err) return console.log('something went wrong loading', err);
|
if (err) return console.log('something went wrong loading', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -111,24 +109,27 @@
|
||||||
if (isNaN(keySize)) {
|
if (isNaN(keySize)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const spinner = document.getElementById('status-spinner');
|
|
||||||
const statusText = document.getElementById('status-text');
|
|
||||||
const statusBlock = document.getElementById('status-block');
|
const statusBlock = document.getElementById('status-block');
|
||||||
|
const progressBar = document.getElementById('progress-bar');
|
||||||
statusBlock.classList.remove('d-none');
|
statusBlock.classList.remove('d-none');
|
||||||
spinner.classList.remove('d-none');
|
|
||||||
|
|
||||||
|
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
|
||||||
|
progressBar.style.width = "25%";
|
||||||
|
progressBar.setAttribute("aria-valuenow", "1");
|
||||||
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
|
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
|
||||||
statusText.innerHTML = i18n.t('keygen.started');
|
progressBar.innerHTML = i18n.t('keygen.started');
|
||||||
const startDate = new Date();
|
const startDate = new Date();
|
||||||
const step = function () {
|
const step = function () {
|
||||||
let duration = (new Date()).getTime() - startDate.getTime();
|
let duration = (new Date()).getTime() - startDate.getTime();
|
||||||
let seconds = Math.floor(duration / 100) / 10;
|
let seconds = Math.floor(duration / 100) / 10;
|
||||||
if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
|
if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
|
||||||
setTimeout(step, 1);
|
setTimeout(step, 1);
|
||||||
statusText.innerHTML = i18n.t('keygen.running', {seconds: seconds}); // `key generation running for ${seconds} seconds`;
|
progressBar.innerHTML = i18n.t('keygen.running', {seconds: seconds});
|
||||||
} else {
|
} else {
|
||||||
statusText.innerHTML = i18n.t('keygen.generated', {seconds: seconds}); // ``
|
progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
|
||||||
spinner.classList.add('d-none');
|
progressBar.style.width = "50%";
|
||||||
|
progressBar.setAttribute("aria-valuenow", "2");
|
||||||
|
progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds});
|
||||||
const keys = state.keys;
|
const keys = state.keys;
|
||||||
keyElement.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
|
keyElement.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
|
||||||
const csr = forge.pki.createCertificationRequest();
|
const csr = forge.pki.createCertificationRequest();
|
||||||
|
@ -145,31 +146,34 @@
|
||||||
if (verified) {
|
if (verified) {
|
||||||
let csrPem = forge.pki.certificationRequestToPem(csr);
|
let csrPem = forge.pki.certificationRequestToPem(csr);
|
||||||
document.getElementById("csr").innerHTML = csrPem;
|
document.getElementById("csr").innerHTML = csrPem;
|
||||||
const sendButton =
|
progressBar.style.width = "75%";
|
||||||
document.getElementById("send-button");
|
progressBar.setAttribute("aria-valuenow", "3");
|
||||||
sendButton.addEventListener("click", function () {
|
|
||||||
postData("/sign/", {"csr": csrPem, "commonName": subject}, csrfToken)
|
postData("/sign/", {"csr": csrPem, "commonName": subject}, csrfToken)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log(data);
|
|
||||||
document.getElementById("crt").innerHTML = data["certificate"];
|
document.getElementById("crt").innerHTML = data["certificate"];
|
||||||
const certificate = forge.pki.certificateFromPem(data["certificate"]);
|
let certificates = []
|
||||||
|
certificates.push(forge.pki.certificateFromPem(data["certificate"]));
|
||||||
|
|
||||||
|
for (let certificatePemData of data["ca_chain"]) {
|
||||||
|
certificates.push(forge.pki.certificateFromPem(certificatePemData));
|
||||||
|
}
|
||||||
|
|
||||||
// browsers have trouble importing anything but 3des encrypted PKCS#12
|
// browsers have trouble importing anything but 3des encrypted PKCS#12
|
||||||
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
|
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
|
||||||
keys.privateKey, certificate, password,
|
keys.privateKey, certificates, password,
|
||||||
{algorithm: '3des'}
|
{algorithm: '3des'}
|
||||||
);
|
);
|
||||||
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
|
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
|
||||||
const p12B64 = forge.util.encode64(p12Der);
|
const p12B64 = forge.util.encode64(p12Der);
|
||||||
|
|
||||||
const a = document.createElement('a');
|
const downloadLink = document.getElementById('download-link');
|
||||||
a.download = 'client_certificate.p12';
|
downloadLink.download = 'client_certificate.p12';
|
||||||
a.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
|
downloadLink.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
|
||||||
a.appendChild(document.createTextNode("Download"));
|
|
||||||
document.getElementById('result').appendChild(a);
|
document.getElementById('download-wrapper').classList.remove("d-none");
|
||||||
|
progressBar.style.width = "100%";
|
||||||
|
progressBar.setAttribute("aria-valuenow", "4");
|
||||||
});
|
});
|
||||||
});
|
|
||||||
sendButton.removeAttribute("disabled");
|
|
||||||
sendButton.classList.remove("disabled");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue