From 3b7835210913aea74e1383fcfa319682a584a60e Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Wed, 16 Jul 2025 14:12:27 +0700 Subject: [PATCH] more refactor --- api/{certs.go => Certs.go} | 6 +- api/CreateCert.go | 125 ++++++++++++ api/{delete_cert.go => DeleteCert.go} | 4 + api/{spa.go => Spa.go} | 4 + api/create_cert.go | 181 ------------------ main.go | 40 +--- .../{create_cert.go => CreateCertRequest.go} | 2 +- .../{delete_cert.go => DeleteCertRequest.go} | 0 server/Configure.go | 13 ++ server/Run.go | 29 +++ utils/CheckExistsOrCreateDir.go | 24 +++ utils/CreateServerCert.go | 42 ++++ utils/LoadEnv.go | 14 ++ utils/LoadRootCertAndKey.go | 55 ++++++ utils/{parse_cert.go => ParseCert.go} | 2 +- utils/SavePEMFile.go | 23 +++ 16 files changed, 345 insertions(+), 219 deletions(-) rename api/{certs.go => Certs.go} (86%) create mode 100644 api/CreateCert.go rename api/{delete_cert.go => DeleteCert.go} (89%) rename api/{spa.go => Spa.go} (81%) delete mode 100644 api/create_cert.go rename model/{create_cert.go => CreateCertRequest.go} (86%) rename model/{delete_cert.go => DeleteCertRequest.go} (100%) create mode 100644 server/Configure.go create mode 100644 server/Run.go create mode 100644 utils/CheckExistsOrCreateDir.go create mode 100644 utils/CreateServerCert.go create mode 100644 utils/LoadEnv.go create mode 100644 utils/LoadRootCertAndKey.go rename utils/{parse_cert.go => ParseCert.go} (94%) create mode 100644 utils/SavePEMFile.go diff --git a/api/certs.go b/api/Certs.go similarity index 86% rename from api/certs.go rename to api/Certs.go index fadc52b..a8f9b19 100644 --- a/api/certs.go +++ b/api/Certs.go @@ -11,6 +11,10 @@ import ( ) func Certs(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } var certs_info []map[string]any entires, err := os.ReadDir(os.Getenv("CERTS_PATH")) if err != nil { @@ -43,7 +47,7 @@ func Certs(w http.ResponseWriter, r *http.Request) { new_cert["href"] = fmt.Sprintf("https://%s", entry.Name()) new_cert["cert"] = string(certdata) new_cert["key"] = string(keydata) - new_cert["certinfo"] = utils.ParseCertificate(fmt.Sprintf("%s/%s/%s.crt", os.Getenv("CERTS_PATH"), entry.Name(), entry.Name())) + new_cert["certinfo"] = utils.ParseCert(fmt.Sprintf("%s/%s/%s.crt", os.Getenv("CERTS_PATH"), entry.Name(), entry.Name())) certs_info = append(certs_info, new_cert) } } diff --git a/api/CreateCert.go b/api/CreateCert.go new file mode 100644 index 0000000..c7e23eb --- /dev/null +++ b/api/CreateCert.go @@ -0,0 +1,125 @@ +package api + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "fmt" + "net/http" + "os" + "scm/model" + "scm/utils" +) + +func CreateCert(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + var requestData model.CreateCertRequest + if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { + http.Error(w, "invalid JSON format", http.StatusBadRequest) + return + } + caCert, caKey, err := utils.LoadRootCertAndKey( + fmt.Sprintf( + "%s/%s/%s.crt", + os.Getenv("CERTS_PATH"), + "root", + "TolaMironcenkoCA", + ), + fmt.Sprintf( + "%s/%s/%s.key", + os.Getenv("CERTS_PATH"), + "root", + "TolaMironcenkoCA", + ), + requestData.CAKeyPassword, + ) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + fmt.Println("error loading certificate and key:", err) + return + } + // TODO: Implement create certificate endpoint + serverKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + fmt.Println("error generating server key:", err) + return + } + serverCert, err := utils.CreateServerCert(caCert, caKey, serverKey, requestData) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + fmt.Println("error creating server certificate:", err) + return + } + err = utils.CheckExistsOrCreateDir(requestData.DNSNames[0]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + err = utils.SavePEMFile( + fmt.Sprintf( + "%s/%s/%s.crt", + os.Getenv("CERTS_PATH"), + requestData.DNSNames[0], + requestData.DNSNames[0], + ), + "CERTIFICATE", + serverCert, + ) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + fmt.Println("error saving certificate:", err) + return + } + err = utils.SavePEMFile( + fmt.Sprintf( + "%s/%s/%s.key", + os.Getenv("CERTS_PATH"), + requestData.DNSNames[0], + requestData.DNSNames[0], + ), + "RSA PRIVATE KEY", + x509.MarshalPKCS1PrivateKey(serverKey), + ) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + fmt.Println("error saving private key:", err) + return + } + fmt.Fprintf(w, "%s", `{"status": "success", "message": "Certificate created successfully"}`) +} + +// func createServerCert( +// caCert *x509.Certificate, +// caKey *rsa.PrivateKey, +// serverKey *rsa.PrivateKey, +// certdata model.CreateCertificateRequest, +// ) ([]byte, error) { +// // Шаблон серверного сертификата +// template := x509.Certificate{ +// SerialNumber: big.NewInt(time.Now().Unix()), +// Subject: pkix.Name{ +// Organization: certdata.OrganizationName, +// CommonName: certdata.CommonName, +// OrganizationalUnit: certdata.OrganizationUnit, +// }, +// NotBefore: time.Now(), +// NotAfter: time.Now().AddDate(1, 0, 0), // 1 год +// KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, +// ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, +// DNSNames: certdata.DNSNames, +// } + +// // Создаем сертификат, подписанный CA +// return x509.CreateCertificate( +// rand.Reader, +// &template, +// caCert, +// &serverKey.PublicKey, +// caKey, +// ) +// } diff --git a/api/delete_cert.go b/api/DeleteCert.go similarity index 89% rename from api/delete_cert.go rename to api/DeleteCert.go index e2b926a..4c7fe1a 100644 --- a/api/delete_cert.go +++ b/api/DeleteCert.go @@ -9,6 +9,10 @@ import ( ) func DeleteCert(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } var requestData model.DeleteCertRequest if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) diff --git a/api/spa.go b/api/Spa.go similarity index 81% rename from api/spa.go rename to api/Spa.go index c75186a..ee6db76 100644 --- a/api/spa.go +++ b/api/Spa.go @@ -13,6 +13,10 @@ var Frontend embed.FS func SpaHandler(fs http.FileSystem) http.Handler { fileServer := http.FileServer(fs) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } fmt.Println("/frontend" + r.URL.Path) r.URL.Path = "/frontend" + path.Clean(r.URL.Path) if _, err := fs.Open(path.Clean(r.URL.Path)); err != nil { diff --git a/api/create_cert.go b/api/create_cert.go deleted file mode 100644 index 07b3189..0000000 --- a/api/create_cert.go +++ /dev/null @@ -1,181 +0,0 @@ -package api - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/json" - "encoding/pem" - "errors" - "fmt" - "math/big" - "net/http" - "os" - "scm/model" - "time" - - "github.com/youmark/pkcs8" -) - -func CreateCert(w http.ResponseWriter, r *http.Request) { - var requestData model.CreateCertificateRequest - if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { - http.Error(w, "invalid JSON format", http.StatusBadRequest) - return - } - caCert, caKey, err := loadCertAndKey( - fmt.Sprintf( - "%s/%s/%s.crt", - os.Getenv("CERTS_PATH"), - "root", - "TolaMironcenkoCA", - ), - fmt.Sprintf( - "%s/%s/%s.key", - os.Getenv("CERTS_PATH"), - "root", - "TolaMironcenkoCA", - ), - requestData.CAKeyPassword, - ) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error loading certificate and key:", err) - return - } - // TODO: Implement create certificate endpoint - serverKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error generating server key:", err) - return - } - serverCert, err := createServerCert(caCert, caKey, serverKey, requestData) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error creating server certificate:", err) - return - } - _, err = os.Stat(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), requestData.DNSNames[0])) - if err == nil { - http.Error(w, "certificate already exists", http.StatusConflict) - fmt.Println("certificate already exists") - return - } else if os.IsNotExist(err) { - err := os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), requestData.DNSNames[0]), 0755) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error creating directory:", err) - return - } - } else { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error checking certificate directory:", err) - return - } - err = savePEM(fmt.Sprintf("%s/%s/%s.crt", os.Getenv("CERTS_PATH"), requestData.DNSNames[0], requestData.DNSNames[0]), "CERTIFICATE", serverCert) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error saving certificate:", err) - return - } - err = savePEM(fmt.Sprintf("%s/%s/%s.key", os.Getenv("CERTS_PATH"), requestData.DNSNames[0], requestData.DNSNames[0]), "RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(serverKey)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - fmt.Println("error saving private key:", err) - return - } - fmt.Fprintf(w, "%s", `{"status": "success", "message": "Certificate created successfully"}`) -} - -func createServerCert( - caCert *x509.Certificate, - caKey *rsa.PrivateKey, - serverKey *rsa.PrivateKey, - certdata model.CreateCertificateRequest, -) ([]byte, error) { - // Шаблон серверного сертификата - template := x509.Certificate{ - SerialNumber: big.NewInt(time.Now().Unix()), - Subject: pkix.Name{ - Organization: certdata.OrganizationName, - CommonName: certdata.CommonName, - OrganizationalUnit: certdata.OrganizationUnit, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(1, 0, 0), // 1 год - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - DNSNames: certdata.DNSNames, - } - - // Создаем сертификат, подписанный CA - return x509.CreateCertificate( - rand.Reader, - &template, - caCert, - &serverKey.PublicKey, - caKey, - ) -} - -func loadCertAndKey(certFile, keyFile, password string) (*x509.Certificate, *rsa.PrivateKey, error) { - fmt.Println(certFile, keyFile) - // Загрузка корневого сертификата - caCertPEM, err := os.ReadFile(certFile) - if err != nil { - return nil, nil, err - } - - block, _ := pem.Decode(caCertPEM) - if block == nil { - return nil, nil, errors.New("failed to parse CA certificate PEM") - } - - caCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, nil, err - } - - // Загрузка зашифрованного приватного ключа - keyPEM, err := os.ReadFile(keyFile) - if err != nil { - return nil, nil, err - } - - block, _ = pem.Decode(keyPEM) - if block == nil { - return nil, nil, errors.New("failed to parse CA key PEM") - } - fmt.Print(block.Type) - // Декодирование ключа с паролем - decryptedKey, err := pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte(password)) - if err != nil { - return nil, nil, fmt.Errorf("failed to decrypt CA key: %v", err) - } - - caKey, ok := decryptedKey.(*rsa.PrivateKey) - if !ok { - return nil, nil, err - } - - return caCert, caKey, nil -} - -func savePEM(filename, blockType string, data []byte) error { - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - - err = pem.Encode(file, &pem.Block{ - Type: blockType, - Bytes: data, - }) - if err != nil { - return err - } - return nil -} diff --git a/main.go b/main.go index 40ab0d4..fefadfb 100644 --- a/main.go +++ b/main.go @@ -1,42 +1,12 @@ package main import ( - "log" - "net/http" - "os" - "scm/api" - - "github.com/joho/godotenv" + "scm/server" + "scm/utils" ) func main() { - err := godotenv.Load("./scm.conf") - if err != nil { - log.Fatal("Error loading .env file") - } - - http.Handle("/", api.SpaHandler(http.FS(api.Frontend))) - http.HandleFunc("/api/certs", api.Certs) - http.HandleFunc("/api/certs/create", api.CreateCert) - http.HandleFunc("/api/certs/delete", api.DeleteCert) - - if os.Getenv("ENABLE_TLS") == "true" { - e := http.ListenAndServeTLS( - os.Getenv("SERVER_ADDRESS")+":"+os.Getenv("SERVER_PORT"), - os.Getenv("CERT_FILE"), - os.Getenv("KEY_FILE"), - nil, - ) - if e != nil { - log.Fatal(e) - } - } else { - e := http.ListenAndServe( - os.Getenv("SERVER_ADDRESS")+":"+os.Getenv("SERVER_PORT"), - nil, - ) - if e != nil { - log.Fatal(e) - } - } + utils.LoadEnv() + server.Configure() + server.Run() } diff --git a/model/create_cert.go b/model/CreateCertRequest.go similarity index 86% rename from model/create_cert.go rename to model/CreateCertRequest.go index a4b9ae8..45f459f 100644 --- a/model/create_cert.go +++ b/model/CreateCertRequest.go @@ -1,6 +1,6 @@ package model -type CreateCertificateRequest struct { +type CreateCertRequest struct { CommonName string `json:"commonname"` OrganizationName []string `json:"organizationname"` OrganizationUnit []string `json:"organizationunit"` diff --git a/model/delete_cert.go b/model/DeleteCertRequest.go similarity index 100% rename from model/delete_cert.go rename to model/DeleteCertRequest.go diff --git a/server/Configure.go b/server/Configure.go new file mode 100644 index 0000000..a7ea53e --- /dev/null +++ b/server/Configure.go @@ -0,0 +1,13 @@ +package server + +import ( + "net/http" + "scm/api" +) + +func Configure() { + http.Handle("/", api.SpaHandler(http.FS(api.Frontend))) + http.HandleFunc("/api/certs", api.Certs) + http.HandleFunc("/api/certs/create", api.CreateCert) + http.HandleFunc("/api/certs/delete", api.DeleteCert) +} diff --git a/server/Run.go b/server/Run.go new file mode 100644 index 0000000..b6e8678 --- /dev/null +++ b/server/Run.go @@ -0,0 +1,29 @@ +package server + +import ( + "log" + "net/http" + "os" +) + +func Run() { + if os.Getenv("ENABLE_TLS") == "true" { + err := http.ListenAndServeTLS( + os.Getenv("SERVER_ADDRESS")+":"+os.Getenv("SERVER_PORT"), + os.Getenv("CERT_FILE"), + os.Getenv("KEY_FILE"), + nil, + ) + if err != nil { + log.Fatal(err) + } + } else { + err := http.ListenAndServe( + os.Getenv("SERVER_ADDRESS")+":"+os.Getenv("SERVER_PORT"), + nil, + ) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/utils/CheckExistsOrCreateDir.go b/utils/CheckExistsOrCreateDir.go new file mode 100644 index 0000000..d649b9a --- /dev/null +++ b/utils/CheckExistsOrCreateDir.go @@ -0,0 +1,24 @@ +package utils + +import ( + "fmt" + "os" +) + +func CheckExistsOrCreateDir(dnsname string) error { + _, err := os.Stat(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), dnsname)) + if err == nil { + fmt.Println("certificate already exists") + return fmt.Errorf("%s", "certificate already exists") + } else if os.IsNotExist(err) { + err := os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("CERTS_PATH"), dnsname), 0755) + if err != nil { + fmt.Println("error creating directory:", err) + return fmt.Errorf("%s", "error creating directory") + } + } else { + fmt.Println("error checking certificate directory:", err) + return fmt.Errorf("%s", "error checking certificate directory") + } + return nil +} diff --git a/utils/CreateServerCert.go b/utils/CreateServerCert.go new file mode 100644 index 0000000..ef2cbf9 --- /dev/null +++ b/utils/CreateServerCert.go @@ -0,0 +1,42 @@ +package utils + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "scm/model" + "time" +) + +func CreateServerCert( + caCert *x509.Certificate, + caKey *rsa.PrivateKey, + serverKey *rsa.PrivateKey, + certdata model.CreateCertRequest, +) ([]byte, error) { + // Шаблон серверного сертификата + template := x509.Certificate{ + SerialNumber: big.NewInt(time.Now().Unix()), + Subject: pkix.Name{ + Organization: certdata.OrganizationName, + CommonName: certdata.CommonName, + OrganizationalUnit: certdata.OrganizationUnit, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), // 1 год + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: certdata.DNSNames, + } + + // Создаем сертификат, подписанный CA + return x509.CreateCertificate( + rand.Reader, + &template, + caCert, + &serverKey.PublicKey, + caKey, + ) +} diff --git a/utils/LoadEnv.go b/utils/LoadEnv.go new file mode 100644 index 0000000..7361c90 --- /dev/null +++ b/utils/LoadEnv.go @@ -0,0 +1,14 @@ +package utils + +import ( + "log" + + "github.com/joho/godotenv" +) + +func LoadEnv() { + err := godotenv.Load("./scm.conf") + if err != nil { + log.Fatal("Error loading .env file") + } +} diff --git a/utils/LoadRootCertAndKey.go b/utils/LoadRootCertAndKey.go new file mode 100644 index 0000000..22ef1ca --- /dev/null +++ b/utils/LoadRootCertAndKey.go @@ -0,0 +1,55 @@ +package utils + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "os" + + "github.com/youmark/pkcs8" +) + +func LoadRootCertAndKey(certFile, keyFile, password string) (*x509.Certificate, *rsa.PrivateKey, error) { + fmt.Println(certFile, keyFile) + // Загрузка корневого сертификата + caCertPEM, err := os.ReadFile(certFile) + if err != nil { + return nil, nil, err + } + + block, _ := pem.Decode(caCertPEM) + if block == nil { + return nil, nil, errors.New("failed to parse CA certificate PEM") + } + + caCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, nil, err + } + + // Загрузка зашифрованного приватного ключа + keyPEM, err := os.ReadFile(keyFile) + if err != nil { + return nil, nil, err + } + + block, _ = pem.Decode(keyPEM) + if block == nil { + return nil, nil, errors.New("failed to parse CA key PEM") + } + fmt.Print(block.Type) + // Декодирование ключа с паролем + decryptedKey, err := pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte(password)) + if err != nil { + return nil, nil, fmt.Errorf("failed to decrypt CA key: %v", err) + } + + caKey, ok := decryptedKey.(*rsa.PrivateKey) + if !ok { + return nil, nil, err + } + + return caCert, caKey, nil +} diff --git a/utils/parse_cert.go b/utils/ParseCert.go similarity index 94% rename from utils/parse_cert.go rename to utils/ParseCert.go index 910bace..64d7eda 100644 --- a/utils/parse_cert.go +++ b/utils/ParseCert.go @@ -7,7 +7,7 @@ import ( "os" ) -func ParseCertificate(certPath string) map[string]any { +func ParseCert(certPath string) map[string]any { // Чтение PEM-файла pemData, err := os.ReadFile(certPath) if err != nil { diff --git a/utils/SavePEMFile.go b/utils/SavePEMFile.go new file mode 100644 index 0000000..957f76e --- /dev/null +++ b/utils/SavePEMFile.go @@ -0,0 +1,23 @@ +package utils + +import ( + "encoding/pem" + "os" +) + +func SavePEMFile(filename, blockType string, data []byte) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + err = pem.Encode(file, &pem.Block{ + Type: blockType, + Bytes: data, + }) + if err != nil { + return err + } + return nil +}