mirror of
https://github.com/outbackdingo/labca.git
synced 2026-01-27 10:19:34 +00:00
Add way to renew (extend lifetime of) CA certificates (#74)
This commit is contained in:
@@ -32,6 +32,7 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
unzip \
|
||||
zip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -58,8 +58,10 @@ setup_nginx_data() {
|
||||
|
||||
[ -e /opt/labca/data/root-ca.crl ] && cp /opt/labca/data/root-ca.crl crl/ || true
|
||||
[ -e /opt/labca/data/root-ca.pem ] && cp /opt/labca/data/root-ca.pem certs/ || true
|
||||
[ -e /opt/labca/data/root-ca.pem ] && ln -sf root-ca.pem certs/test-root.pem || true
|
||||
[ -e /opt/labca/data/root-ca.der ] && cp /opt/labca/data/root-ca.der certs/ || true
|
||||
[ -e /opt/labca/data/issuer/ca-int.pem ] && cp /opt/labca/data/issuer/ca-int.pem certs/ || true
|
||||
[ -e /opt/labca/data/issuer/ca-int.pem ] && ln -sf ca-int.pem certs/test-ca.pem || true
|
||||
[ -e /opt/labca/data/issuer/ca-int.pem ] && cp /opt/labca/data/issuer/ca-int.der certs/ || true
|
||||
|
||||
if [ ! -e /etc/nginx/ssl/labca_cert.pem ]; then
|
||||
|
||||
11
gui/apply
11
gui/apply
@@ -8,9 +8,10 @@ dataDir="$baseDir/data"
|
||||
export PKI_ROOT_CERT_BASE="$dataDir/root-ca"
|
||||
export PKI_INT_CERT_BASE="$dataDir/issuer/ca-int"
|
||||
|
||||
cd /opt/wwwstatic
|
||||
cd /opt/boulder/labca
|
||||
$baseDir/apply-boulder
|
||||
|
||||
$baseDir/apply-nginx
|
||||
cd /opt/wwwstatic
|
||||
|
||||
if [ -e "$PKI_ROOT_CERT_BASE.crl" ]; then
|
||||
cp $PKI_ROOT_CERT_BASE.crl crl/
|
||||
@@ -18,10 +19,10 @@ else
|
||||
echo "WARNING: no Root CRL file present - please upload one from the manage page"
|
||||
fi
|
||||
cp $PKI_ROOT_CERT_BASE.pem certs/
|
||||
ln -sf root-ca.pem certs/test-root.pem
|
||||
cp $PKI_ROOT_CERT_BASE.der certs/
|
||||
cp $PKI_INT_CERT_BASE.pem certs/
|
||||
ln -sf ca-int.pem certs/test-ca.pem
|
||||
cp $PKI_INT_CERT_BASE.der certs/
|
||||
|
||||
|
||||
cd /opt/boulder/labca
|
||||
$baseDir/apply-boulder
|
||||
$baseDir/apply-nginx
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
baseDir=$(dirname $0)
|
||||
baseDir=$(cd $(dirname $0) && pwd)
|
||||
dataDir="$baseDir/data"
|
||||
|
||||
PKI_DNS=$(grep dns $dataDir/config.json | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
baseDir=$(dirname $0)
|
||||
baseDir=$(cd $(dirname $0) && pwd)
|
||||
dataDir="$baseDir/data"
|
||||
|
||||
PKI_WEB_TITLE=$(grep web_title $dataDir/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
|
||||
@@ -2,25 +2,36 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"math/big"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CertificateInfo contains all data related to a certificate (file)
|
||||
type CertificateInfo struct {
|
||||
IsRoot bool
|
||||
IsFirst bool
|
||||
KeyTypes map[string]string
|
||||
KeyType string
|
||||
CreateType string
|
||||
IsRootGenerated bool
|
||||
RootSubject string
|
||||
RootEnddate string
|
||||
NumDays int
|
||||
|
||||
Country string
|
||||
Organization string
|
||||
@@ -115,7 +126,7 @@ func reportError(param interface{}) error {
|
||||
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
|
||||
res := errors.New("Error! See LabCA logs for details")
|
||||
res := errors.New("error: see LabCA logs for details")
|
||||
switch v := param.(type) {
|
||||
case error:
|
||||
res = errors.New("Error (" + v.Error() + ")! See LabCA logs for details")
|
||||
@@ -151,7 +162,7 @@ func preCreateTasks(path string) error {
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path + "serial"); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + "serial"); errors.Is(err, fs.ErrNotExist) {
|
||||
s, err := getRandomSerial()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -160,7 +171,7 @@ func preCreateTasks(path string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path + "crlnumber"); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + "crlnumber"); errors.Is(err, fs.ErrNotExist) {
|
||||
if err = os.WriteFile(path+"crlnumber", []byte("1000\n"), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -173,6 +184,23 @@ func preCreateTasks(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateRootCRLDays(filename string, numDays int) error {
|
||||
read, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not read '" + filename + "': " + err.Error())
|
||||
}
|
||||
re := regexp.MustCompile(`(default_crl_days\s*=).*`)
|
||||
res := re.ReplaceAll(read, []byte("$1 "+strconv.Itoa(numDays)))
|
||||
|
||||
if err = os.WriteFile(filename, res, 0640); err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not write '" + filename + "': " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate a key and certificate file for the data from this CertificateInfo
|
||||
func (ci *CertificateInfo) Generate(path string, certBase string) error {
|
||||
// 1. Generate key
|
||||
@@ -217,14 +245,18 @@ func (ci *CertificateInfo) Generate(path string, certBase string) error {
|
||||
subject = strings.Replace(subject, " ", "\\\\", -1)
|
||||
|
||||
if ci.IsRoot {
|
||||
if _, err := exeCmd("openssl req -config " + path + "openssl.cnf -days 3650 -new -utf8 -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil {
|
||||
if _, err := exeCmd("openssl req -config " + path + "openssl.cnf -days " + strconv.Itoa(ci.NumDays) + " -new -utf8 -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
if err := updateRootCRLDays(path+"openssl.cnf", ci.NumDays); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
} else {
|
||||
if _, err := exeCmd("openssl req -config " + path + "openssl.cnf -new -utf8 -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".csr"); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
if out, err := exeCmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days 3600 -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil {
|
||||
if out, err := exeCmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days " + strconv.Itoa(ci.NumDays) + " -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil {
|
||||
if strings.Contains(string(out), "root-ca.key for reading, No such file or directory") {
|
||||
return errors.New("NO_ROOT_KEY")
|
||||
}
|
||||
@@ -321,7 +353,7 @@ func (ci *CertificateInfo) Import(tmpDir string, tmpKey string, tmpCert string)
|
||||
return err
|
||||
}
|
||||
|
||||
} else if contentType == "application/zip" {
|
||||
} else if contentType == "application/zip" || contentType == "application/x-zip-compressed" {
|
||||
err := ci.ImportZip(tmpFile, tmpDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -377,7 +409,7 @@ func (ci *CertificateInfo) Upload(tmpKey string, tmpCert string) error {
|
||||
func parseSubjectDn(subject string) map[string]string {
|
||||
trackerResultMap := map[string]string{"C=": "", "C =": "", "O=": "", "O =": "", "CN=": "", "CN =": "", "OU=": "", "OU =": ""}
|
||||
|
||||
for tracker, _ := range trackerResultMap {
|
||||
for tracker := range trackerResultMap {
|
||||
index := strings.Index(subject, tracker)
|
||||
|
||||
if index < 0 {
|
||||
@@ -449,7 +481,7 @@ func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey str
|
||||
}
|
||||
|
||||
keyFileExists := true
|
||||
if _, err := os.Stat(rootKey); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(rootKey); errors.Is(err, fs.ErrNotExist) {
|
||||
keyFileExists = false
|
||||
}
|
||||
if keyFileExists {
|
||||
@@ -499,7 +531,7 @@ func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey str
|
||||
return errors.New("issuer not issued by our Root CA")
|
||||
}
|
||||
|
||||
r, err = exeCmd("openssl verify -CAfile data/root-ca.pem " + issuerCert)
|
||||
_, err = exeCmd("openssl verify -CAfile data/root-ca.pem " + issuerCert)
|
||||
if err != nil {
|
||||
return errors.New("could not verify that issuer was issued by our Root CA")
|
||||
}
|
||||
@@ -524,7 +556,7 @@ func (ci *CertificateInfo) MoveFiles(path string, rootCert string, rootKey strin
|
||||
}
|
||||
if rootKey != "" {
|
||||
keyFileExists := true
|
||||
if _, err := os.Stat(rootKey); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(rootKey); errors.Is(err, fs.ErrNotExist) {
|
||||
keyFileExists = false
|
||||
}
|
||||
if keyFileExists {
|
||||
@@ -564,26 +596,60 @@ func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string,
|
||||
rootCert = filepath.Join(tmpDir, "root-ca.pem")
|
||||
rootKey = filepath.Join(tmpDir, "root-ca.key")
|
||||
|
||||
if _, err := os.Stat(rootCert); os.IsNotExist(err) {
|
||||
return errors.New("file does not contain root-ca.pem")
|
||||
if _, err := os.Stat(rootCert); errors.Is(err, fs.ErrNotExist) {
|
||||
altCert := filepath.Join(tmpDir, "test-root.pem")
|
||||
if _, err = os.Stat(altCert); err == nil {
|
||||
if _, err := exeCmd("mv " + altCert + " " + rootCert); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
altKey := filepath.Join(tmpDir, "test-root.key")
|
||||
if _, err = os.Stat(altKey); err == nil {
|
||||
if _, err := exeCmd("mv " + altKey + " " + rootKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(rootCert); errors.Is(err, fs.ErrNotExist) {
|
||||
return errors.New("file does not contain root-ca.pem")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issuerCert = filepath.Join(tmpDir, "ca-int.pem")
|
||||
issuerKey = filepath.Join(tmpDir, "ca-int.key")
|
||||
|
||||
if _, err := os.Stat(issuerCert); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(issuerCert); errors.Is(err, fs.ErrNotExist) {
|
||||
if ci.IsRoot {
|
||||
issuerCert = ""
|
||||
} else {
|
||||
return errors.New("file does not contain ca-int.pem")
|
||||
altCert := filepath.Join(tmpDir, "test-ca.pem")
|
||||
if _, err = os.Stat(altCert); err == nil {
|
||||
if _, err := exeCmd("mv " + altCert + " " + issuerCert); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(issuerCert); errors.Is(err, fs.ErrNotExist) {
|
||||
return errors.New("file does not contain ca-int.pem")
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(issuerKey); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(issuerKey); errors.Is(err, fs.ErrNotExist) {
|
||||
if ci.IsRoot || wasCSR {
|
||||
issuerKey = ""
|
||||
} else {
|
||||
return errors.New("file does not contain ca-int.key")
|
||||
altKey := filepath.Join(tmpDir, "test-ca.key")
|
||||
if _, err = os.Stat(altKey); err == nil {
|
||||
if _, err := exeCmd("mv " + altKey + " " + issuerKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(issuerKey); errors.Is(err, fs.ErrNotExist) {
|
||||
return errors.New("file does not contain ca-int.key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +664,29 @@ func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string,
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract enddate to determine what the default CRL validity should be
|
||||
if ci.IsRoot {
|
||||
certFile := path + filepath.Base(rootCert)
|
||||
read, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not read '" + certFile + "': " + err.Error())
|
||||
}
|
||||
block, _ := pem.Decode(read)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
fmt.Println(block)
|
||||
return errors.New("failed to decode PEM block containing certificate")
|
||||
}
|
||||
crt, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numDays := time.Until(crt.NotAfter).Hours() / 24
|
||||
if err := updateRootCRLDays("data/openssl.cnf", int(math.Ceil(numDays))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -646,7 +735,7 @@ func (ci *CertificateInfo) Create(path string, certBase string, wasCSR bool) err
|
||||
return fmt.Errorf("unknown CreateType")
|
||||
}
|
||||
|
||||
// This is shared between pfx/zip upload and pem text upload
|
||||
// This is shared between pfx/zip import and pem text upload
|
||||
if ci.CreateType != "generate" {
|
||||
err := ci.Extract(path, certBase, tmpDir, wasCSR)
|
||||
if err != nil {
|
||||
@@ -660,7 +749,7 @@ func (ci *CertificateInfo) Create(path string, certBase string, wasCSR bool) err
|
||||
|
||||
if ci.IsRoot {
|
||||
keyFileExists := true
|
||||
if _, err := os.Stat(path + certBase + ".key"); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + certBase + ".key"); errors.Is(err, fs.ErrNotExist) {
|
||||
keyFileExists = false
|
||||
}
|
||||
if keyFileExists {
|
||||
@@ -687,6 +776,48 @@ func postCreateTasks(path string, certBase string, isRoot bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeRootKey(path, certName, tmpDir, keyData, passphrase string) (bool, string) {
|
||||
tmpKey := filepath.Join(tmpDir, certName+".key")
|
||||
|
||||
if err := os.WriteFile(tmpKey, []byte(keyData), 0600); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
if passphrase != "" {
|
||||
pwd := "pass:" + strings.Replace(passphrase, " ", "\\\\", -1)
|
||||
|
||||
if out, err := exeCmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil {
|
||||
if strings.Contains(string(out), ":bad decrypt:") {
|
||||
return false, "Incorrect password"
|
||||
}
|
||||
|
||||
return false, "Unable to load Root CA key"
|
||||
}
|
||||
|
||||
if _, err := exeCmd("mv " + tmpKey + "-out " + tmpKey); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
modKey, err := exeCmd("openssl rsa -noout -modulus -in " + tmpKey)
|
||||
if err != nil {
|
||||
return false, "Not a private key"
|
||||
}
|
||||
modCert, err := exeCmd("openssl x509 -noout -modulus -in " + path + certName + ".pem")
|
||||
if err != nil {
|
||||
return false, "Unable to load Root CA certificate"
|
||||
}
|
||||
if string(modKey) != string(modCert) {
|
||||
return false, "Key does not match the Root CA certificate"
|
||||
}
|
||||
|
||||
if _, err := exeCmd("mv " + tmpKey + " " + path); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (ci *CertificateInfo) StoreRootKey(path string) bool {
|
||||
if ci.Errors == nil {
|
||||
ci.Errors = make(map[string]string)
|
||||
@@ -704,54 +835,13 @@ func (ci *CertificateInfo) StoreRootKey(path string) bool {
|
||||
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
tmpKey := filepath.Join(tmpDir, "root-ca.key")
|
||||
|
||||
if err := os.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil {
|
||||
ci.Errors["Modal"] = err.Error()
|
||||
return false
|
||||
}
|
||||
|
||||
if ci.Passphrase != "" {
|
||||
pwd := "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1)
|
||||
|
||||
if out, err := exeCmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil {
|
||||
if strings.Contains(string(out), ":bad decrypt:") {
|
||||
ci.Errors["Modal"] = "Incorrect password"
|
||||
return false
|
||||
}
|
||||
|
||||
ci.Errors["Modal"] = "Unable to load Root CA key"
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := exeCmd("mv " + tmpKey + "-out " + tmpKey); err != nil {
|
||||
ci.Errors["Modal"] = err.Error()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
modKey, err := exeCmd("openssl rsa -noout -modulus -in " + tmpKey)
|
||||
if err != nil {
|
||||
ci.Errors["Modal"] = "Not a private key"
|
||||
return false
|
||||
}
|
||||
modCert, err := exeCmd("openssl x509 -noout -modulus -in " + path + "root-ca.pem")
|
||||
if err != nil {
|
||||
ci.Errors["Modal"] = "Unable to load Root CA certificate"
|
||||
return false
|
||||
}
|
||||
if string(modKey) != string(modCert) {
|
||||
ci.Errors["Modal"] = "Key does not match the Root CA certificate"
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := exeCmd("mv " + tmpKey + " " + path); err != nil {
|
||||
ci.Errors["Modal"] = err.Error()
|
||||
certBase := "root-ca"
|
||||
if res, newError := storeRootKey(path, certBase, tmpDir, ci.Key, ci.Passphrase); !res {
|
||||
ci.Errors["Modal"] = newError
|
||||
return false
|
||||
}
|
||||
|
||||
// Create root CRL file now that we have the key
|
||||
certBase := "root-ca"
|
||||
if _, err := exeCmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil {
|
||||
fmt.Printf("StoreRootKey: %s\n", err.Error())
|
||||
return false
|
||||
@@ -808,6 +898,147 @@ func (ci *CertificateInfo) StoreCRL(path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func renewCertificate(certname string, days int, rootname string, rootkeyfile string, passphrase string) error {
|
||||
certFile := locateFile(certname + ".pem")
|
||||
path := filepath.Dir(certFile) + "/"
|
||||
certBase := path + certname
|
||||
keyFile := certBase + ".key"
|
||||
rootCert := ""
|
||||
rootKey := keyFile
|
||||
|
||||
if strings.HasPrefix(certname, "ca-int") || strings.HasPrefix(certname, "test-ca") {
|
||||
rootCert = locateFile(rootname + ".pem")
|
||||
rootKey = locateFile(rootname + ".key")
|
||||
|
||||
// Make sure openssl allows us to add certificates with the same subject
|
||||
attrFile := "data/index.txt.attr"
|
||||
read, err := os.ReadFile(attrFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not read index.txt.attr file: " + err.Error())
|
||||
}
|
||||
re := regexp.MustCompile(`unique_subject = yes`)
|
||||
res := re.ReplaceAll(read, []byte("unique_subject = no"))
|
||||
|
||||
if string(res) != string(read) {
|
||||
if err = os.WriteFile(attrFile, res, 0640); err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not write index.txt.attr file: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "labca")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if _, err := os.Stat(rootKey); errors.Is(err, fs.ErrNotExist) {
|
||||
if rootkeyfile == "" {
|
||||
return errors.New("NO_ROOT_KEY")
|
||||
} else {
|
||||
if res, newError := storeRootKey(path, certname, tmpDir, rootkeyfile, passphrase); !res {
|
||||
return errors.New("NO_ROOT_KEY:" + newError)
|
||||
}
|
||||
defer exeCmd("rm " + rootKey)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := exeCmd("openssl x509 -noout -subject -nameopt utf8 -in " + certFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subject := string(r[8 : len(r)-1])
|
||||
subject = "/" + strings.ReplaceAll(subject, ", ", "/")
|
||||
subject = strings.Replace(subject, " ", "\\\\", -1)
|
||||
|
||||
if rootKey == keyFile {
|
||||
if _, err := exeCmd("openssl req -config data/openssl.cnf -days " + strconv.Itoa(days) + " -new -utf8 -x509 -extensions v3_ca -subj " + subject +
|
||||
" -key " + keyFile + " -out " + certFile + ".tmp"); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
if err := updateRootCRLDays("data/openssl.cnf", days); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
if _, err := exeCmd("openssl ca -config data/openssl.cnf -gencrl -keyfile " + keyFile + " -cert " + certFile + ".tmp -out " + certBase + ".crl"); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
} else {
|
||||
if _, err := exeCmd("openssl req -config data/issuer/openssl.cnf -new -utf8 -subj " + subject + " -key " + keyFile + " -out " + certBase + ".csr"); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
if out, err := exeCmd("openssl ca -config data/openssl.cnf -cert " + rootCert + " -keyfile " + rootKey + " -extensions v3_intermediate_ca -days " +
|
||||
strconv.Itoa(days) + " -md sha384 -notext -batch -in " + certBase + ".csr -out " + certFile + ".tmp"); err != nil {
|
||||
if strings.Contains(string(out), ".key for reading, No such file or directory") {
|
||||
fmt.Println(out)
|
||||
return errors.New("NO_ROOT_KEY")
|
||||
}
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
}
|
||||
if _, err := exeCmd("mv " + certFile + ".tmp " + certFile); err != nil {
|
||||
return reportError(err)
|
||||
}
|
||||
|
||||
// TODO: need to get rid of this!
|
||||
if rootKey == keyFile {
|
||||
if strings.HasPrefix(certname, "test-root") {
|
||||
dataFile := locateFile("root-ca.pem")
|
||||
if _, err := exeCmd("cp " + certFile + " " + dataFile); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
dataKeyFile := strings.TrimSuffix(dataFile, filepath.Ext(dataFile)) + ".key"
|
||||
if _, err := exeCmd("cp " + keyFile + " " + dataKeyFile); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
crlFile := strings.TrimSuffix(dataFile, filepath.Ext(dataFile)) + ".crl"
|
||||
if _, err := exeCmd("cp " + certBase + ".crl " + crlFile); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.HasPrefix(certname, "test-ca") {
|
||||
dataFile := locateFile("ca-int.pem")
|
||||
if _, err := exeCmd("cp " + certFile + " " + dataFile); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
dataKeyFile := strings.TrimSuffix(dataFile, filepath.Ext(dataFile)) + ".key"
|
||||
if _, err := exeCmd("cp " + keyFile + " " + dataKeyFile); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func locateFileIn(path, name string) string {
|
||||
if _, err := os.Stat(path + name); err == nil {
|
||||
return path + name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TODO: sort out the file naming/locations properly to not need this and be future proof!!
|
||||
// Most is found in /opt/boulder/ and some in /opt/boulder/labca/
|
||||
func locateFile(name string) string {
|
||||
|
||||
for _, path := range []string{"", "data/", "data/issuer", "/go/src/labca/data/", "/go/src/labca/data/issuer/", "labca/", "/opt/boulder/", "/opt/boulder/labca/"} {
|
||||
if res := locateFileIn(path, name); res != "" {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("WARNING: could not find '%s'!\n", name)
|
||||
return ""
|
||||
}
|
||||
|
||||
func exeCmd(cmd string) ([]byte, error) {
|
||||
parts := strings.Fields(cmd)
|
||||
for i := 0; i < len(parts); i++ {
|
||||
@@ -821,4 +1052,4 @@ func exeCmd(cmd string) ([]byte, error) {
|
||||
fmt.Print(fmt.Sprint(err) + ": " + string(out))
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
339
gui/chains.go
Normal file
339
gui/chains.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const caaConfFile = "/opt/boulder/labca/config/ca-a.json"
|
||||
const cabConfFile = "/opt/boulder/labca/config/ca-b.json"
|
||||
const wfeConfFile = "/opt/boulder/labca/config/wfe2.json"
|
||||
|
||||
// From boulder: cmd/boulder-wfe2/main.go
|
||||
type WFEConfig struct {
|
||||
WFE struct {
|
||||
Chains [][]string `validate:"required,min=1,dive,min=2,dive,required"`
|
||||
}
|
||||
}
|
||||
|
||||
// From boulder: issuance/issuer.go
|
||||
type IssuerLoc struct {
|
||||
ConfigFile string `validate:"required_without_all=PKCS11 File" json:"configFile"`
|
||||
CertFile string `validate:"required" json:"certFile,omitempty"`
|
||||
NumSessions int `json:"numSessions"`
|
||||
}
|
||||
|
||||
// From boulder: issuance/issuer.go
|
||||
type IssuerConfig struct {
|
||||
UseForRSALeaves bool `json:"useForRSALeaves"`
|
||||
UseForECDSALeaves bool `json:"useForECDSALeaves"`
|
||||
|
||||
IssuerURL string `validate:"required,url" json:"issuerURL,omitempty"`
|
||||
OCSPURL string `validate:"required,url" json:"ocspURL,omitempty"`
|
||||
CRLURL string `validate:"omitempty,url" json:"crlURL,omitempty"`
|
||||
|
||||
Location IssuerLoc `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
// From boulder: cmd/boulder-ca/main.go but deconstructed
|
||||
type Issuance struct {
|
||||
Issuers []IssuerConfig `validate:"min=1,dive" json:"issuers"`
|
||||
}
|
||||
type CA struct {
|
||||
Issuance Issuance `json:"issuance"`
|
||||
}
|
||||
type CAConfig struct {
|
||||
CA CA `json:"ca"`
|
||||
}
|
||||
|
||||
// CertDetails contains info about each certificate for use in the GUI
|
||||
type CertDetails struct {
|
||||
CertFile string
|
||||
BaseName string
|
||||
Subject string
|
||||
IsRoot bool
|
||||
UseForRSA bool
|
||||
UseForECDSA bool
|
||||
NotAfter string
|
||||
Details string
|
||||
}
|
||||
|
||||
type CertChain struct {
|
||||
RootCert CertDetails
|
||||
IssuerCerts []CertDetails
|
||||
}
|
||||
|
||||
func getCertFileDetails(certFile string) (string, error) {
|
||||
var details string
|
||||
|
||||
res, err := exeCmd("openssl x509 -noout -text -nameopt utf8 -in " + certFile)
|
||||
if err != nil {
|
||||
fmt.Println("cannot get details from '" + certFile + "': " + fmt.Sprint(err))
|
||||
return "", err
|
||||
}
|
||||
details = string(res)
|
||||
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func getCertFileNotAFter(certFile string) (string, error) {
|
||||
var notafter string
|
||||
|
||||
res, err := exeCmd("openssl x509 -noout -enddate -nameopt utf8 -in " + certFile)
|
||||
if err != nil {
|
||||
fmt.Println("cannot get enddate from '" + certFile + "': " + fmt.Sprint(err))
|
||||
return "", err
|
||||
}
|
||||
if len(res) <= 9 {
|
||||
fmt.Println("enddate of '" + certFile + "'does not start with 'notAfter='")
|
||||
return "", errors.New("enddate of '" + certFile + "'does not start with 'notAfter='")
|
||||
}
|
||||
notafter = string(res[9 : len(res)-1])
|
||||
return notafter, nil
|
||||
}
|
||||
|
||||
func getCertFileSubject(certFile string) (string, error) {
|
||||
var subject string
|
||||
|
||||
res, err := exeCmd("openssl x509 -noout -subject -nameopt utf8 -in " + certFile)
|
||||
if err != nil {
|
||||
fmt.Println("cannot get subject from '" + certFile + "': " + fmt.Sprint(err))
|
||||
return "", err
|
||||
}
|
||||
if len(res) <= 8 {
|
||||
fmt.Println("subject of '" + certFile + "'does not start with 'subject='")
|
||||
return "", errors.New("subject of '" + certFile + "'does not start with 'subject='")
|
||||
}
|
||||
subject = string(res[8 : len(res)-1])
|
||||
return subject, nil
|
||||
}
|
||||
|
||||
func getRawCAChains() []IssuerConfig {
|
||||
caConf, err := os.Open(caaConfFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
defer caConf.Close()
|
||||
|
||||
byteValue, _ := io.ReadAll(caConf)
|
||||
|
||||
var result CAConfig
|
||||
json.Unmarshal([]byte(byteValue), &result)
|
||||
|
||||
return result.CA.Issuance.Issuers
|
||||
}
|
||||
|
||||
func enhanceChains(chains []CertChain) []CertChain {
|
||||
rawChains := getRawCAChains()
|
||||
|
||||
for i := 0; i < len(rawChains); i++ {
|
||||
for k := 0; k < len(chains); k++ {
|
||||
for n := 0; n < len(chains[k].IssuerCerts); n++ {
|
||||
if chains[k].IssuerCerts[n].CertFile == rawChains[i].Location.CertFile {
|
||||
chains[k].IssuerCerts[n].UseForRSA = rawChains[i].UseForRSALeaves
|
||||
chains[k].IssuerCerts[n].UseForECDSA = rawChains[i].UseForECDSALeaves
|
||||
certFile := locateFile(rawChains[i].Location.CertFile)
|
||||
if d, err := getCertFileDetails(certFile); err == nil {
|
||||
chains[k].IssuerCerts[n].Details = d
|
||||
}
|
||||
if na, err := getCertFileNotAFter(certFile); err == nil {
|
||||
chains[k].IssuerCerts[n].NotAfter = na
|
||||
}
|
||||
if s, err := getCertFileSubject(certFile); err == nil {
|
||||
chains[k].IssuerCerts[n].Subject = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chains[k].RootCert.Subject == "" {
|
||||
certFile := locateFile(chains[k].RootCert.CertFile)
|
||||
if d, err := getCertFileDetails(certFile); err == nil {
|
||||
chains[k].RootCert.Details = d
|
||||
}
|
||||
if na, err := getCertFileNotAFter(certFile); err == nil {
|
||||
chains[k].RootCert.NotAfter = na
|
||||
}
|
||||
if s, err := getCertFileSubject(certFile); err == nil {
|
||||
chains[k].RootCert.Subject = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chains
|
||||
}
|
||||
|
||||
func getRawWFEChains() [][]string {
|
||||
wfeConf, err := os.Open(wfeConfFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
defer wfeConf.Close()
|
||||
|
||||
byteValue, _ := io.ReadAll(wfeConf)
|
||||
|
||||
var result WFEConfig
|
||||
json.Unmarshal([]byte(byteValue), &result)
|
||||
|
||||
return result.WFE.Chains
|
||||
}
|
||||
|
||||
func getChains() []CertChain {
|
||||
var chains []CertChain
|
||||
|
||||
rawChains := getRawWFEChains()
|
||||
|
||||
for i := 0; i < len(rawChains); i++ {
|
||||
chain := rawChains[i]
|
||||
issuer := chain[0]
|
||||
root := chain[1]
|
||||
|
||||
var certChain CertChain
|
||||
cIdx := -1
|
||||
for k := 0; k < len(chains); k++ {
|
||||
if chains[k].RootCert.CertFile == root {
|
||||
certChain = chains[k]
|
||||
cIdx = k
|
||||
}
|
||||
}
|
||||
|
||||
if cIdx < 0 {
|
||||
base := filepath.Base(root)
|
||||
base = strings.TrimSuffix(base, filepath.Ext(base))
|
||||
certChain = CertChain{RootCert: CertDetails{
|
||||
CertFile: root,
|
||||
BaseName: base,
|
||||
IsRoot: true,
|
||||
}}
|
||||
chains = append(chains, certChain)
|
||||
cIdx = len(chains) - 1
|
||||
}
|
||||
|
||||
base := filepath.Base(issuer)
|
||||
base = strings.TrimSuffix(base, filepath.Ext(base))
|
||||
certChain.IssuerCerts = append(certChain.IssuerCerts, CertDetails{
|
||||
CertFile: issuer,
|
||||
BaseName: base,
|
||||
IsRoot: false,
|
||||
})
|
||||
|
||||
chains[cIdx] = certChain
|
||||
}
|
||||
|
||||
chains = enhanceChains(chains)
|
||||
|
||||
return chains
|
||||
}
|
||||
|
||||
func setUseForLeavesFile(filename, forRSA, forECDSA string) error {
|
||||
caConf, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not open config file: " + err.Error())
|
||||
}
|
||||
defer caConf.Close()
|
||||
|
||||
byteValue, _ := io.ReadAll(caConf)
|
||||
|
||||
var result CAConfig
|
||||
if err = json.Unmarshal([]byte(byteValue), &result); err != nil {
|
||||
return errors.New("could not parse config file: " + err.Error())
|
||||
}
|
||||
|
||||
// Make sure that the named certificate(s) exist
|
||||
foundRSA := false
|
||||
foundECDSA := false
|
||||
for i := 0; i < len(result.CA.Issuance.Issuers); i++ {
|
||||
if strings.Contains(result.CA.Issuance.Issuers[i].Location.CertFile, forRSA) {
|
||||
foundRSA = true
|
||||
}
|
||||
if strings.Contains(result.CA.Issuance.Issuers[i].Location.CertFile, forECDSA) {
|
||||
foundECDSA = true
|
||||
}
|
||||
}
|
||||
if !foundRSA {
|
||||
return errors.New("certificate '" + forRSA + "' not found in ca file")
|
||||
}
|
||||
if !foundECDSA {
|
||||
return errors.New("certificate '" + forECDSA + "' not found in ca file")
|
||||
}
|
||||
|
||||
// Now set the flags for the named certificate(s)
|
||||
for i := 0; i < len(result.CA.Issuance.Issuers); i++ {
|
||||
if forRSA != "" {
|
||||
result.CA.Issuance.Issuers[i].UseForRSALeaves = strings.Contains(result.CA.Issuance.Issuers[i].Location.CertFile, forRSA)
|
||||
}
|
||||
if forECDSA != "" {
|
||||
result.CA.Issuance.Issuers[i].UseForECDSALeaves = strings.Contains(result.CA.Issuance.Issuers[i].Location.CertFile, forECDSA)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the modified data back to file, using regex magic to replace only the issuers list...
|
||||
if jsonString, err := json.MarshalIndent(result, "", "\t"); err == nil {
|
||||
re := regexp.MustCompile(`(?s).*"issuers": \[(.*?)\s*\].*`)
|
||||
iss := re.ReplaceAll(jsonString, []byte("$1"))
|
||||
|
||||
read, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not read config file: " + err.Error())
|
||||
}
|
||||
re = regexp.MustCompile(`(?s)(\s*"issuers": \[).*?(\s*\])`)
|
||||
res := re.ReplaceAll(read, []byte("$1"+string(iss)+"$2"))
|
||||
|
||||
if err = os.WriteFile(filename, res, 0640); err != nil {
|
||||
fmt.Println(err)
|
||||
return errors.New("could not write config file: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
return errors.New("could not convert json data: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUseForLeaves(forRSA, forECDSA string) error {
|
||||
if err := exec.Command("cp", "-f", caaConfFile, caaConfFile+"_BAK").Run(); err != nil {
|
||||
return errors.New("could not create ca-a backup file: " + err.Error())
|
||||
}
|
||||
if err := exec.Command("cp", "-f", cabConfFile, cabConfFile+"_BAK").Run(); err != nil {
|
||||
return errors.New("could not create ca-b backup file: " + err.Error())
|
||||
}
|
||||
|
||||
if err := setUseForLeavesFile(caaConfFile, forRSA, forECDSA); err != nil {
|
||||
exec.Command("mv", caaConfFile+"_BAK", caaConfFile).Run()
|
||||
exec.Command("mv", cabConfFile+"_BAK", cabConfFile).Run()
|
||||
return err
|
||||
}
|
||||
if err := setUseForLeavesFile(cabConfFile, forRSA, forECDSA); err != nil {
|
||||
exec.Command("mv", caaConfFile+"_BAK", caaConfFile).Run()
|
||||
exec.Command("mv", cabConfFile+"_BAK", cabConfFile).Run()
|
||||
return err
|
||||
}
|
||||
|
||||
exec.Command("rm", caaConfFile+"_BAK").Run()
|
||||
exec.Command("rm", cabConfFile+"_BAK").Run()
|
||||
|
||||
if forRSA != "" {
|
||||
viper.Set("certs.issuerRSA", forRSA)
|
||||
}
|
||||
if forECDSA != "" {
|
||||
viper.Set("certs.issuerECDSA", forECDSA)
|
||||
}
|
||||
if forRSA != "" || forECDSA != "" {
|
||||
viper.WriteConfig()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
282
gui/main.go
282
gui/main.go
@@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -497,6 +497,8 @@ func _sendCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) {
|
||||
|
||||
out, err := exec.Command(head, parts...).Output()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println(out)
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -963,49 +965,31 @@ func _emailSendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func _exportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
basename := "certificates"
|
||||
if r.Form.Get("root") != "true" {
|
||||
basename = "issuer"
|
||||
}
|
||||
if r.Form.Get("issuer") != "true" {
|
||||
basename = "root"
|
||||
}
|
||||
certname := r.Form.Get("certname")
|
||||
|
||||
certFile := locateFile(certname + ".pem")
|
||||
keyFile := strings.TrimSuffix(certFile, filepath.Ext(certFile)) + ".key"
|
||||
|
||||
if r.Form.Get("type") == "pfx" {
|
||||
w.Header().Set("Content-Type", "application/x-pkcs12")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".pfx")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca_"+certname+".pfx")
|
||||
|
||||
var certBase string
|
||||
if basename == "root" {
|
||||
certBase = "data/root-ca"
|
||||
} else {
|
||||
certBase = "data/issuer/ca-int"
|
||||
}
|
||||
|
||||
cmd := "openssl pkcs12 -export -inkey " + certBase + ".key -in " + certBase + ".pem -passout pass:" + r.Form.Get("export-pwd")
|
||||
cmd := "openssl pkcs12 -export -inkey " + keyFile + " -in " + certFile + " -passout pass:" + r.Form.Get("export-pwd")
|
||||
|
||||
_sendCmdOutput(w, r, cmd)
|
||||
}
|
||||
|
||||
if r.Form.Get("type") == "zip" {
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".zip")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca_"+certname+".zip")
|
||||
|
||||
cmd := "zip -j -P " + r.Form.Get("export-pwd") + " - "
|
||||
var certBase string
|
||||
if r.Form.Get("root") == "true" {
|
||||
certBase = "data/root-ca"
|
||||
cmd = cmd + certBase + ".key " + certBase + ".pem "
|
||||
}
|
||||
if r.Form.Get("issuer") == "true" {
|
||||
certBase = "data/issuer/ca-int"
|
||||
cmd = cmd + certBase + ".key " + certBase + ".pem "
|
||||
}
|
||||
cmd := "zip -j -P " + r.Form.Get("export-pwd") + " - " + keyFile + " " + certFile
|
||||
|
||||
_sendCmdOutput(w, r, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func _doCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) string {
|
||||
parts := strings.Fields(cmd)
|
||||
for i := 0; i < len(parts); i++ {
|
||||
@@ -1022,6 +1006,7 @@ func _doCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) string {
|
||||
|
||||
return string(out)
|
||||
}
|
||||
*/
|
||||
|
||||
func _encrypt(plaintext []byte) (string, error) {
|
||||
key := []byte(viper.GetString("keys.enc"))
|
||||
@@ -1126,7 +1111,7 @@ func generateCRLHandler(w http.ResponseWriter, r *http.Request, isRoot bool) {
|
||||
path := "data/"
|
||||
certBase := "root-ca"
|
||||
keyFileExists := true
|
||||
if _, err := os.Stat(path + certBase + ".key"); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + certBase + ".key"); errors.Is(err, fs.ErrNotExist) {
|
||||
keyFileExists = false
|
||||
}
|
||||
if keyFileExists {
|
||||
@@ -1155,13 +1140,13 @@ func generateCRLHandler(w http.ResponseWriter, r *http.Request, isRoot bool) {
|
||||
}
|
||||
// Remove the Root Key if we want to keep it offline
|
||||
if viper.GetBool("keep_root_offline") {
|
||||
if _, err := os.Stat(path + certBase + ".key"); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + certBase + ".key"); !errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Println("Removing private Root key from the system...")
|
||||
if _, err := exeCmd("rm " + path + certBase + ".key"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path + certBase + ".key.der"); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + certBase + ".key.der"); !errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := exeCmd("rm " + path + certBase + ".key.der"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key (DER format): %v", err)
|
||||
}
|
||||
@@ -1205,6 +1190,63 @@ func uploadCRLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func updateLeaveIssuersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
res := struct {
|
||||
Success bool
|
||||
Error string
|
||||
}{Success: true}
|
||||
|
||||
if err := setUseForLeaves(r.Form.Get("rsa"), r.Form.Get("ecdsa")); err != nil {
|
||||
res.Success = false
|
||||
res.Error = err.Error()
|
||||
} else {
|
||||
defer func() {
|
||||
if !_hostCommand(w, r, "boulder-restart") {
|
||||
log.Printf("updateLeaveIssuersHandler: error restarting boulder: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func renewCertHandler(w http.ResponseWriter, r *http.Request) {
|
||||
res := struct {
|
||||
Success bool
|
||||
Error string
|
||||
}{Success: true}
|
||||
|
||||
days, err := strconv.Atoi(r.Form.Get("days"))
|
||||
if err != nil {
|
||||
fmt.Printf("'%v' is not a number", r.Form.Get("days"))
|
||||
errorHandler(w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := renewCertificate(r.Form.Get("certname"), days, r.Form.Get("rootname"), r.Form.Get("root_key"), r.Form.Get("passphrase")); err != nil {
|
||||
res.Success = false
|
||||
res.Error = err.Error()
|
||||
}
|
||||
|
||||
ex, _ := os.Executable()
|
||||
exePath := filepath.Dir(ex)
|
||||
path, _ := filepath.Abs(exePath + "/..")
|
||||
if _, err := exeCmd(path + "/apply"); err != nil {
|
||||
fmt.Println(err)
|
||||
res.Success = false
|
||||
res.Error = "Could not apply: " + err.Error()
|
||||
}
|
||||
|
||||
if !_hostCommand(w, r, "boulder-restart") {
|
||||
res.Success = false
|
||||
res.Error = "Error restarting Boulder (ACME)"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func _managePostDispatch(w http.ResponseWriter, r *http.Request, action string) bool {
|
||||
if action == "backup-restore" || action == "backup-delete" || action == "backup-now" || action == "backup-upload" {
|
||||
_backupHandler(w, r)
|
||||
@@ -1266,6 +1308,16 @@ func _managePostDispatch(w http.ResponseWriter, r *http.Request, action string)
|
||||
return true
|
||||
}
|
||||
|
||||
if action == "update-leave-issuers" {
|
||||
updateLeaveIssuersHandler(w, r)
|
||||
return true
|
||||
}
|
||||
|
||||
if action == "renew-cert" {
|
||||
renewCertHandler(w, r)
|
||||
return true
|
||||
}
|
||||
|
||||
if action == "svc-restart" {
|
||||
if _, err := exeCmd("./restart_control"); err != nil {
|
||||
log.Printf("_managePostDispatch: error restarting control container: %v", err)
|
||||
@@ -1319,6 +1371,8 @@ func _managePost(w http.ResponseWriter, r *http.Request) {
|
||||
"upload-root-crl",
|
||||
"gen-root-crl",
|
||||
"gen-issuer-crl",
|
||||
"update-leave-issuers",
|
||||
"renew-cert",
|
||||
} {
|
||||
if a == action {
|
||||
actionKnown = true
|
||||
@@ -1509,8 +1563,8 @@ func _manageGet(w http.ResponseWriter, r *http.Request) {
|
||||
backupFiles = backupFiles[:len(backupFiles)-1]
|
||||
manageData["BackupFiles"] = backupFiles
|
||||
|
||||
manageData["RootDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -nameopt utf8 -in data/root-ca.pem")
|
||||
manageData["IssuerDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -nameopt utf8 -in data/issuer/ca-int.pem")
|
||||
chains := getChains()
|
||||
manageData["CertificateChains"] = chains
|
||||
|
||||
if viper.Get("crl_interval") == nil || viper.GetString("crl_interval") == "" {
|
||||
manageData["CRLInterval"] = "24h"
|
||||
@@ -1575,6 +1629,54 @@ func manageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func manageNewRootHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !viper.GetBool("config.complete") {
|
||||
http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: dynamically determine next filename (root-ca-2, root-ca-3, etc.)
|
||||
|
||||
if !_certCreate(w, r, "root-ca-3", true) {
|
||||
// Cleanup the cert (if it even exists) so we will retry on the next run
|
||||
if _, err := os.Stat("data/root-ca-3.pem"); !errors.Is(err, fs.ErrNotExist) {
|
||||
exeCmd("mv data/root-ca-3.pem data/root-ca-3.pem_TMP")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: actually add the newly created key to the relevant config files (ca-a, ca-b, wfe2, possibly others)
|
||||
|
||||
// TODO: reload boulder!
|
||||
|
||||
http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/manage#certs", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func manageNewIssuerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !viper.GetBool("config.complete") {
|
||||
http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: dynamically determine next filename (ca-int-2, ca-int-3, etc.)
|
||||
|
||||
// Is revertroot at all relevant in this scenario?
|
||||
|
||||
if !_certCreate(w, r, "ca-int-3", false) {
|
||||
// Cleanup the cert (if it even exists) so we will retry on the next run
|
||||
os.Remove("data/issuer/ca-int-3.pem")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: actually add the newly created key to the relevant config files (ca-a, ca-b, wfe2, possibly others)
|
||||
|
||||
// TODO: reload boulder!
|
||||
|
||||
http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/manage#certs", http.StatusSeeOther)
|
||||
}
|
||||
*/
|
||||
|
||||
func logsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
logType := vars["type"]
|
||||
@@ -1749,9 +1851,11 @@ func _buildCI(r *http.Request, session *sessions.Session, isRoot bool) *Certific
|
||||
CreateType: "generate",
|
||||
CommonName: "Root CA",
|
||||
RequestBase: r.Header.Get("X-Request-Base"),
|
||||
NumDays: 3652, // 10 years
|
||||
}
|
||||
if !isRoot {
|
||||
ci.CommonName = "CA"
|
||||
ci.NumDays = 1826 // 5 years
|
||||
}
|
||||
ci.Initialize()
|
||||
|
||||
@@ -1834,7 +1938,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
// Undo what setupHandler did when showing the public key...
|
||||
_, errPem := os.Stat("data/root-ca.pem")
|
||||
_, errTmp := os.Stat("data/root-ca.pem_TMP")
|
||||
if os.IsNotExist(errPem) && !os.IsNotExist(errTmp) {
|
||||
if errors.Is(errPem, fs.ErrNotExist) && !errors.Is(errTmp, fs.ErrNotExist) {
|
||||
exeCmd("mv data/root-ca.pem_TMP data/root-ca.pem")
|
||||
}
|
||||
|
||||
@@ -1848,11 +1952,54 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
path = path + "issuer/"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path + certBase + ".pem"); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + certBase + ".pem"); errors.Is(err, fs.ErrNotExist) {
|
||||
session, _ := sessionStore.Get(r, "labca")
|
||||
|
||||
if r.Method == "GET" {
|
||||
ci := _buildCI(r, session, isRoot)
|
||||
if isRoot && (certBase == "root-ca" || certBase == "test-root") {
|
||||
ci.IsFirst = true
|
||||
} else if !isRoot && (certBase == "ca-int" || certBase == "test-ca") {
|
||||
ci.IsFirst = true
|
||||
}
|
||||
|
||||
if len(r.URL.Query()["root"]) > 0 {
|
||||
certFile := locateFile(r.URL.Query()["root"][0] + ".pem")
|
||||
ci.RootEnddate, err = getCertFileNotAFter(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
|
||||
ci.RootSubject, err = getCertFileSubject(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
subjectMap := parseSubjectDn(ci.RootSubject)
|
||||
if val, ok := subjectMap["C"]; ok {
|
||||
ci.Country = val
|
||||
}
|
||||
if val, ok := subjectMap["O"]; ok {
|
||||
ci.Organization = val
|
||||
}
|
||||
} else if !isRoot {
|
||||
certFile := locateFile("root-ca.pem")
|
||||
ci.RootEnddate, err = getCertFileNotAFter(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
ci.RootSubject, err = getCertFileSubject(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
|
||||
return false
|
||||
@@ -1877,6 +2024,19 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
ci.OrgUnit = r.Form.Get("ou")
|
||||
ci.CommonName = r.Form.Get("cn")
|
||||
|
||||
ci.RootEnddate = r.Form.Get("root-enddate")
|
||||
ci.RootSubject = r.Form.Get("root-subject")
|
||||
if r.Form.Get("numdays") != "" {
|
||||
ci.NumDays, err = strconv.Atoi(r.Form.Get("numdays"))
|
||||
if err != nil {
|
||||
if ci.IsRoot {
|
||||
ci.NumDays = 3652
|
||||
} else {
|
||||
ci.NumDays = 1826
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ci.CreateType == "import" {
|
||||
file, handler, err := r.FormFile("import")
|
||||
if err != nil {
|
||||
@@ -1934,7 +2094,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
return false
|
||||
}
|
||||
defer csr.Close()
|
||||
b, err := ioutil.ReadAll(csr)
|
||||
b, _ := io.ReadAll(csr)
|
||||
|
||||
render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "CSR": string(b), "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
|
||||
return false
|
||||
@@ -1973,7 +2133,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
return false
|
||||
}
|
||||
defer csr.Close()
|
||||
b, err := ioutil.ReadAll(csr)
|
||||
b, _ := io.ReadAll(csr)
|
||||
|
||||
session.Values["csr"] = true
|
||||
if err = session.Save(r, w); err != nil {
|
||||
@@ -2002,13 +2162,13 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
|
||||
if viper.GetBool("keep_root_offline") {
|
||||
if _, err := os.Stat(path + "../root-ca.key"); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + "../root-ca.key"); !errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Println("Removing private Root key from the system...")
|
||||
if _, err := exeCmd("rm " + path + "../root-ca.key"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path + "../root-ca.key.der"); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path + "../root-ca.key.der"); !errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := exeCmd("rm " + path + "../root-ca.key.der"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key (DER format): %v", err)
|
||||
}
|
||||
@@ -2040,7 +2200,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
b, err := ioutil.ReadAll(key)
|
||||
b, _ := io.ReadAll(key)
|
||||
|
||||
render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "RootKey": string(b), "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
|
||||
return false
|
||||
@@ -2160,9 +2320,9 @@ func _helptext(stage string) template.HTML {
|
||||
if stage == "register" {
|
||||
return template.HTML(fmt.Sprint("<p class=\"form-register\">You need to create an admin account for\n",
|
||||
"managing this instance of LabCA. There can only be one admin account, but you can configure all\n",
|
||||
"its attributes once the initial setup has completed.<br><br>Instead, you can also\n",
|
||||
"its attributes once the initial setup has completed.<br><br><b>Instead, you can also\n",
|
||||
"<a href=\"#\" onclick=\"false\" class=\"toggle-restore\">restore from a backup file</a> of a\n",
|
||||
"previous LabCA installation.</p>\n",
|
||||
"previous LabCA installation.</b></p>\n",
|
||||
"<p class=\"form-restore\">If you have a backup file from a previous LabCA installation and want to\n",
|
||||
"restore this instance with the exact same configuration, use that backup file here.\n",
|
||||
"<br><br>Otherwise you should follow the <a href=\"#\" onclick=\"false\"\n",
|
||||
@@ -2544,7 +2704,7 @@ func setupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 3. Setup root CA certificate
|
||||
if !_certCreate(w, r, "root-ca", true) {
|
||||
// Cleanup the cert (if it even exists) so we will retry on the next run
|
||||
if _, err := os.Stat("data/root-ca.pem"); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat("data/root-ca.pem"); !errors.Is(err, fs.ErrNotExist) {
|
||||
exeCmd("mv data/root-ca.pem data/root-ca.pem_TMP")
|
||||
}
|
||||
return
|
||||
@@ -3147,11 +3307,14 @@ func init() {
|
||||
|
||||
if *configFile == "" {
|
||||
viper.SetConfigName("config")
|
||||
viper.AddConfigPath("data")
|
||||
configPath = "./data"
|
||||
ex, _ := os.Executable()
|
||||
exePath := filepath.Dir(ex)
|
||||
path, _ := filepath.Abs(exePath + "/..")
|
||||
configPath = path + "/data"
|
||||
viper.AddConfigPath(configPath)
|
||||
} else {
|
||||
_, err := os.Stat(*configFile)
|
||||
if os.IsNotExist(err) {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
viper.WriteConfigAs(*configFile)
|
||||
}
|
||||
|
||||
@@ -3260,6 +3423,27 @@ func init() {
|
||||
listenAddress = fmt.Sprintf("%s:%d", a, p)
|
||||
|
||||
updateAvailable = false
|
||||
|
||||
/*
|
||||
// TODO: Still needs to be done for this!
|
||||
// Store boulder chains if we don't have them already
|
||||
doWrite := false
|
||||
if viper.GetString("certs.ca") == "" {
|
||||
caChains := getRawCAChains()
|
||||
viper.Set("certs.ca", caChains)
|
||||
doWrite = true
|
||||
}
|
||||
if viper.GetString("certs.wfe") == "" {
|
||||
chains := getRawWFEChains()
|
||||
viper.Set("certs.wfe", chains)
|
||||
doWrite = true
|
||||
}
|
||||
if doWrite {
|
||||
viper.WriteConfig()
|
||||
}
|
||||
|
||||
// TODO: also apply from here if different?? How exaclty is a code upgrade delaing with this??
|
||||
*/
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -3286,6 +3470,8 @@ func main() {
|
||||
r.HandleFunc("/stats", statsHandler).Methods("GET")
|
||||
r.HandleFunc("/about", aboutHandler).Methods("GET")
|
||||
r.HandleFunc("/manage", manageHandler).Methods("GET", "POST")
|
||||
// r.HandleFunc("/manage/newissuer", manageNewIssuerHandler).Methods("GET", "POST")
|
||||
// r.HandleFunc("/manage/newroot", manageNewRootHandler).Methods("GET", "POST")
|
||||
r.HandleFunc("/final", finalHandler).Methods("GET")
|
||||
r.HandleFunc("/error", showErrorHandler).Methods("GET")
|
||||
r.HandleFunc("/login", loginHandler).Methods("GET", "POST")
|
||||
@@ -3329,9 +3515,11 @@ func main() {
|
||||
r.PathPrefix("/accounts/static/").Handler(http.StripPrefix("/accounts/static/", sfs))
|
||||
r.PathPrefix("/authz/static/").Handler(http.StripPrefix("/authz/static/", sfs))
|
||||
r.PathPrefix("/challenges/static/").Handler(http.StripPrefix("/challenges/static/", sfs))
|
||||
r.PathPrefix("/certs/static/").Handler(http.StripPrefix("/certs/static/", sfs))
|
||||
r.PathPrefix("/certificates/static/").Handler(http.StripPrefix("/certificates/static/", sfs))
|
||||
r.PathPrefix("/orders/static/").Handler(http.StripPrefix("/orders/static/", sfs))
|
||||
r.PathPrefix("/logs/static/").Handler(http.StripPrefix("/logs/static/", sfs))
|
||||
r.PathPrefix("/manage/static/").Handler(http.StripPrefix("/manage/static/", sfs))
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", sfs))
|
||||
}
|
||||
}
|
||||
@@ -3350,4 +3538,4 @@ func main() {
|
||||
} else {
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y iproute2 zip
|
||||
apt-get install -y iproute2 zip unzip
|
||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common gnupg
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
[ ! -e /etc/apt/keyrings/docker.gpg ] || mv /etc/apt/keyrings/docker.gpg /etc/apt/keyrings/docker.gpg_PREV
|
||||
|
||||
@@ -124,6 +124,14 @@ td.pad-low-top {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
.btn-new-issuer {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
.btn-6em {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.btn-right {
|
||||
float: right;
|
||||
}
|
||||
@@ -132,6 +140,10 @@ td.pad-low-top {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.bd-modal-lg .modal-dialog {
|
||||
display: table;
|
||||
position: relative;
|
||||
@@ -146,11 +158,11 @@ td.pad-low-top {
|
||||
|
||||
.modal-content {
|
||||
padding: 6px;
|
||||
width: 614px;
|
||||
width: 674px;
|
||||
}
|
||||
|
||||
.modal-content > textarea {
|
||||
width: 600px;
|
||||
width: 660px;
|
||||
height: 550px;
|
||||
}
|
||||
|
||||
@@ -197,3 +209,16 @@ pre.json {
|
||||
.hide-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#modal-spinner {
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
button.close {
|
||||
margin-top: -40px;
|
||||
}
|
||||
|
||||
.form-small-inline {
|
||||
display: inline-block;
|
||||
width: 5em !important;
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
<div class="col-md-6 col-sm-12">
|
||||
{{with .CertificateInfo}}
|
||||
<h3>{{ if .IsRoot }}<b>Root</b>{{ else }}<b>Issuer</b> (2nd level){{ end }} Certificate</h3>
|
||||
{{ if not .IsRoot }}
|
||||
Root Subject: <span id="root-subject-display">{{ .RootSubject }}</span><br><br>
|
||||
{{ end }}
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="{{ if eq .CreateType "generate" }}active{{ end }}">
|
||||
@@ -19,13 +22,17 @@
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade {{ if eq .CreateType "generate" }}active in{{ end }}" id="generate">
|
||||
<br/>
|
||||
<form role="form" class="form-cert" action="{{ .RequestBase }}/setup" enctype="multipart/form-data" method="POST">
|
||||
<form role="form" class="form-cert" enctype="multipart/form-data" method="POST">
|
||||
<input type="hidden" name="cert" value="{{ if .IsRoot }}root{{ else }}issuer{{ end }}">
|
||||
<input type="hidden" name="createtype" value="generate">
|
||||
<input type="hidden" name="keep-root-online" id="keep-root-online">
|
||||
<input type="hidden" name="ack-rootkey" id="ack-rootkey">
|
||||
<input type="hidden" name="rootkey" id="rootkey">
|
||||
<input type="hidden" name="rootpassphrase" id="rootpassphrase">
|
||||
{{ if not .IsRoot }}
|
||||
<input type="hidden" name="root-subject" id="root-subject" value="{{ .RootSubject }}">
|
||||
<input type="hidden" name="root-enddate" id="root-enddate" value="{{ .RootEnddate }}">
|
||||
{{ end }}
|
||||
<div class="form-group">
|
||||
<label for="keytype">Key type and size:</label>
|
||||
<select class="form-control" id="keytype" name="keytype" required autocomplete="off">
|
||||
@@ -66,6 +73,12 @@
|
||||
<span class="error">{{ . }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="numdays">Validity (days):</label>
|
||||
<input class="form-control" type="text" id="numdays" name="numdays" value="{{ .NumDays }}" required>
|
||||
End date: <span id="validity-enddate"></span><br>
|
||||
<span class="error hidden" id="numdays-error">{{ .Errors.numdays }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ with .Errors.Generate }}
|
||||
<span class="error">{{ . }}</span><br/>
|
||||
@@ -74,7 +87,7 @@
|
||||
{{ if and (not .IsRoot) (not .IsRootGenerated) }}
|
||||
<input class="btn btn-default" type="submit" value="Get CSR" name="getcsr">
|
||||
{{ end }}
|
||||
{{ if not .IsRoot }}
|
||||
{{ if and (not .IsRoot) .IsFirst }}
|
||||
<input class="btn btn-danger btn-right" type="submit" value="Revert Root" name="revertroot" id="revertroot">
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -83,7 +96,7 @@
|
||||
|
||||
<div class="tab-pane fade {{ if eq .CreateType "import" }}active in{{ end }}" id="import">
|
||||
<br/>
|
||||
<form role="form" class="form-cert" action="{{ .RequestBase }}/setup" enctype="multipart/form-data" method="POST">
|
||||
<form role="form" class="form-cert" enctype="multipart/form-data" method="POST">
|
||||
<input type="hidden" name="cert" value="{{ if .IsRoot }}root{{ else }}issuer{{ end }}">
|
||||
<input type="hidden" name="createtype" value="import">
|
||||
<p>
|
||||
@@ -111,7 +124,7 @@
|
||||
|
||||
<div class="tab-pane fade {{ if eq .CreateType "upload" }}active in{{ end }}" id="upload">
|
||||
<br/>
|
||||
<form role="form" class="form-cert" action="{{ .RequestBase }}/setup" enctype="multipart/form-data" method="POST">
|
||||
<form role="form" class="form-cert" enctype="multipart/form-data" method="POST">
|
||||
<input type="hidden" name="cert" value="{{ if .IsRoot }}root{{ else }}issuer{{ end }}">
|
||||
<input type="hidden" name="createtype" value="upload">
|
||||
<div class="form-group">
|
||||
@@ -151,7 +164,7 @@
|
||||
{{ with .Errors.Upload }}
|
||||
<span class="error">{{ . }}</span><br/>
|
||||
{{ end }}
|
||||
<input class="btn btn-default" type="submit" value="Upload">
|
||||
<input class="btn btn-default" type="submit" id="cert-submit" value="Upload">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -268,11 +281,76 @@
|
||||
});
|
||||
|
||||
{{ if not .CertificateInfo.IsRoot }}
|
||||
|
||||
$(".form-cert").submit(function() {
|
||||
$("#processing").removeClass("hidden");
|
||||
});
|
||||
{{end}}
|
||||
|
||||
function updateEndDate() {
|
||||
// Determine new end date
|
||||
x = new Date();
|
||||
x.setDate(x.getDate() + parseInt($('#numdays').val()))
|
||||
$('#validity-enddate').text(x.toUTCString());
|
||||
|
||||
try {
|
||||
if (($("#root-enddate").val() != undefined) && ($("#root-enddate").val().length > 0)) {
|
||||
r = new Date($("#root-enddate").val());
|
||||
if (x > r) {
|
||||
$("#numdays-error").text("Validity cannot exceed root certificate end date");
|
||||
$("#numdays-error").removeClass('hidden').show();
|
||||
} else {
|
||||
$("#numdays-error").text("");
|
||||
if (!$("#numdays-error").hasClass('hidden')) {
|
||||
$("#numdays-error").hide().addClass('hidden');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$("#numdays-error").text("");
|
||||
if (!$("#numdays-error").hasClass('hidden')) {
|
||||
$("#numdays-error").hide().addClass('hidden');
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
$("#cert-submit").prop('disabled', !$("#numdays-error").hasClass('hidden'));
|
||||
}
|
||||
|
||||
$.fn.inputFilter = function(callback, errMsg) {
|
||||
return this.on("input keydown keyup mousedown mouseup select contextmenu drop focusout", function(e) {
|
||||
if (callback(this.value)) {
|
||||
// Accepted value
|
||||
if (["keydown","mousedown","focusout"].indexOf(e.type) >= 0){
|
||||
$("#numdays-error").text("");
|
||||
if (!$("#numdays-error").hasClass('hidden')) {
|
||||
$("#numdays-error").hide().addClass('hidden');
|
||||
}
|
||||
}
|
||||
this.oldValue = this.value;
|
||||
this.oldSelectionStart = this.selectionStart;
|
||||
this.oldSelectionEnd = this.selectionEnd;
|
||||
updateEndDate();
|
||||
} else if (this.hasOwnProperty("oldValue")) {
|
||||
// Rejected value - restore the previous one
|
||||
$("#numdays-error").text(errMsg);
|
||||
if ($("#numdays-error").hasClass('hidden')) {
|
||||
$("#numdays-error").removeClass('hidden').show();
|
||||
}
|
||||
this.value = this.oldValue;
|
||||
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
|
||||
} else {
|
||||
// Rejected value - nothing to restore
|
||||
this.value = "";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$("#numdays").inputFilter(function(value) {
|
||||
return /^\d*$/.test(value); // Allow digits only, using a RegExp
|
||||
},"Only digits are allowed");
|
||||
|
||||
updateEndDate();
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -158,43 +158,65 @@
|
||||
|
||||
<div class="tab-pane fade" id="certs">
|
||||
<br/>
|
||||
<form role="form">
|
||||
<div class="form-group">
|
||||
<p><b>Download certificates:</b><br/>
|
||||
<a href="/certs/root-ca.der">root-ca.der</a> | <a href="/certs/root-ca.pem">root-ca.pem</a><br/>
|
||||
<a href="/certs/ca-int.der">ca-int.der</a> | <a href="/certs/ca-int.pem">ca-int.pem</a>
|
||||
<hr></p>
|
||||
|
||||
<label for="root-cb">Export certificates + keys:</label><br/>
|
||||
<input type="checkbox" class="export-cb" id="root-cb" value="root"></input>
|
||||
Root Certificate (<a href="#" data-toggle="collapse" data-target="#root-details" title="View Root Certificate details"><small>Details </small><i class="fa fa-fw fa-eye" id="root-show"></i><i class="fa fa-fw fa-eye-slash hidden" id="root-hide"></i></a>)
|
||||
<pre id="root-details" class="collapse">{{ .RootDetails }}</pre>
|
||||
<br id="root-br"/>
|
||||
<input type="checkbox" class="export-cb" id="issuer-cb" value="issuer"></input>
|
||||
Issuer Certificate (<a href="#" data-toggle="collapse" data-target="#issuer-details" title="View Issuer Certificate details"><small>Details </small><i class="fa fa-fw fa-eye" id="issuer-show"></i><i class="fa fa-fw fa-eye-slash hidden" id="issuer-hide"></i></a>)
|
||||
<pre id="issuer-details" class="collapse">{{ .IssuerDetails }}</pre>
|
||||
<div class="row">
|
||||
<div class="col-lg-9 col-md-12">
|
||||
<table class="table table-bordered mb15 p48">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Subject</th>
|
||||
<th></th>
|
||||
<th>Issue RSA</th>
|
||||
<th>Issue ECDSA</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{{ range $item := .CertificateChains }}
|
||||
<tr>
|
||||
<td class="vmiddle">
|
||||
{{ $item.RootCert.Subject }}
|
||||
<a href="#" title="View {{ $item.RootCert.BaseName }} details" id="show-{{ $item.RootCert.BaseName }}"><i class="fa fa-fw fa-eye"></i></a>
|
||||
</td>
|
||||
<td class="vmiddle">
|
||||
<a href="/certs/{{ $item.RootCert.BaseName }}.pem" title="Download this certificate">download</a>
|
||||
</td>
|
||||
<td class="vmiddle"></td>
|
||||
<td class="vmiddle"></td>
|
||||
<td class="vmiddle">
|
||||
<button class="btn btn-outline btn-reg btn-success export-cert-key" type="button" title="Export this certificate and key" data-name="{{ $item.RootCert.BaseName }}" data-subject="{{ $item.RootCert.Subject }}">Export</button>
|
||||
<button class="btn btn-outline btn-reg btn-warning renew-cert" type="button" title="Generate new version with extended lifetime" data-name="{{ $item.RootCert.BaseName }}" data-rootname="{{ $item.RootCert.BaseName }}" data-subject="{{ $item.RootCert.Subject }}" data-rootsubject="{{ $item.RootCert.Subject }}" data-notbefore="{{ $item.RootCert.NotAfter }}" data-isroot="true">Renew</button>
|
||||
<!--
|
||||
<button class="btn btn-outline btn-new-issuer btn-warning new-issuer-cert" type="button" title="Generate new Issuer Certificate under this Root" data-root="{{ $item.RootCert.BaseName }}" data-rootsubject="{{ $item.RootCert.Subject }}">New Issuer</button>
|
||||
<button class="btn btn-outline btn-reg btn-danger delete-cert hidden" type="button" title="Delete this certificate" data-name="{{ $item.RootCert.BaseName }}">Delete</button>
|
||||
-->
|
||||
</td>
|
||||
</tr>
|
||||
{{ range $subitem := $item.IssuerCerts }}
|
||||
<tr>
|
||||
<td class="vmiddle">
|
||||
┖ {{ $subitem.Subject }}
|
||||
<a href="#" title="View {{ $subitem.BaseName }} details"><i class="fa fa-fw fa-eye" id="show-{{ $subitem.BaseName }}"></i></a>
|
||||
</td>
|
||||
<td class="vmiddle">
|
||||
<a href="/certs/{{ $subitem.BaseName }}.pem" title="Download this certificate">download</a>
|
||||
</td>
|
||||
<td class="vmiddle center"><input type="radio" id="rsa-{{ $subitem.BaseName }}" name="issue-rsa" value="{{ $subitem.BaseName }}" title="Use this certificate for issueing RSA leave certificates" {{ if $subitem.UseForRSA }}data-orig="true" checked{{ end }}/></td>
|
||||
<td class="vmiddle center"><input type="radio" id="ecdsa-{{ $subitem.BaseName }}" name="issue-ecdsa" value="{{ $subitem.BaseName }}" title="Use this certificate for issueing ECDSA leave certificates" {{ if $subitem.UseForECDSA }}data-orig="true" checked{{ end }}/></td>
|
||||
<td class="vmiddle">
|
||||
<button class="btn btn-outline btn-reg btn-success export-cert-key" type="button" title="Export this certificate and key" data-name="{{ $subitem.BaseName }}" data-subject="{{ $subitem.Subject }}">Export</button>
|
||||
<button class="btn btn-outline btn-reg btn-warning renew-cert" type="button" title="Generate new version with extended lifetime" data-name="{{ $subitem.BaseName }}" data-rootname="{{ $item.RootCert.BaseName }}" data-subject="{{ $subitem.Subject }}" data-rootsubject="{{ $item.RootCert.Subject }}" data-notbefore="{{ $subitem.NotAfter }}" data-notafter="{{ $item.RootCert.NotAfter }}" data-isroot="false">Renew</button>
|
||||
<!--
|
||||
<button class="btn btn-outline btn-reg btn-danger delete-cert" type="button" title="Delete this certificate" data-name="{{ $subitem.BaseName }}">Delete</button>
|
||||
-->
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
<!--
|
||||
<button class="btn btn-outline btn-6em btn-warning new-root-cert" type="button" title="Generate new Root Certificate">New Root</button>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="export-pfx">Export format:</label><br/>
|
||||
<input type="radio" class="export-rd" id="export-pfx" name="export-type" value="pfx" checked/> .pfx (PKCS#12) file<br/>
|
||||
<input type="radio" class="export-rd" id="export-zip" name="export-type" value="zip"/> .zip archive (less secure)<br/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="export-pwd">Export file password (twice):</label>
|
||||
<input class="form-control non-fluid" type="password" id="export-pwd" name="export-pwd" required/>
|
||||
<span class="fa fa-eye vizpwd"></span>
|
||||
<span class="error hidden" id="export-pwd-err"></span>
|
||||
<input class="form-control non-fluid" type="password" id="export-pwd2" name="export-pwd2" required/>
|
||||
<span class="fa fa-eye vizpwd"></span>
|
||||
<span class="error hidden" id="export-pwd2-err"></span>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default" type="button" id="cert-export" title="Download selected certificate(s) and their keys" disabled>Export Certificate</button>
|
||||
<span class="error hidden" id="cert-export-err"></span>
|
||||
<a id="export-target" style="display: none"></a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="crls">
|
||||
@@ -387,7 +409,9 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-root-key-content">
|
||||
<h4>Root Key Required</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<p>Please provide the Root CA key file in PEM format. As soon as we are done with it, it will be removed from the system again.</p>
|
||||
<span id="modal-show-root-key-subject" class="hidden">Root subject: <span id="modal-root-key-subject"></span><br></span>
|
||||
<textarea class="form-control" id="modal-rootkey" rows="10" cols="80" required></textarea>
|
||||
<div class="form-group">
|
||||
<label for="modal-rootpassphrase">Passphrase (optional):</label>
|
||||
@@ -395,6 +419,7 @@
|
||||
</div>
|
||||
<span class="error" id="modal-root-key-error" style="display: none;"></span><br/>
|
||||
<input class="btn btn-default btn-reg" value="Upload" id="modal-root-key-upload"/>
|
||||
<input class="btn btn-default btn-reg hidden" value="Upload" id="modal-root-key-renew"/>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="cancel-rootkey">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -404,6 +429,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-crl-content">
|
||||
<h4>CRL</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<p>Please provide the CRL for the Root CA.</p>
|
||||
<textarea class="form-control" id="modal-crl-val" rows="10" cols="80" required>{{ .CRL }}</textarea>
|
||||
<span class="error" id="modal-crl-error" style="display: none;"></span><br/>
|
||||
@@ -417,6 +443,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-backup-content">
|
||||
<h4>Upload Backup File</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<p>Please select a backup (.tgz) file that was downloaded from LabCA earlier.</p>
|
||||
<form enctype="multipart/form-data" id="modal-backup-form" method="POST">
|
||||
<input type="hidden" name="action" value="backup-upload">
|
||||
@@ -428,6 +455,107 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ range $item := .CertificateChains }}
|
||||
<div id="modal-{{ $item.RootCert.BaseName }}" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-{{ $item.RootCert.BaseName }}-content">
|
||||
<h4>{{ $item.RootCert.Subject }}</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<pre>{{ $item.RootCert.Details }}</pre>
|
||||
<button type="button" class="btn btn-default btn-modal-dismiss" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ range $subitem := $item.IssuerCerts }}
|
||||
<div id="modal-{{ $subitem.BaseName }}" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-{{ $subitem.BaseName }}-content">
|
||||
<h4>{{ $subitem.Subject }} Details</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<pre>{{ $subitem.Details }}</pre>
|
||||
<button type="button" class="btn btn-default btn-modal-dismiss" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div id="modal-export" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-export-content">
|
||||
<h4>Export certificate + key</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<h5><span id="modal-export-subject"></span></h5>
|
||||
<form enctype="multipart/form-data" id="modal-export-form" method="POST">
|
||||
<input type="hidden" name="export-certname" id="export-certname">
|
||||
<div class="form-group">
|
||||
<label for="export-pfx">Export format:</label><br/>
|
||||
<input type="radio" class="export-rd" id="export-pfx" name="export-type" value="pfx" checked/> .pfx (PKCS#12) file<br/>
|
||||
<input type="radio" class="export-rd" id="export-zip" name="export-type" value="zip"/> .zip archive (less secure)<br/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export-pwd">Export file password (twice):</label>
|
||||
<input class="form-control non-fluid" type="password" id="export-pwd" name="export-pwd" required/>
|
||||
<span class="fa fa-eye vizpwd"></span>
|
||||
<span class="error hidden" id="export-pwd-err"></span>
|
||||
<input class="form-control non-fluid" type="password" id="export-pwd2" name="export-pwd2" required/>
|
||||
<span class="fa fa-eye vizpwd"></span>
|
||||
<span class="error hidden" id="export-pwd2-err"></span>
|
||||
</div>
|
||||
<span class="error" id="modal-export-error" style="display: none;"></span><br/>
|
||||
<a id="export-target" style="display: none"></a>
|
||||
<input class="btn btn-default btn-reg" value="Export" id="modal-export-done"/>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="cancel-export">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-leaves" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-leaves-content">
|
||||
<h4>Are you sure?</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<form enctype="multipart/form-data" id="modal-leaves-form" method="POST">
|
||||
<input type="hidden" id="new-rsa-issuer" name="new-rsa-issuer">
|
||||
<input type="hidden" id="new-ecdsa-issuer" name="new-ecdsa-issuer">
|
||||
<span id="want-new-rsa-issuer" class="hidden">• Change the RSA issuer certificate<br/></span>
|
||||
<span id="want-new-ecdsa-issuer" class="hidden">• Change the ECDSA issuer certificate<br/></span>
|
||||
<span class="error" id="modal-leaves-error" style="display: none;"></span><br/>
|
||||
<input class="btn btn-default btn-reg btn-warning btn-6em" value="Change" id="modal-leaves-done"/>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="cancel-leaves">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-renew" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content modal-renew-content">
|
||||
<h4>Renew CA</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span>×</span></button>
|
||||
<form enctype="multipart/form-data" id="modal-renew-form" method="POST">
|
||||
<input type="hidden" id="modal-renew-cert" name="modal-renew-cert">
|
||||
<input type="hidden" id="renew-rootcert" name="renew-rootcert">
|
||||
<input type="hidden" id="renew-rootsubject" name="renew-rootsubject">
|
||||
Subject: <span id="renew-subject"></span><br><br>
|
||||
Current end date: <span id="renew-current-enddate"></span><br>
|
||||
<span id="renew-show-root-enddate" class="hidden">Root end date: <span id="renew-root-enddate"></span><br></span>
|
||||
<br>
|
||||
|
||||
<label>New validity:</label>
|
||||
<input class="form-control non-fluid form-small-inline" type="text" name="renew-numdays" id="renew-numdays" required> days<br>
|
||||
<span class="error backend-error hidden" id="renew-numdays-error"></span><br>
|
||||
New end date: <span id="renew-new-enddate"></span><br>
|
||||
|
||||
<span class="error" id="modal-renew-error" style="display: none;"></span><br/>
|
||||
<input class="btn btn-default btn-reg btn-warning" value="Renew" id="modal-renew-done"/>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="cancel-renew">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{ define "tail" }}
|
||||
@@ -441,6 +569,9 @@
|
||||
$('.nav-tabs-sticky').stickyTabs();
|
||||
|
||||
$('#modal-backup-form').ajaxForm();
|
||||
$('#modal-export-form').ajaxForm();
|
||||
$('#modal-leaves-form').ajaxForm();
|
||||
$('#modal-renew-form').ajaxForm();
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
positionFooter();
|
||||
@@ -460,13 +591,75 @@
|
||||
}
|
||||
});
|
||||
|
||||
{{ range $item := .CertificateChains }}
|
||||
$("#show-{{ $item.RootCert.BaseName }}").click(function(evt) {
|
||||
$('#modal-{{ $item.RootCert.BaseName }}').modal('show');
|
||||
return false;
|
||||
});
|
||||
{{ range $subitem := $item.IssuerCerts }}
|
||||
$("#show-{{ $subitem.BaseName }}").click(function(evt) {
|
||||
$('#modal-{{ $subitem.BaseName }}').modal('show');
|
||||
return false;
|
||||
});
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
function leave_issuer_changed() {
|
||||
iss_rsa = "";
|
||||
iss_ecdsa = "";
|
||||
iss_rsa_changed = false;
|
||||
iss_ecdsa_changed = false;
|
||||
|
||||
$('input[type=radio][name=issue-rsa]').each(function() {
|
||||
if ($(this).prop('checked') && !$(this).data('orig')) {
|
||||
iss_rsa = $(this).val();
|
||||
iss_rsa_changed = true;
|
||||
}
|
||||
});
|
||||
$('input[type=radio][name=issue-ecdsa]').each(function() {
|
||||
if ($(this).prop('checked') && !$(this).data('orig')) {
|
||||
iss_ecdsa = $(this).val();
|
||||
iss_ecdsa_changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (iss_rsa_changed || iss_ecdsa_changed) {
|
||||
$("#new-rsa-issuer").val(iss_rsa);
|
||||
if (iss_rsa_changed) {
|
||||
$("#want-new-rsa-issuer").removeClass("hidden").show();
|
||||
} else {
|
||||
$("#want-new-rsa-issuer").hide();
|
||||
}
|
||||
$("#new-ecdsa-issuer").val(iss_ecdsa);
|
||||
if (iss_ecdsa_changed) {
|
||||
$("#want-new-ecdsa-issuer").removeClass("hidden").show();
|
||||
} else {
|
||||
$("#want-new-ecdsa-issuer").hide();
|
||||
}
|
||||
|
||||
$('#modal-leaves').modal('show');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$('input[type=radio][name=issue-rsa]').change(function() {
|
||||
return leave_issuer_changed();
|
||||
});
|
||||
|
||||
$('input[type=radio][name=issue-ecdsa]').change(function() {
|
||||
return leave_issuer_changed();
|
||||
});
|
||||
|
||||
$(".btn").click(function(evt) {
|
||||
$('#modal-spinner').modal('show');
|
||||
if ($(evt.target).hasClass('btn-warning') || $(evt.target).hasClass('btn-danger')) {
|
||||
if (!window.confirm("Are you sure?")) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
return false;
|
||||
if ($(evt.target).attr("id") != "modal-leaves-done" && $(evt.target).attr("id") != "modal-renew-done" && $(evt.target).attr("id") != "modal-newcert-done" &&
|
||||
!$(evt.target).hasClass('renew-cert') && !$(evt.target).hasClass('new-issuer-cert') && !$(evt.target).hasClass('new-root-cert')) {
|
||||
if (!window.confirm("Are you sure?")) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$(evt.target).blur();
|
||||
@@ -486,7 +679,9 @@
|
||||
$("#issuer-crl-result").hide();
|
||||
$("#crl-interval-result").hide();
|
||||
$("#modal-backup-error").hide();
|
||||
|
||||
$("#modal-export-error").hide();
|
||||
$("#modal-leaves-error").hide();
|
||||
$("#modal-renew-error").hide();
|
||||
|
||||
if ( $(evt.target).attr("id") == "backup-now") {
|
||||
$.ajax(window.location.href, {
|
||||
@@ -617,7 +812,7 @@
|
||||
$("#backup-result").removeClass("hidden").removeClass("success").show().text(err).addClass("error");
|
||||
});
|
||||
|
||||
} else if ( $(evt.target).attr("id") == "cert-export") {
|
||||
} else if ($(evt.target).attr("id") == "modal-export-done") {
|
||||
type = ($("#export-zip").prop('checked') ? "zip" : ($("#export-pfx").prop('checked') ? "pfx" : "none"));
|
||||
|
||||
if ($("#export-pwd").val().length < 4) {
|
||||
@@ -635,36 +830,41 @@
|
||||
req.onload = function (event) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
|
||||
var blob = req.response;
|
||||
var fileName = null;
|
||||
var contentType = req.getResponseHeader("content-type");
|
||||
if (event.currentTarget.status == 200) {
|
||||
var blob = req.response;
|
||||
var fileName = null;
|
||||
var contentType = req.getResponseHeader("content-type");
|
||||
|
||||
// IE/EDGE seems not returning some response header
|
||||
if (req.getResponseHeader("content-disposition")) {
|
||||
var contentDisposition = req.getResponseHeader("content-disposition");
|
||||
fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
|
||||
} else {
|
||||
fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
|
||||
}
|
||||
// IE/EDGE seems not returning some response header
|
||||
if (req.getResponseHeader("content-disposition")) {
|
||||
var contentDisposition = req.getResponseHeader("content-disposition");
|
||||
fileName = contentDisposition.substring(contentDisposition.indexOf("=") + 1);
|
||||
} else {
|
||||
fileName = "unnamed." + contentType.substring(contentType.indexOf("/") + 1);
|
||||
}
|
||||
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
// Internet Explorer
|
||||
window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
// Internet Explorer
|
||||
window.navigator.msSaveOrOpenBlob(new Blob([blob], { type: contentType }), fileName);
|
||||
} else {
|
||||
var el = document.getElementById("export-target");
|
||||
el.href = window.URL.createObjectURL(blob);
|
||||
el.download = fileName;
|
||||
el.click();
|
||||
}
|
||||
|
||||
$('#modal-export').modal('hide');
|
||||
} else {
|
||||
var el = document.getElementById("export-target");
|
||||
el.href = window.URL.createObjectURL(blob);
|
||||
el.download = fileName;
|
||||
el.click();
|
||||
$("#modal-export-error").removeClass("hidden").show().text("Backend returned: " + event.currentTarget.statusText);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function (event) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$("#cert-export-err").removeClass("hidden").show().text("Oops, something went wrong...");
|
||||
$("#modal-export-error").removeClass("hidden").show().text("Error communicating with backend server");
|
||||
};
|
||||
|
||||
req.send("action=" + $(evt.target).attr("id") + "&root=" + $("#root-cb").prop('checked') +
|
||||
"&issuer=" + $("#issuer-cb").prop('checked') + "&type=" + type + "&export-pwd=" + $("#export-pwd").val());
|
||||
req.send("action=cert-export&certname=" + $("#export-certname").val() + "&type=" + type + "&export-pwd=" + $("#export-pwd").val());
|
||||
}
|
||||
|
||||
} else if ( $(evt.target).attr("id") == "update-account") {
|
||||
@@ -1085,13 +1285,200 @@
|
||||
});
|
||||
}
|
||||
|
||||
} else if ( $(evt.target).attr("id") == "cancel-rootkey" || $(evt.target).attr("id") == "cancel-crl" || $(evt.target).attr("id") == "cancel-backup") {
|
||||
} else if ($(evt.target).attr("id") == "cancel-rootkey" || $(evt.target).attr("id") == "cancel-crl" || $(evt.target).attr("id") == "cancel-backup" ||
|
||||
$(evt.target).attr("id") == "cancel-export" || $(evt.target).attr("id") == "cancel-leaves" || $(evt.target).attr("id") == "cancel-renew" ||
|
||||
$(evt.target).attr("id") == "cancel-newcert") {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$('#modal-backup').modal('hide');
|
||||
$('#modal-crl').modal('hide');
|
||||
$('#modal-root-key').modal('hide');
|
||||
$('#modal-export').modal('hide');
|
||||
$('#modal-leaves').modal('hide');
|
||||
$('#modal-renew').modal('hide');
|
||||
$('#modal-newcert').modal('hide');
|
||||
return false;
|
||||
|
||||
} else if ($(evt.target).hasClass("btn-modal-dismiss")) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
return true;
|
||||
|
||||
} else if ($(evt.target).attr("id") == "modal-leaves-done") {
|
||||
if ($("#want-new-rsa-issuer").hasClass("hidden") && $("#want-new-ecdsa-issuer").hasClass("hidden")) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$('#modal-leaves').modal('hide');
|
||||
} else {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("POST", window.location.href, true);
|
||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
|
||||
req.onload = function(event) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
|
||||
if (event.currentTarget.status == 200) {
|
||||
try {
|
||||
res = JSON.parse(req.response);
|
||||
if (res.Success) {
|
||||
$('input[type=radio][name=issue-rsa]').each(function() {
|
||||
$(this).data('orig', $(this).prop('checked'))
|
||||
});
|
||||
$('input[type=radio][name=issue-ecdsa]').each(function() {
|
||||
$(this).data('orig', $(this).prop('checked'))
|
||||
});
|
||||
$('#modal-leaves').modal('hide');
|
||||
|
||||
var msg = "Successfully updated issuers, restarting 'Boulder (ACME)' now...";
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 10000);
|
||||
$("#modal-leaves-error").removeClass("hidden").removeClass("error").show().text(msg).addClass("success");
|
||||
|
||||
} else {
|
||||
$("#modal-leaves-error").removeClass("hidden").show().text(res.Error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
$("#modal-leaves-error").removeClass("hidden").show().text("Received an unexpected response from the server");
|
||||
}
|
||||
|
||||
} else if (event.currentTarget.status == 502) {
|
||||
// Is a result of the backend restart...
|
||||
var msg = "Successfully updated issuers and restarted 'Boulder (ACME)'";
|
||||
$("#modal-leaves-error").removeClass("hidden").removeClass("error").show().text(msg).addClass("success");
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
|
||||
} else {
|
||||
$("#modal-leaves-error").removeClass("hidden").show().text("Backend returned: " + event.currentTarget.statusText);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function(event) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$("#modal-leaves-error").removeClass("hidden").show().text("Error communicating with backend server");
|
||||
};
|
||||
|
||||
req.send("action=update-leave-issuers" + "&rsa=" + $("#new-rsa-issuer").val() + "&ecdsa=" + $("#new-ecdsa-issuer").val());
|
||||
}
|
||||
return false;
|
||||
|
||||
} else if ($(evt.target).hasClass('export-cert-key')) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$('#modal-export-subject').text($(evt.target).data('subject'));
|
||||
$('#export-certname').val($(evt.target).data('name'));
|
||||
$('#modal-export').modal('show');
|
||||
return false;
|
||||
|
||||
} else if ($(evt.target).hasClass('renew-cert')) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$('#modal-renew-cert').val($(evt.target).data('name'));
|
||||
$('#renew-rootcert').val($(evt.target).data('rootname'));
|
||||
$('#renew-subject').text($(evt.target).data('subject'));
|
||||
$('#renew-rootsubject').val($(evt.target).data('rootsubject'));
|
||||
d = new Date($(evt.target).data('notbefore')).toUTCString()
|
||||
$('#renew-current-enddate').text(d);
|
||||
if ($(evt.target).data('isroot')) {
|
||||
$("#renew-show-root-enddate").hide();
|
||||
$("#renew-root-enddate").text("");
|
||||
$('#renew-numdays').val(4 * 365);
|
||||
} else {
|
||||
$("#renew-show-root-enddate").removeClass("hidden").show();
|
||||
d = new Date($(evt.target).data('notafter')).toUTCString()
|
||||
$("#renew-root-enddate").text(d);
|
||||
$('#renew-numdays').val(365);
|
||||
}
|
||||
updateEndDate();
|
||||
$('#modal-renew').modal('show');
|
||||
return false;
|
||||
|
||||
} else if ($(evt.target).attr("id") == "modal-renew-done") {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("POST", window.location.href, true);
|
||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
|
||||
req.onload = function(event) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
|
||||
if (event.currentTarget.status == 200) {
|
||||
try {
|
||||
res = JSON.parse(req.response);
|
||||
if (res.Success) {
|
||||
var msg = "Successfully updated issuers and restarted 'Boulder (ACME)'";
|
||||
setTimeout(function () {
|
||||
$('#modal-renew').modal('hide');
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
$("#modal-renew-error").removeClass("hidden").removeClass("error").show().text(msg).addClass("success");
|
||||
|
||||
} else if (res.Error.startsWith("NO_ROOT_KEY")) {
|
||||
$("#modal-show-root-key-subject").removeClass("hidden").show();
|
||||
$("#modal-root-key-subject").text($('#renew-rootsubject').val());
|
||||
$("#modal-root-key-upload").hide();
|
||||
$("#modal-root-key-renew").removeClass("hidden").show();
|
||||
if (res.Error.split(":").length > 1) {
|
||||
$("#modal-root-key-error").show().text(res.Error.split(":")[1]);
|
||||
}
|
||||
$('#modal-root-key').modal('show');
|
||||
$('#modal-renew').modal('hide');
|
||||
return false;
|
||||
|
||||
} else {
|
||||
$("#modal-renew-error").removeClass("hidden").show().text(res.Error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
$("#modal-renew-error").removeClass("hidden").show().text("Received an unexpected response from the server");
|
||||
}
|
||||
|
||||
} else if (event.currentTarget.status == 502) {
|
||||
// Is a result of the backend restart...
|
||||
var msg = "Successfully updated issuers and restarted 'Boulder (ACME)'";
|
||||
$("#modal-renew-error").removeClass("hidden").removeClass("error").show().text(msg).addClass("success");
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
|
||||
} else {
|
||||
$("#modal-renew-error").removeClass("hidden").show().text("Backend returned: " + event.currentTarget.statusText);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function(event) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$("#modal-renew-error").removeClass("hidden").show().text("Error communicating with backend server");
|
||||
};
|
||||
|
||||
req.send("action=renew-cert&certname=" + $('#modal-renew-cert').val() + "&days=" + $("#renew-numdays").val() +
|
||||
"&rootname=" + $('#renew-rootcert').val() + "&root_key=" + $("#modal-rootkey").val() + "&passphrase=" + $("#modal-rootpassphrase").val());
|
||||
|
||||
return false;
|
||||
|
||||
} else if ($(evt.target).attr("id") == "modal-root-key-renew") {
|
||||
if ($("#modal-rootkey").val() == "") {
|
||||
$('#modal-spinner').modal('hide');
|
||||
$("#modal-root-key-error").show().text("Please provide the root key");
|
||||
return false;
|
||||
} else {
|
||||
$("#modal-show-root-key-subject").hide();
|
||||
$("#modal-root-key-upload").removeClass("hidden").show();
|
||||
$("#modal-root-key-renew").hide();
|
||||
$('#modal-root-key').modal('hide');
|
||||
$('#modal-renew').modal('show');
|
||||
$("#modal-renew-done").click();
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if ($(evt.target).hasClass('new-issuer-cert')) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
window.location.href = "/manage/newissuer?root=" + $(evt.target).data('root');
|
||||
|
||||
} else if ($(evt.target).hasClass('new-root-cert')) {
|
||||
$('#modal-spinner').modal('hide');
|
||||
window.location.href = "/manage/newroot";
|
||||
|
||||
} else if ($(evt.target).hasClass("delete-cert")) {
|
||||
console.log("TODO: implement delete...");
|
||||
|
||||
} else {
|
||||
$.ajax(window.location.href, {
|
||||
method: "POST",
|
||||
@@ -1145,54 +1532,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
$("#root-details").on('show.bs.collapse', function() {
|
||||
$("#root-show").hide();
|
||||
$("#root-hide").removeClass("hidden").show();
|
||||
$("#root-br").hide();
|
||||
});
|
||||
|
||||
$("#root-details").on('hidden.bs.collapse', function() {
|
||||
$("#root-show").show();
|
||||
$("#root-hide").hide();
|
||||
$("#root-br").show();
|
||||
});
|
||||
|
||||
$("#issuer-details").on('show.bs.collapse', function() {
|
||||
$("#issuer-show").hide();
|
||||
$("#issuer-hide").removeClass("hidden").show();
|
||||
});
|
||||
|
||||
$("#issuer-details").on('hidden.bs.collapse', function() {
|
||||
$("#issuer-show").show();
|
||||
$("#issuer-hide").hide();
|
||||
});
|
||||
|
||||
function change_export_settings() {
|
||||
checkbox_count = 0;
|
||||
if ($("#root-cb").prop('checked')) {
|
||||
checkbox_count += 1;
|
||||
}
|
||||
if ($("#issuer-cb").prop('checked')) {
|
||||
checkbox_count += 1;
|
||||
}
|
||||
|
||||
if ($("input[type=radio]#export-pfx").prop('checked')) {
|
||||
$("#cert-export").prop('disabled', checkbox_count != 1);
|
||||
} else {
|
||||
$("#cert-export").prop('disabled', checkbox_count == 0);
|
||||
}
|
||||
|
||||
if ($("input[type=radio]#export-zip").prop('checked')) {
|
||||
$("#cert-export").text("Export Certificate(s)");
|
||||
} else {
|
||||
$("#cert-export").text("Export Certificate");
|
||||
}
|
||||
}
|
||||
|
||||
$(".export-cb").change(change_export_settings);
|
||||
|
||||
$(".export-rd").change(change_export_settings);
|
||||
|
||||
function check_fqdn() {
|
||||
if ($("#fqdn").val() == window.location.host) {
|
||||
$("#fqdn_warning").hide();
|
||||
@@ -1236,7 +1575,87 @@
|
||||
$("#update-account").prop('disabled', !ok);
|
||||
});
|
||||
|
||||
function updateEndDate() {
|
||||
// Determine new end date
|
||||
x = new Date();
|
||||
x.setDate(x.getDate() + parseInt($('#renew-numdays').val()))
|
||||
$('#renew-new-enddate').text(x.toUTCString());
|
||||
|
||||
try {
|
||||
c = new Date($('#renew-current-enddate').text());
|
||||
if (x <= c) {
|
||||
if ($("#renew-numdays-error").hasClass('hidden')) {
|
||||
$("#renew-numdays-error").text("Validity cannot end before current end date");
|
||||
$("#renew-numdays-error").removeClass('hidden').show();
|
||||
}
|
||||
} else if ($('#renew-root-enddate').text().length > 0) {
|
||||
r = new Date($('#renew-root-enddate').text());
|
||||
if (x > r) {
|
||||
if ($("#renew-numdays-error").hasClass('hidden')) {
|
||||
$("#renew-numdays-error").text("Validity cannot exceed root certificate end date");
|
||||
$("#renew-numdays-error").removeClass('hidden').show();
|
||||
}
|
||||
} else {
|
||||
$("#renew-numdays-error").text("");
|
||||
if (!$("#renew-numdays-error").hasClass('hidden')) {
|
||||
$("#renew-numdays-error").hide().addClass('hidden');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$("#renew-numdays-error").text("");
|
||||
if (!$("#renew-numdays-error").hasClass('hidden')) {
|
||||
$("#renew-numdays-error").hide().addClass('hidden');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
$("#modal-renew-done").prop('disabled', !$("#renew-numdays-error").hasClass('hidden'));
|
||||
}
|
||||
|
||||
$.fn.inputFilter = function(callback, errMsg) {
|
||||
return this.on("input keydown keyup mousedown mouseup select contextmenu drop focusout", function (e) {
|
||||
if (callback(this.value)) {
|
||||
// Accepted value
|
||||
if (["keydown", "mousedown", "focusout"].indexOf(e.type) >= 0) {
|
||||
$("#renew-numdays-error").text("");
|
||||
if (!$("#renew-numdays-error").hasClass('hidden')) {
|
||||
$("#renew-numdays-error").hide().addClass('hidden');
|
||||
}
|
||||
}
|
||||
this.oldValue = this.value;
|
||||
this.oldSelectionStart = this.selectionStart;
|
||||
this.oldSelectionEnd = this.selectionEnd;
|
||||
updateEndDate();
|
||||
} else if (this.hasOwnProperty("oldValue")) {
|
||||
// Rejected value - restore the previous one
|
||||
$("#renew-numdays-error").text(errMsg);
|
||||
if ($("#renew-numdays-error").hasClass('hidden')) {
|
||||
$("#renew-numdays-error").removeClass('hidden').show();
|
||||
}
|
||||
this.value = this.oldValue;
|
||||
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
|
||||
} else {
|
||||
// Rejected value - nothing to restore
|
||||
this.value = "";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$("#renew-numdays").inputFilter(function (value) {
|
||||
return /^\d*$/.test(value); // Allow digits only, using a RegExp
|
||||
}, "Only digits are allowed");
|
||||
|
||||
$(".delete-cert.hidden").each(function () {
|
||||
try {
|
||||
if ($($(this).parent().parent().next().children()[0]).text().indexOf("┖") < 0) {
|
||||
$(this).removeClass('hidden');
|
||||
}
|
||||
} catch (e) { }
|
||||
});
|
||||
|
||||
pwduxHandlers('#password-strength', '#new-password', ['#username', '#accemail']);
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
4
install
4
install
@@ -448,7 +448,7 @@ install_pkg() {
|
||||
}
|
||||
|
||||
install_extra() {
|
||||
local packages=(apt-transport-https ca-certificates curl gnupg net-tools tzdata ucspi-tcp zip python lsb-release)
|
||||
local packages=(apt-transport-https ca-certificates curl gnupg net-tools tzdata ucspi-tcp zip unzip python lsb-release)
|
||||
for package in "${packages[@]}"; do
|
||||
install_pkg "$package"
|
||||
done
|
||||
@@ -519,8 +519,10 @@ static_web() {
|
||||
cp -rp $cloneDir/gui/static/* .
|
||||
[ -e $adminDir/data/root-ca.crl ] && cp $adminDir/data/root-ca.crl crl/ || true
|
||||
[ -e $adminDir/data/root-ca.pem ] && cp $adminDir/data/root-ca.pem certs/ || true
|
||||
[ -e $adminDir/data/root-ca.pem ] && ln -sf root-ca.pem certs/test-root.pem || true
|
||||
[ -e $adminDir/data/root-ca.der ] && cp $adminDir/data/root-ca.der certs/ || true
|
||||
[ -e $adminDir/data/issuer/ca-int.pem ] && cp $adminDir/data/issuer/ca-int.pem certs/ || true
|
||||
[ -e $adminDir/data/issuer/ca-int.pem ] && ln -sf ca-int.pem certs/test-ca.pem || true
|
||||
[ -e $adminDir/data/issuer/ca-int.der ] && cp $adminDir/data/issuer/ca-int.der certs/ || true
|
||||
|
||||
local have_config=$(grep restarted $adminDir/data/config.json | grep true)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
diff --git a/crl/storer/storer.go b/crl/storer/storer.go
|
||||
index cd0bf86c..dd492aec 100644
|
||||
index cd0bf86c0..26e52f789 100644
|
||||
--- a/crl/storer/storer.go
|
||||
+++ b/crl/storer/storer.go
|
||||
@@ -12,6 +12,9 @@ import (
|
||||
@@ -11,7 +11,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
+ "io/fs"
|
||||
"math/big"
|
||||
+ "os"
|
||||
+ "path/filepath"
|
||||
@@ -12,7 +14,7 @@ index cd0bf86c..dd492aec 100644
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
@@ -38,6 +41,7 @@ type crlStorer struct {
|
||||
@@ -38,6 +42,7 @@ type crlStorer struct {
|
||||
cspb.UnimplementedCRLStorerServer
|
||||
s3Client simpleS3
|
||||
s3Bucket string
|
||||
@@ -20,7 +22,7 @@ index cd0bf86c..dd492aec 100644
|
||||
issuers map[issuance.IssuerNameID]*issuance.Certificate
|
||||
uploadCount *prometheus.CounterVec
|
||||
sizeHistogram *prometheus.HistogramVec
|
||||
@@ -50,6 +54,7 @@ func New(
|
||||
@@ -50,6 +55,7 @@ func New(
|
||||
issuers []*issuance.Certificate,
|
||||
s3Client simpleS3,
|
||||
s3Bucket string,
|
||||
@@ -28,7 +30,7 @@ index cd0bf86c..dd492aec 100644
|
||||
stats prometheus.Registerer,
|
||||
log blog.Logger,
|
||||
clk clock.Clock,
|
||||
@@ -83,6 +88,7 @@ func New(
|
||||
@@ -83,6 +89,7 @@ func New(
|
||||
issuers: issuersByNameID,
|
||||
s3Client: s3Client,
|
||||
s3Bucket: s3Bucket,
|
||||
@@ -36,7 +38,7 @@ index cd0bf86c..dd492aec 100644
|
||||
uploadCount: uploadCount,
|
||||
sizeHistogram: sizeHistogram,
|
||||
latencyHistogram: latencyHistogram,
|
||||
@@ -203,15 +209,19 @@ func (cs *crlStorer) UploadCRL(stream cspb.CRLStorer_UploadCRLServer) error {
|
||||
@@ -203,15 +210,19 @@ func (cs *crlStorer) UploadCRL(stream cspb.CRLStorer_UploadCRLServer) error {
|
||||
checksum := sha256.Sum256(crlBytes)
|
||||
checksumb64 := base64.StdEncoding.EncodeToString(checksum[:])
|
||||
crlContentType := "application/pkix-crl"
|
||||
@@ -65,7 +67,7 @@ index cd0bf86c..dd492aec 100644
|
||||
|
||||
latency := cs.clk.Now().Sub(start)
|
||||
cs.latencyHistogram.WithLabelValues(issuer.Subject.CommonName).Observe(latency.Seconds())
|
||||
@@ -240,3 +250,46 @@ func getIDPExt(exts []pkix.Extension) []byte {
|
||||
@@ -240,3 +251,46 @@ func getIDPExt(exts []pkix.Extension) []byte {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -89,7 +91,7 @@ index cd0bf86c..dd492aec 100644
|
||||
+ lntmp := ln + ".new"
|
||||
+ fn = fmt.Sprintf("%d-%d-%d.crl", nameID, crlNumber, shardIdx)
|
||||
+
|
||||
+ if err = os.Remove(lntmp); err != nil && !os.IsNotExist(err) {
|
||||
+ if err = os.Remove(lntmp); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
+ return fmt.Errorf("removing symlink: %w", err)
|
||||
+ }
|
||||
+ if err = os.Symlink(fn, lntmp); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user