From 39c27b0de38b04ad8b774bdb1f075d7296795155 Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Mon, 21 Jul 2025 16:36:28 +0700 Subject: [PATCH] added root generation --- .gitignore | 1 + api/RootCert.go | 35 ++++ api/frontend/index.html | 82 +++++++++ api/frontend/js/certs.js | 223 +++++++++++------------ model/CreateRootCertRequest.go | 14 ++ scm.conf | 2 +- server/Configure.go | 3 + utils/CheckOrCreateRootKeyCertificate.go | 50 +++++ utils/CreateRootKeyCertificate.go | 91 +++++++++ 9 files changed, 386 insertions(+), 115 deletions(-) create mode 100644 api/RootCert.go create mode 100644 model/CreateRootCertRequest.go create mode 100644 utils/CheckOrCreateRootKeyCertificate.go create mode 100644 utils/CreateRootKeyCertificate.go diff --git a/.gitignore b/.gitignore index 99adeb7..b23009b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ scm go.sum +certs diff --git a/api/RootCert.go b/api/RootCert.go new file mode 100644 index 0000000..ce031c3 --- /dev/null +++ b/api/RootCert.go @@ -0,0 +1,35 @@ +package api + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "scm/model" +) + +func RootCert(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + _, err := os.Stat(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root/root.crt")) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprintf(w, `{"status":"ok"}`) + case "POST": + var rootCertRequest model.CreateRootCertRequest + err := json.NewDecoder(r.Body).Decode(&rootCertRequest) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + + + fmt.Fprintf(w, `{"status":"ok"}`) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} diff --git a/api/frontend/index.html b/api/frontend/index.html index 0d5cfa6..800d4ba 100644 --- a/api/frontend/index.html +++ b/api/frontend/index.html @@ -305,5 +305,87 @@ + diff --git a/api/frontend/js/certs.js b/api/frontend/js/certs.js index 9ef3cb9..9577de4 100644 --- a/api/frontend/js/certs.js +++ b/api/frontend/js/certs.js @@ -4,132 +4,127 @@ const preview = document.querySelector("#preview"); var certificates = []; const renderCards = () => { - cards.innerHTML = ""; + cards.innerHTML = ""; + if (certificates !== null) { certificates.map((cert) => { - cards.innerHTML += `
-
-
- ${cert.domain} -
-
- -
`; + cards.innerHTML += `
+
+
+ ${cert.domain} +
+
+ +
`; }); + } else { + cards.innerHTML = `

No Certificates Found

`; + } }; const getCerts = () => { - fetch(window.location.origin + "/api/certs") - .then((data) => data.json()) - .then((data) => { - certificates = data; - renderCards(); - }) - .catch((error) => { - console.error("Error fetching certificates:", error); - alert("Failed to fetch certificates"); - }); + fetch(window.location.origin + "/api/certs") + .then((data) => data.json()) + .then((data) => { + certificates = data; + renderCards(); + }) + .catch((error) => { + console.error("Error fetching certificates:", error); + alert("Failed to fetch certificates"); + }); }; getCerts(); if (preview) { - preview.addEventListener("show.bs.modal", (e) => { - const button = e.relatedTarget; - const recipient = button.getAttribute("data-bs-whatever"); - // const modalBodyTextarea = preview.querySelector(".modal-body textarea"); - const dnsnames = button.getAttribute("data-bs-dnsnames"); - const notafter = button.getAttribute("data-bs-notafter"); - const notbefore = button.getAttribute("data-bs-notbefore"); - const issuerorganizationunit = button.getAttribute( - "data-bs-issuer-organizationunit", - ); - const issuerorganization = button.getAttribute( - "data-bs-issuer-organization", - ); - const issuercommonname = button.getAttribute( - "data-bs-issuer-commonname", - ); - const subjectorganizationunit = button.getAttribute( - "data-bs-subject-organizationunit", - ); - const subjectorganization = button.getAttribute( - "data-bs-subject-organization", - ); - const subjectcommonname = button.getAttribute( - "data-bs-subject-commonname", - ); - preview.querySelector("#raw").innerHTML = recipient.replace( - /\n/g, - "
", - ); - preview.querySelector("#dnsNames").value = dnsnames; - preview.querySelector("#notAfter").value = notafter; - preview.querySelector("#notBefore").value = notbefore; - preview.querySelector("#issuerorganizationunit").value = - issuerorganizationunit; - preview.querySelector("#issuerorganization").value = issuerorganization; - preview.querySelector("#issuercommonname").value = issuercommonname; - preview.querySelector("#subjectorganizationunit").value = - subjectorganizationunit; - preview.querySelector("#subjectorganization").value = - subjectorganization; - preview.querySelector("#subjectcommonname").value = subjectcommonname; - }); + preview.addEventListener("show.bs.modal", (e) => { + const button = e.relatedTarget; + const recipient = button.getAttribute("data-bs-whatever"); + // const modalBodyTextarea = preview.querySelector(".modal-body textarea"); + const dnsnames = button.getAttribute("data-bs-dnsnames"); + const notafter = button.getAttribute("data-bs-notafter"); + const notbefore = button.getAttribute("data-bs-notbefore"); + const issuerorganizationunit = button.getAttribute( + "data-bs-issuer-organizationunit", + ); + const issuerorganization = button.getAttribute( + "data-bs-issuer-organization", + ); + const issuercommonname = button.getAttribute("data-bs-issuer-commonname"); + const subjectorganizationunit = button.getAttribute( + "data-bs-subject-organizationunit", + ); + const subjectorganization = button.getAttribute( + "data-bs-subject-organization", + ); + const subjectcommonname = button.getAttribute("data-bs-subject-commonname"); + preview.querySelector("#raw").innerHTML = recipient.replace(/\n/g, "
"); + preview.querySelector("#dnsNames").value = dnsnames; + preview.querySelector("#notAfter").value = notafter; + preview.querySelector("#notBefore").value = notbefore; + preview.querySelector("#issuerorganizationunit").value = + issuerorganizationunit; + preview.querySelector("#issuerorganization").value = issuerorganization; + preview.querySelector("#issuercommonname").value = issuercommonname; + preview.querySelector("#subjectorganizationunit").value = + subjectorganizationunit; + preview.querySelector("#subjectorganization").value = subjectorganization; + preview.querySelector("#subjectcommonname").value = subjectcommonname; + }); } cards.addEventListener("click", (e) => { - if (e.target.classList.contains("delcert")) { - if (confirm("Are you sure you want to delete this certificate?")) { - fetch(window.location.origin + `/api/certs/delete`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - domain: certificates.find((item) => item.id === e.target.id) - .domain, - }), - }) - .then((data) => data.json()) - .then((jsondata) => { - if (jsondata.status === "success") { - getCerts(); - alert("Certificate deleted successfully"); - } else { - alert("Failed to delete certificate"); - } - }) - .catch((error) => { - console.error(error); - alert("An error occurred while deleting the certificate"); - }); - } + if (e.target.classList.contains("delcert")) { + if (confirm("Are you sure you want to delete this certificate?")) { + fetch(window.location.origin + `/api/certs/delete`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + domain: certificates.find((item) => item.id === e.target.id).domain, + }), + }) + .then((data) => data.json()) + .then((jsondata) => { + if (jsondata.status === "success") { + getCerts(); + alert("Certificate deleted successfully"); + } else { + alert("Failed to delete certificate"); + } + }) + .catch((error) => { + console.error(error); + alert("An error occurred while deleting the certificate"); + }); } + } }); diff --git a/model/CreateRootCertRequest.go b/model/CreateRootCertRequest.go new file mode 100644 index 0000000..a32fb04 --- /dev/null +++ b/model/CreateRootCertRequest.go @@ -0,0 +1,14 @@ +package model + +type CreateRootCertRequest struct { + CommonName string `json:"CommonName"` + Organization string `json:"Organization"` + OrganizationalUnit string `json:"OrganizationalUnit"` + Country string `json:"Country"` + Locality string `json:"Locality"` + Province string `json:"Province"` + StreetAddress string `json:"StreetAddress"` + PostalCode string `json:"PostalCode"` + ValidityYears int `json:"ValidityYears"` + KeyPassword string `json:"KeyPassword"` +} diff --git a/scm.conf b/scm.conf index 05525d4..7077c7b 100644 --- a/scm.conf +++ b/scm.conf @@ -2,7 +2,7 @@ ENABLE_TLS=true CERT_FILE=/home/tola/certs/localhost/localhost.crt KEY_FILE=/home/tola/certs/localhost/localhost.key -CERTS_PATH=/home/tola/certs +CERTS_PATH=/home/tola/Documents/golang/scm/certs SERVER_ADDRESS=0.0.0.0 SERVER_PORT=8777 diff --git a/server/Configure.go b/server/Configure.go index a7ea53e..640785f 100644 --- a/server/Configure.go +++ b/server/Configure.go @@ -3,11 +3,14 @@ package server import ( "net/http" "scm/api" + "scm/utils" ) func Configure() { http.Handle("/", api.SpaHandler(http.FS(api.Frontend))) http.HandleFunc("/api/certs", api.Certs) + http.HandleFunc("/api/certs/root", api.RootCert) http.HandleFunc("/api/certs/create", api.CreateCert) http.HandleFunc("/api/certs/delete", api.DeleteCert) + utils.CheckOrCreateRootKeyCertificate() } diff --git a/utils/CheckOrCreateRootKeyCertificate.go b/utils/CheckOrCreateRootKeyCertificate.go new file mode 100644 index 0000000..49827b1 --- /dev/null +++ b/utils/CheckOrCreateRootKeyCertificate.go @@ -0,0 +1,50 @@ +package utils + +import ( + "fmt" + "os" +) + +func CheckOrCreateRootKeyCertificate() error { + _, err := os.Stat(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root")) + if err == nil { + _, err := os.Stat(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root/root.key")) + if err == nil { + _, err := os.Stat(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root/root.crt")) + if err == nil { + return fmt.Errorf("%s", "key already exists") + } else if os.IsNotExist(err) { + os.RemoveAll(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root")) + err = CreateRootKeyCertificate() + if err != nil { + fmt.Println("error creating root key certificate:", err) + return fmt.Errorf("%s", "error creating root key certificate") + } + } else { + fmt.Println("error checking certificate directory:", err) + return fmt.Errorf("%s", "error checking certificate directory") + } + } else if os.IsNotExist(err) { + os.RemoveAll(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root")) + err = CreateRootKeyCertificate() + if err != nil { + fmt.Println("error creating root key certificate:", err) + return fmt.Errorf("%s", "error creating root key certificate") + } + } else { + fmt.Println("error checking certificate directory:", err) + return fmt.Errorf("%s", "error checking certificate directory") + } + } else if os.IsNotExist(err) { + os.RemoveAll(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), "root")) + err = CreateRootKeyCertificate() + if err != nil { + fmt.Println("error creating root key certificate:", err) + return fmt.Errorf("%s", "error creating root key certificate") + } + } else { + fmt.Println("error checking certificate directory:", err) + return fmt.Errorf("%s", "error checking certificate directory") + } + return nil +} diff --git a/utils/CreateRootKeyCertificate.go b/utils/CreateRootKeyCertificate.go new file mode 100644 index 0000000..201419a --- /dev/null +++ b/utils/CreateRootKeyCertificate.go @@ -0,0 +1,91 @@ +package utils + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "os" + "time" + + "github.com/youmark/pkcs8" +) + +func CreateRootKeyCertificate() error { + privkey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return err + } + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "Root Certificate", + OrganizationalUnit: []string{"Organizational Unit"}, + Organization: []string{"Organization"}, + Country: []string{"Country"}, + Locality: []string{"Locality"}, + Province: []string{"Province"}, + StreetAddress: []string{"Street Address"}, + PostalCode: []string{"Postal Code"}, + SerialNumber: serialNumber.String(), + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + BasicConstraintsValid: true, + IsCA: true, + } + + cert, err := x509.CreateCertificate(rand.Reader, &template, &template, &privkey.PublicKey, privkey) + if err != nil { + return err + } + + encryptedKey, err := pkcs8.ConvertPrivateKeyToPKCS8( + privkey, + []byte("Simba28082020"), + ) + if err != nil { + return err + } + + err = CheckExistsOrCreateDir("root") + if err != nil { + return err + } + + err = SavePEMFile(fmt.Sprintf( + "%s/%s", + os.Getenv("CERTS_PATH"), + "root/root.crt", + ), + "CERTIFICATE", + cert, + ) + if err != nil { + return err + } + + err = SavePEMFile(fmt.Sprintf( + "%s/%s", + os.Getenv("CERTS_PATH"), + "root/root.key", + ), "ENCRYPTED PRIVATE KEY", encryptedKey) + if err != nil { + return err + } + // Implementation of CreateRootCertificate function + return nil +}