diff --git a/gui/apply b/gui/apply index cc1b8a9..2984ab3 100755 --- a/gui/apply +++ b/gui/apply @@ -12,7 +12,11 @@ cd /opt/wwwstatic $baseDir/apply-nginx -cp $PKI_ROOT_CERT_BASE.crl crl/ +if [ -e "$PKI_ROOT_CERT_BASE.crl" ]; then + cp $PKI_ROOT_CERT_BASE.crl crl/ +else + echo "WARNING: no Root CRL file present - please upload one from the manage page" +fi cp $PKI_ROOT_CERT_BASE.pem certs/ cp $PKI_ROOT_CERT_BASE.der certs/ cp $PKI_INT_CERT_BASE.pem certs/ diff --git a/gui/apply-boulder b/gui/apply-boulder index 68c0206..d5dc047 100755 --- a/gui/apply-boulder +++ b/gui/apply-boulder @@ -191,13 +191,15 @@ fi if [ -e $PKI_ROOT_CERT_BASE.key ]; then cp -p $PKI_ROOT_CERT_BASE.key test-root.key cp -p $PKI_ROOT_CERT_BASE.key.der test-root.key.der - cp -p $PKI_ROOT_CERT_BASE.pem test-root.pem openssl rsa -in $PKI_ROOT_CERT_BASE.key -pubout > test-root.pubkey.pem 2>/dev/null || openssl ec -in $PKI_ROOT_CERT_BASE.key -pubout > test-root.pubkey.pem openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in test-root.key -out test-root.p8 fi +if [ -e $PKI_ROOT_CERT_BASE.pem ]; then + cp -p $PKI_ROOT_CERT_BASE.pem test-root.pem +fi chown -R `ls -l PKI.md | cut -d" " -f 3,4 | sed 's/ /:/g'` . -if [ -e $PKI_INT_CERT_BASE.key ] && [ -e $PKI_ROOT_CERT_BASE.key ]; then +if [ -e $PKI_INT_CERT_BASE.key ] && [ -e $PKI_ROOT_CERT_BASE.pem ]; then [ -f setup_complete ] || touch setup_complete fi diff --git a/gui/certificate.go b/gui/certificate.go index 4351735..5111244 100644 --- a/gui/certificate.go +++ b/gui/certificate.go @@ -16,10 +16,11 @@ import ( // CertificateInfo contains all data related to a certificate (file) type CertificateInfo struct { - IsRoot bool - KeyTypes map[string]string - KeyType string - CreateType string + IsRoot bool + KeyTypes map[string]string + KeyType string + CreateType string + IsRootGenerated bool Country string Organization string @@ -33,6 +34,7 @@ type CertificateInfo struct { Key string Passphrase string Certificate string + CRL string RequestBase string Errors map[string]string @@ -84,7 +86,7 @@ func (ci *CertificateInfo) Validate() bool { } if ci.CreateType == "upload" { - if strings.TrimSpace(ci.Key) == "" { + if !ci.IsRoot && strings.TrimSpace(ci.Key) == "" { ci.Errors["Key"] = "Please provide a PEM-encoded key" } if strings.TrimSpace(ci.Certificate) == "" { @@ -95,7 +97,7 @@ func (ci *CertificateInfo) Validate() bool { return len(ci.Errors) == 0 } -func reportError(err error) error { +func reportError(param interface{}) error { lines := strings.Split(string(debug.Stack()), "\n") if len(lines) >= 5 { lines = append(lines[:0], lines[5:]...) @@ -113,7 +115,17 @@ func reportError(err error) error { fmt.Println(strings.Join(lines, "\n")) - return errors.New("Error (" + err.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") + case []byte: + res = errors.New("Error (" + string(v) + ")! See LabCA logs for details") + default: + fmt.Printf("unexpected type %T", v) + } + + return res } func getRandomSerial() (string, error) { @@ -212,7 +224,10 @@ func (ci *CertificateInfo) Generate(path string, certBase string) error { 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 _, 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 3600 -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") + } return reportError(err) } } @@ -287,7 +302,7 @@ func (ci *CertificateInfo) ImportZip(tmpFile string, tmpDir string) error { } // Import a certificate and key file -func (ci *CertificateInfo) Import(path string, certBase string, tmpDir string, tmpKey string, tmpCert string) error { +func (ci *CertificateInfo) Import(tmpDir string, tmpKey string, tmpCert string) error { tmpFile := filepath.Join(tmpDir, ci.ImportHandler.Filename) f, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0666) @@ -320,34 +335,36 @@ func (ci *CertificateInfo) Import(path string, certBase string, tmpDir string, t } // Upload a certificate and key file -func (ci *CertificateInfo) Upload(path string, certBase string, tmpKey string, tmpCert string) error { - if err := os.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil { - return err - } - - pwd := "pass:dummy" - 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:") { - return errors.New("incorrect password") +func (ci *CertificateInfo) Upload(tmpKey string, tmpCert string) error { + if ci.Key != "" { + if err := os.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil { + return err } - return reportError(err) - } + pwd := "pass:dummy" + if ci.Passphrase != "" { + pwd = "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1) + } - if _, err := exeCmd("mv " + tmpKey + "-out " + tmpKey); err != nil { - return reportError(err) + if out, err := exeCmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil { + if strings.Contains(string(out), ":bad decrypt:") { + return errors.New("incorrect password") + } + + return reportError(err) + } + + if _, err := exeCmd("mv " + tmpKey + "-out " + tmpKey); err != nil { + return reportError(err) + } } if err := os.WriteFile(tmpCert, []byte(ci.Certificate), 0644); err != nil { return err } - if _, err := exeCmd("openssl x509 -in " + tmpCert + " -out " + tmpCert + "-out"); err != nil { - return reportError(err) + if out, err := exeCmd("openssl x509 -in " + tmpCert + " -out " + tmpCert + "-out"); err != nil { + return reportError(out) } if _, err := exeCmd("mv " + tmpCert + "-out " + tmpCert); err != nil { @@ -357,6 +374,54 @@ func (ci *CertificateInfo) Upload(path string, certBase string, tmpKey string, t return nil } +func parseSubjectDn(subject string) map[string]string { + trackerResultMap := map[string]string{"C=": "", "C =": "", "O=": "", "O =": "", "CN=": "", "CN =": "", "OU=": "", "OU =": ""} + + for tracker, _ := range trackerResultMap { + index := strings.Index(subject, tracker) + + if index < 0 { + continue + } + + var res string + // track quotes for delimited fields so we know not to split on the comma + quoteCount := 0 + + for i := index + len(tracker); i < len(subject); i++ { + char := subject[i] + + // if ", we need to count and delimit + if char == 34 { + quoteCount++ + if quoteCount == 2 { + break + } else { + continue + } + } + + // comma, lets stop here but only if we don't have quotes + if char == 44 && quoteCount == 0 { + break + } + + // add this individual char + res += string(rune(char)) + } + + trackerResultMap[strings.TrimSpace(strings.TrimSuffix(tracker, "="))] = strings.TrimSpace(strings.TrimPrefix(res, "=")) + } + + for k, v := range trackerResultMap { + if len(v) == 0 { + delete(trackerResultMap, k) + } + } + + return trackerResultMap +} + // ImportCerts imports both the root and the issuer certificates func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey string, issuerCert string, issuerKey string) error { var rootSubject string @@ -369,12 +434,32 @@ func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey str rootSubject = string(r[0 : len(r)-1]) fmt.Printf("Import root with subject '%s'\n", rootSubject) - _, err = exeCmd("openssl pkey -noout -in " + rootKey) - if err != nil { - return reportError(err) + subjectMap := parseSubjectDn(rootSubject) + if val, ok := subjectMap["C"]; ok { + ci.Country = val + } + if val, ok := subjectMap["O"]; ok { + ci.Organization = val + } + if val, ok := subjectMap["OU"]; ok { + ci.OrgUnit = val + } + if val, ok := subjectMap["CN"]; ok { + ci.CommonName = val } - fmt.Println("Import root key") + keyFileExists := true + if _, err := os.Stat(rootKey); os.IsNotExist(err) { + keyFileExists = false + } + if keyFileExists { + _, err = exeCmd("openssl pkey -noout -in " + rootKey) + if err != nil { + return reportError(err) + } + + fmt.Println("Import root key") + } } if (issuerCert != "") && (issuerKey != "") { @@ -414,6 +499,11 @@ 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) + if err != nil { + return errors.New("could not verify that issuer was issued by our Root CA") + } + _, err = exeCmd("openssl pkey -noout -in " + issuerKey) if err != nil { return reportError(err) @@ -433,8 +523,14 @@ func (ci *CertificateInfo) MoveFiles(path string, rootCert string, rootKey strin } } if rootKey != "" { - if _, err := exeCmd("mv " + rootKey + " " + path); err != nil { - return reportError(err) + keyFileExists := true + if _, err := os.Stat(rootKey); os.IsNotExist(err) { + keyFileExists = false + } + if keyFileExists { + if _, err := exeCmd("mv " + rootKey + " " + path); err != nil { + return reportError(err) + } } } if issuerCert != "" { @@ -449,7 +545,7 @@ func (ci *CertificateInfo) MoveFiles(path string, rootCert string, rootKey strin } if (issuerCert != "") && (issuerKey != "") && ci.IsRoot { - if err := postCreateTasks(path+"issuer/", "ca-int"); err != nil { + if err := postCreateTasks(path+"issuer/", "ca-int", false); err != nil { return err } } @@ -458,7 +554,7 @@ func (ci *CertificateInfo) MoveFiles(path string, rootCert string, rootKey strin } // Extract key and certificate files from a container file -func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string) error { +func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string, wasCSR bool) error { var rootCert string var rootKey string var issuerCert string @@ -471,9 +567,6 @@ func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string) if _, err := os.Stat(rootCert); os.IsNotExist(err) { return errors.New("file does not contain root-ca.pem") } - if _, err := os.Stat(rootKey); os.IsNotExist(err) { - return errors.New("file does not contain root-ca.key") - } } issuerCert = filepath.Join(tmpDir, "ca-int.pem") @@ -487,7 +580,7 @@ func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string) } } if _, err := os.Stat(issuerKey); os.IsNotExist(err) { - if ci.IsRoot { + if ci.IsRoot || wasCSR { issuerKey = "" } else { return errors.New("file does not contain ca-int.key") @@ -509,7 +602,7 @@ func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string) } // Create a new pair of key + certificate files based on the info in CertificateInfo -func (ci *CertificateInfo) Create(path string, certBase string) error { +func (ci *CertificateInfo) Create(path string, certBase string, wasCSR bool) error { if err := preCreateTasks(path); err != nil { return err } @@ -538,13 +631,13 @@ func (ci *CertificateInfo) Create(path string, certBase string) error { } } else if ci.CreateType == "import" { - err := ci.Import(path, certBase, tmpDir, tmpKey, tmpCert) + err := ci.Import(tmpDir, tmpKey, tmpCert) if err != nil { return err } } else if ci.CreateType == "upload" { - err := ci.Upload(path, certBase, tmpKey, tmpCert) + err := ci.Upload(tmpKey, tmpCert) if err != nil { return err } @@ -555,28 +648,37 @@ func (ci *CertificateInfo) Create(path string, certBase string) error { // This is shared between pfx/zip upload and pem text upload if ci.CreateType != "generate" { - err := ci.Extract(path, certBase, tmpDir) + err := ci.Extract(path, certBase, tmpDir, wasCSR) if err != nil { return err } } - if err := postCreateTasks(path, certBase); err != nil { + if err := postCreateTasks(path, certBase, ci.IsRoot); err != nil { return err } if ci.IsRoot { - if _, err := exeCmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil { - return reportError(err) + keyFileExists := true + if _, err := os.Stat(path + certBase + ".key"); os.IsNotExist(err) { + keyFileExists = false + } + if keyFileExists { + // TODO: make option in GUI to trigger crl creation! + if _, err := exeCmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil { + return reportError(err) + } } } return nil } -func postCreateTasks(path string, certBase string) error { - if _, err := exeCmd("openssl pkey -in " + path + certBase + ".key -out " + path + certBase + ".key.der -outform der"); err != nil { - return reportError(err) +func postCreateTasks(path string, certBase string, isRoot bool) error { + if !isRoot { + if _, err := exeCmd("openssl pkey -in " + path + certBase + ".key -out " + path + certBase + ".key.der -outform der"); err != nil { + return reportError(err) + } } if _, err := exeCmd("openssl x509 -in " + path + certBase + ".pem -out " + path + certBase + ".der -outform DER"); err != nil { @@ -586,6 +688,127 @@ func postCreateTasks(path string, certBase string) error { return nil } +func (ci *CertificateInfo) StoreRootKey(path string) bool { + if ci.Errors == nil { + ci.Errors = make(map[string]string) + } + if strings.TrimSpace(ci.Key) == "" { + ci.Errors["Modal"] = "Please provide a PEM-encoded key" + return false + } + + tmpDir, err := os.MkdirTemp("", "labca") + if err != nil { + ci.Errors["Modal"] = err.Error() + return false + } + + 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() + 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 + } + + return true +} + +func (ci *CertificateInfo) StoreCRL(path string) bool { + if ci.Errors == nil { + ci.Errors = make(map[string]string) + } + if strings.TrimSpace(ci.CRL) == "" { + fmt.Println("WARNING: no Root CRL file provided - please upload one from the manage page") + return true + } + + tmpDir, err := os.MkdirTemp("", "labca") + if err != nil { + ci.Errors["Modal"] = err.Error() + return false + } + + defer os.RemoveAll(tmpDir) + + tmpCRL := filepath.Join(tmpDir, "root-ca.crl") + + if err := os.WriteFile(tmpCRL, []byte(ci.CRL), 0644); err != nil { + ci.Errors["Modal"] = err.Error() + return false + } + + crlIssuer, err := exeCmd("openssl crl -noout -issuer -in " + tmpCRL) + if err != nil { + ci.Errors["Modal"] = "Not a CRL file" + return false + } + rootSubj, err := exeCmd("openssl x509 -noout -subject -in " + path + "root-ca.pem") + if err != nil { + ci.Errors["Modal"] = "Cannot get Root CA subject" + return false + } + + if strings.TrimPrefix(string(crlIssuer), "issuer=") != strings.TrimPrefix(string(rootSubj), "subject=") { + ci.Errors["Modal"] = "CRL file does not match the Root CA certificate" + return false + } + + if _, err := exeCmd("mv " + tmpCRL + " " + path); err != nil { + ci.Errors["Modal"] = err.Error() + return false + } + + return true +} + func exeCmd(cmd string) ([]byte, error) { parts := strings.Fields(cmd) for i := 0; i < len(parts); i++ { diff --git a/gui/main.go b/gui/main.go index 7e2c735..2e7493f 100644 --- a/gui/main.go +++ b/gui/main.go @@ -18,6 +18,7 @@ import ( "fmt" "html/template" "io" + "io/ioutil" "log" "math" "math/big" @@ -1542,6 +1543,9 @@ func _buildCI(r *http.Request, session *sessions.Session, isRoot bool) *Certific ci.Initialize() if session.Values["ct"] != nil { + if !isRoot && session.Values["ct"].(string) == "generate" { + ci.IsRootGenerated = true + } ci.CreateType = session.Values["ct"].(string) } if session.Values["kt"] != nil { @@ -1587,6 +1591,49 @@ func issuerNameID(certfile string) (int64, error) { } func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot bool) bool { + if r.Method == "POST" { + if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + + if r.Form.Get("revertroot") != "" { + // From issuer certificate creation page it is possible to remove the root again and start over + exeCmd("rm data/root-ca.key") // Does not necessarily exist + if _, err := exeCmd("rm data/root-ca.pem"); err != nil { + errorHandler(w, r, err, http.StatusInternalServerError) + return false + } + certBase = "root-ca" + isRoot = true + r.Method = "GET" + sess, _ := sessionStore.Get(r, "labca") + sess.Values["ct"] = "generate" + if err := sess.Save(r, w); err != nil { + log.Printf("cannot save session: %s\n", err) + } + } else if r.Form.Get("ack-rootkey") == "yes" { + // Root Key was shown, do we need to keep it online? + if r.Form.Get("keep-root-online") == "true" { + sess, _ := sessionStore.Get(r, "labca") + sess.Values["root-online"] = true + if err := sess.Save(r, w); err != nil { + log.Printf("cannot save session: %s\n", err) + } + } + + // 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) { + exeCmd("mv data/root-ca.pem_TMP data/root-ca.pem") + } + + r.Method = "GET" + return true + } + } + path := "data/" if !isRoot { path = path + "issuer/" @@ -1641,15 +1688,99 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot ci.RequestBase = r.Header.Get("X-Request-Base") if !ci.Validate() { - render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) - return false + if session.Values["csr"] == true { + delete(ci.Errors, "Key") + } else { + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } } - if err := ci.Create(path, certBase); err != nil { - ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = err.Error() - log.Printf("_certCreate: create failed: %v", err) - render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) - return false + wasCSR := session.Values["csr"] == true + if r.Form.Get("ack-rootkey") != "yes" { + if r.Form.Get("rootkey") != "" { + rootci := &CertificateInfo{ + IsRoot: true, + Key: r.Form.Get("rootkey"), + Passphrase: r.Form.Get("rootpassphrase"), + } + if !rootci.StoreRootKey("data/") { + ci.Errors["Modal"] = rootci.Errors["Modal"] + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "GetRootKey": true, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + } + if r.Form.Get("crl") != "" { + rootci := &CertificateInfo{ + IsRoot: true, + CRL: r.Form.Get("crl"), + } + if !rootci.StoreCRL("data/") { + ci.Errors["Modal"] = rootci.Errors["Modal"] + csr, err := os.Open(path + certBase + ".csr") + if err != nil { + ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = "Error reading .csr file! See LabCA logs for details" + log.Printf("_certCreate: read csr: %v", err) + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + defer csr.Close() + b, err := ioutil.ReadAll(csr) + + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "CSR": string(b), "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + } + + if err := ci.Create(path, certBase, wasCSR); err != nil { + if err.Error() == "NO_ROOT_KEY" { + if r.Form.Get("generate") != "" { + if r.Form.Get("rootkey") == "" { + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "GetRootKey": true, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } else { + rootci := &CertificateInfo{ + IsRoot: true, + Key: r.Form.Get("rootkey"), + Passphrase: r.Form.Get("rootpassphrase"), + } + if !rootci.StoreRootKey("data/") { + ci.Errors["Modal"] = rootci.Errors["Modal"] + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "GetRootKey": true, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + } + + if r.Form.Get("getcsr") != "" { + csr, err := os.Open(path + certBase + ".csr") + if err != nil { + ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = "Error reading .csr file! See LabCA logs for details" + log.Printf("_certCreate: read csr: %v", err) + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + defer csr.Close() + b, err := ioutil.ReadAll(csr) + + session.Values["csr"] = true + if err = session.Save(r, w); err != nil { + log.Printf("cannot save session: %s\n", err) + } + + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "CSR": string(b), "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + } else { + ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = err.Error() + log.Printf("_certCreate: create failed: %v", err) + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + } } if !ci.IsRoot { @@ -1660,6 +1791,15 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot } else { log.Printf("_certCreate: could not calculate IssuerNameID: %v", err) } + + if session.Values["root-online"] != true { + if _, err := os.Stat(path + "../root-ca.key"); !os.IsNotExist(err) { + 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 viper.Get("labca.organization") == nil { @@ -1677,6 +1817,21 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot log.Printf("cannot save session: %s\n", err) } + if ci.IsRoot && ci.CreateType == "generate" && r.Form.Get("ack-rootkey") != "yes" { + key, err := os.Open(path + certBase + ".key") + if err != nil { + ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = "Error reading .key file! See LabCA logs for details" + log.Printf("_certCreate: read key: %v", err) + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + defer key.Close() + b, err := ioutil.ReadAll(key) + + render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "RootKey": string(b), "Progress": _progress(certBase), "HelpText": _helptext(certBase)}) + return false + } + // Fake the method to GET as we need to continue in the setupHandler() function r.Method = "GET" } else { @@ -1811,18 +1966,25 @@ func _helptext(stage string) template.HTML { return template.HTML(fmt.Sprint("

This is the top level certificate that will sign the issuer\n", "certificate(s). You can either generate a fresh Root CA (Certificate Authority) or import an\n", "existing one, e.g. a backup from another LabCA instance.

\n", - "

If you want to generate a certificate, pick a key type and strength (the higher the number the\n", + "

If you want to generate a new certificate, pick a key type and strength (the higher the number the\n", "more secure, ECDSA is more modern than RSA), provide at least a country and organization name,\n", "and the common name. It is recommended that the common name contains the word 'Root' as well\n", "as your organization name so you can recognize it, and that's why that is automatically filled\n", - "once you leave the organization field.

")) + "once you leave the organization field.

\n", + "

If you want to upload an existing root certificate, you may choose to keep the private key\n", + "offline for security reasons according to best practices. If you do include it here, we will be able\n", + "to generate an issuing certificate automatically in the next step. If you don't include it, we will\n", + "ask for it when needed.

")) } else if stage == "ca-int" { return template.HTML(fmt.Sprint("

This is what end users will see as the issuing certificate. Again,\n", "you can either generate a fresh certificate or import an existing one, as long as it is signed by\n", "the Root CA from the previous step.

\n", - "

If you want to generate a certificate, by default the same key type and strength is selected as\n", + "

If you want to generate a certificate, by default the same key type and strength is selected as\n", "was chosen in the previous step when generating the root, but you may choose a different\n", - "one. By default the common name is the same as the CN for the Root CA, minus the word 'Root'.

")) + "one. By default the common name is the same as the CN for the Root CA, minus the word 'Root'.

\n", + "

If you are using an offline Root CA certificate then you can download the Certificate Signing\n", + "Request (CSR) here and have it signed by the Root CA. Alternatively we (temporarily) need the\n", + "secret key of the Root CA for generating the issuing certificate.

")) } else if stage == "standalone" { return template.HTML(fmt.Sprint("

Currently only step-ca is supported, using the MySQL database backend.\n", "Please provide the necessary connectiuon details here.")) @@ -2168,7 +2330,9 @@ 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 - os.Remove("data/root-ca.pem") + if _, err := os.Stat("data/root-ca.pem"); !os.IsNotExist(err) { + exeCmd("mv data/root-ca.pem data/root-ca.pem_TMP") + } return } diff --git a/gui/static/css/labca.css b/gui/static/css/labca.css index e555a38..4c11d77 100644 --- a/gui/static/css/labca.css +++ b/gui/static/css/labca.css @@ -124,6 +124,10 @@ td.pad-low-top { width: 10em; } +.btn-right { + float: right; +} + .vmiddle { vertical-align: middle !important; } @@ -140,6 +144,16 @@ td.pad-low-top { border: none; } +.modal-content { + padding: 6px; + width: 614px; +} + +.modal-content > textarea { + width: 600px; + height: 550px; +} + .non-fluid { width: auto !important; } diff --git a/gui/templates/views/cert.tmpl b/gui/templates/views/cert.tmpl index e5ccf9f..8261595 100644 --- a/gui/templates/views/cert.tmpl +++ b/gui/templates/views/cert.tmpl @@ -22,6 +22,10 @@

+ + + +
+ {{ with .Errors.Country }} {{ . }} {{ end }}
- + {{ with .Errors.Organization }} {{ . }} {{ end }}
- + {{ with .Errors.OrgUnit }} {{ . }} {{ end }} @@ -66,7 +70,13 @@ {{ with .Errors.Generate }} {{ . }}
{{ end }} - + + {{ if and (not .IsRoot) (not .IsRootGenerated) }} + + {{ end }} + {{ if not .IsRoot }} + + {{ end }}
@@ -105,12 +115,20 @@
-
+
+ - +
-
-