From 9049c67cf2166849ef4eedb23f4ac70795139e12 Mon Sep 17 00:00:00 2001 From: DaanSelen Date: Tue, 6 Jan 2026 16:07:49 +0100 Subject: [PATCH] feat: new features - version 1.0.0 --- generator.go | 4 +- main.go | 216 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 160 insertions(+), 60 deletions(-) diff --git a/generator.go b/generator.go index 6f80cdb..bdba9ca 100644 --- a/generator.go +++ b/generator.go @@ -30,8 +30,8 @@ func generatePKCS12(pKey any, cert *x509.Certificate, caCerts []*x509.Certificat pfxData, err := pkcs12.Modern.Encode(pKey, cert, caCerts, pfxPass) if err != nil { - return "Failed to create PFX with given data.", nil, err + return "", nil, err } else { - return "PKCS generated succesfully, password: " + pfxPass, pfxData, nil + return pfxPass, pfxData, nil } } diff --git a/main.go b/main.go index 3027906..c08b8ea 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "image/color" "log" "os" + "path/filepath" certpair "pkcs-generator/modules/certpairs" "slices" @@ -21,7 +22,7 @@ import ( ) var ( - windowSize fyne.Size = fyne.NewSize(700, 500) + windowSize fyne.Size = fyne.NewSize(700, 600) keyTextFilter []string = []string{".key", ".txt", ".pem"} crtTextFilter []string = []string{".crt", ".cer", ".txt", ".pem"} @@ -31,24 +32,44 @@ func main() { a := app.NewWithID("nl.systemec.pkcs-generator") a.Settings().SetTheme(theme.DefaultTheme()) - w := a.NewWindow("Systemec PKCS-Generator") + w := a.NewWindow("PKCS-Generator") w.Resize(windowSize) - var keyPath, certPath string + var keyPath, certPath, caIssuer string + var writePassDown, overwritePassDown bool + + // Needs to be rendered early! + // Certificate Intermediate selector + // Basic in form but can easily be expanded. + var caChoices = []string{"Sectigo Public Server Authentication CA DV R36", "Sectigo RSA Domain Validation Secure Server CA"} + var sectigo2025 bool + caRadio := widget.NewRadioGroup(caChoices, func(selected string) { + switch selected { + case "Sectigo Public Server Authentication CA DV R36": + log.Println("Sectigo Public Server Authentication CA DV R36") + sectigo2025 = true + case "Sectigo RSA Domain Validation Secure Server CA": + log.Println("Sectigo RSA Domain Validation Secure Server CA") + sectigo2025 = false + default: //Fallback + sectigo2025 = true + } + }) // Labels to show selected filenames fileLabel1 := widget.NewLabel("No file selected") fileLabel2 := widget.NewLabel("No file selected") - radioLabel1 := widget.NewLabel("Select which Sectigo Intermediate") + radioLabel1 := widget.NewLabel("If needed you can override the selection.\nOtherwise let the application decide.") // Certificate Keyfile keyBtn := widget.NewButton("Upload Private Key File", func() { keyDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) { + defer r.Close() + if r != nil { fileLabel1.SetText(r.URI().Name()) keyPath = r.URI().Path() - r.Close() } }, w) keyFilter := storage.NewExtensionFileFilter(keyTextFilter) @@ -58,14 +79,34 @@ func main() { keyDiag.Show() }) + keyWide := container.NewGridWrap( + fyne.NewSize(400, 50), + keyBtn, + ) + + // Issuer entry + issuerText := widget.NewLabel("Certificate issuer will appear here...") + issuerText.Selectable = true + // Certificate file certBtn := widget.NewButton("Upload Certificate File", func() { // Use NewFileOpen to get the dialog object certDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) { + defer r.Close() + if r != nil { fileLabel2.SetText(r.URI().Name()) certPath = r.URI().Path() - r.Close() + + certData := readFile(certPath) + certObj := parseX509(certData) + caIssuer = certObj.Issuer.CommonName + log.Println("Issuer:", caIssuer) + issuerText.SetText(caIssuer) + + if slices.Contains(caChoices, caIssuer) { + caRadio.SetSelected(caIssuer) + } } }, w) certFilter := storage.NewExtensionFileFilter(crtTextFilter) @@ -76,31 +117,39 @@ func main() { certDiag.Show() }) - // Certificate Intermediate selector - // Basic in form but can easily be expanded. - var sectigo2025 bool - caRadio := widget.NewRadioGroup([]string{"New Sectigo (2025-03-22+)", "Old Sectigo (2025-03-22-)"}, func(selected string) { - switch selected { - case "New Sectigo (2025-03-22+)": - sectigo2025 = true - case "Old Sectigo (2025-03-22-)": - sectigo2025 = false - default: //Fallback - sectigo2025 = true + certWide := container.NewGridWrap( + fyne.NewSize(400, 50), + certBtn, + ) + + // Render the overwrite checkfox first since I need its variable/object to disable/enable in the next checkbox (which is visually above this one) + overwriteCheckBox := widget.NewCheck("Overwrite possible existing 'pkcs_password' file", func(checked bool) { + if checked { + log.Println("Checked box: overwrite if file exists.") + overwritePassDown = true + } else { + log.Println("Unchecked box: not overwriting if file exists.") + overwritePassDown = false } }) - caRadio.SetSelected("New Sectigo (2025+)") // default + // Disable the checkbox by default since its reliant on the writeCheckBox + overwriteCheckBox.Disable() - // Multiline entry - textLabl := widget.NewLabel("Status will appear here...") - textLabl.Selectable = true + writeCheckBox := widget.NewCheck("Save password to file", func(checked bool) { + if checked { + log.Println("Checked box: saving to file.") + writePassDown = true + overwriteCheckBox.Enable() + } else { + log.Println("Unchecked box: not saving to file") + writePassDown = false + overwriteCheckBox.Disable() + } + }) - border := canvas.NewRectangle(color.Transparent) - border.StrokeColor = color.RGBA{255, 255, 255, 255} - border.StrokeWidth = 2 - border.CornerRadius = 5 - - statusBox := container.NewStack(border, textLabl) + // Status label, where pfx pass will appear + statusText := widget.NewLabel("Status will appear here...") + statusText.Selectable = true actionBtn := widget.NewButton("Generate", func() { files := []string{keyPath, certPath} @@ -116,83 +165,127 @@ func main() { certPair = certpair.SectigoOldChain } + // Check if one of the filepaths is empty allPresent := true if slices.Contains(files, "") { allPresent = false } + // Check if all needed file/data is present if !allPresent { - textLabl.SetText("One or more files missing!") + statusText.SetText("One or more files missing!") log.Println("One or more files missing!") return } - respText, pfxData := integrityCheckAndGo(keyPath, certPath, certPair[1], certPair[0]) + // Generate the PKCS file with the given data + pfxPass, pfxData := integrityCheckAndGo(keyPath, certPath, certPair[1], certPair[0]) - if pfxData == nil { - log.Println(respText) - textLabl.SetText(respText) - return + // Check if the data returned is OK + if len(pfxData) == 0 || pfxData == nil { + log.Println("Something went wrong while creating the PKCS file...") + statusText.SetText("Something went wrong while creating the PKCS file...") } - // Show Save File dialog immediately + // Most important dialog next, saving the PKCS somewhere + // We also need to declare some variables because otherwise theyd become out of scope + var desiredPath string + var defaultName string = "certificate_store.pfx" svDialog := dialog.NewFileSave( func(writer fyne.URIWriteCloser, err error) { + defer writer.Close() + + desiredPath = writer.URI().Path() + desiredParentPath := filepath.Dir(desiredPath) + if err != nil { dialog.ShowError(err, w) - return } if writer == nil { // User cancelled - return } - _, writeErr := writer.Write(pfxData) - if writeErr != nil { - dialog.ShowError(writeErr, w) - return + _, err = writer.Write(pfxData) + if err != nil { + dialog.ShowError(err, w) } - writer.Close() + var dnText string = "PKCS file saved to: " + desiredPath + "\nThe password for the generated pkcs file is:\n\n" + pfxPass - var dnText string = "\nPKCS file saved to: " + writer.URI().Path() + "\n\n" + respText + "\n" + // Write down the PKCS password on the filesystem + if writePassDown { + wholePath := desiredParentPath + "/pkcs_password" + var alreadyExists bool - textLabl.SetText(dnText) + if _, err := os.Stat(wholePath); err == nil { + alreadyExists = true + } else if os.IsNotExist(err) { + alreadyExists = false + } + + if alreadyExists && !overwritePassDown { + log.Println("File already exists and overwrite checkbox is unchecked.") + dnText += "\n\nCAREFUL! PKCS password was NOT written down. File exists! NOT OVERWRITING!" + } else { + log.Println("Writing PKCS password to: " + wholePath) + err := os.WriteFile(wholePath, []byte(pfxPass), 0644) + if err != nil { + log.Println("Error writing file:", err) + } + } + } + + statusText.SetText(dnText) }, w) svDialog.Resize(windowSize) - svDialog.SetFileName("certificate_store.pfx") + svDialog.SetFileName(defaultName) svDialog.SetFilter(storage.NewExtensionFileFilter([]string{".pfx"})) svDialog.Show() }) + actionWide := container.NewGridWrap( + fyne.NewSize(200, 50), + actionBtn, + ) + cancelBtn := widget.NewButton("Exit", func() { log.Println("Quitting...") - os.Exit(0) + a.Quit() }) + exitWide := container.NewGridWrap( + fyne.NewSize(200, 50), + cancelBtn, + ) + // CREATE LAYOUTS bottom := container.NewHBox( - actionBtn, // left + actionWide, // left layout.NewSpacer(), // flexible space - cancelBtn, // right + exitWide, // right ) centerContent := container.NewVBox( - widget.NewLabel("Select relevant files."), - container.New(layout.NewGridLayout(2), keyBtn, fileLabel1), - container.New(layout.NewGridLayout(2), certBtn, fileLabel2), - widget.NewLabel("\n"), + container.New(layout.NewGridLayout(2), keyWide, fileLabel1), + container.New(layout.NewGridLayout(2), certWide, fileLabel2), + widget.NewLabel(""), + issuerText, + canvas.NewLine(color.Gray{Y: 128}), + widget.NewLabel(""), container.New(layout.NewGridLayout(2), radioLabel1, caRadio), - layout.NewSpacer(), // optional flexible space - statusBox, // Add the referenced text container + layout.NewSpacer(), // optional flexible space + writeCheckBox, + overwriteCheckBox, + canvas.NewLine(color.Gray{Y: 128}), + statusText, // Add the referenced text container widget.NewLabel(""), // Add empty line for space ) content := container.NewBorder( - nil, // top + widget.NewLabel("Select relevant files."), // top bottom, // bottom nil, nil, // left, right centerContent, @@ -253,9 +346,16 @@ func integrityCheckAndGo(keyPath, certPath, caRootString, caCertString string) ( keyData := readFile(keyPath) // Read the private key file (PEM/DER) certData := readFile(certPath) // Read the certificate file (PEM/DER) + log.Println("Loading key") key := parsePrivateKey(keyData) // Convert bytes to Go private key object - cert := parseX509(certData) // Convert bytes to Go x509.Certificate + + log.Println("Loading certificate") + cert := parseX509(certData) // Convert bytes to Go x509.Certificate + + log.Println("Loading CA certificate") caCert := parseX509([]byte(caCertString)) + + log.Println("Loading ROOT certificate") rootCert := parseX509([]byte(caRootString)) checkPublicKey(cert) // Print the information about the key @@ -268,9 +368,9 @@ func integrityCheckAndGo(keyPath, certPath, caRootString, caCertString string) ( caCertList := []*x509.Certificate{caCert, rootCert} - msg, pfxData, err := generatePKCS12(key, cert, caCertList) + pfxPass, pfxData, err := generatePKCS12(key, cert, caCertList) if err != nil { - return msg, nil + return "", nil } - return msg, pfxData + return pfxPass, pfxData }