package main import ( "crypto/x509" "encoding/pem" "image/color" "log" "os" certpair "pkcs-generator/modules/certpairs" "slices" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) var ( windowSize fyne.Size = fyne.NewSize(700, 500) keyTextFilter []string = []string{".key", ".txt", ".pem"} crtTextFilter []string = []string{".crt", ".cer", ".txt", ".pem"} ) func main() { a := app.NewWithID("nl.systemec.pkcs-generator") a.Settings().SetTheme(theme.DefaultTheme()) w := a.NewWindow("Systemec PKCS-Generator") w.Resize(windowSize) var keyPath, certPath string // Labels to show selected filenames fileLabel1 := widget.NewLabel("No file selected") fileLabel2 := widget.NewLabel("No file selected") radioLabel1 := widget.NewLabel("Select which Sectigo Intermediate") // Certificate Keyfile keyBtn := widget.NewButton("Upload Private Key File", func() { keyDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) { if r != nil { fileLabel1.SetText(r.URI().Name()) keyPath = r.URI().Path() r.Close() } }, w) keyFilter := storage.NewExtensionFileFilter(keyTextFilter) keyDiag.SetFilter(keyFilter) keyDiag.Resize(windowSize) keyDiag.Show() }) // 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) { if r != nil { fileLabel2.SetText(r.URI().Name()) certPath = r.URI().Path() r.Close() } }, w) certFilter := storage.NewExtensionFileFilter(crtTextFilter) certDiag.SetFilter(certFilter) // Resize the dialog certDiag.Resize(windowSize) 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 } }) caRadio.SetSelected("New Sectigo (2025+)") // default // Multiline entry textLabl := widget.NewLabel("Status will appear here...") textLabl.Selectable = true border := canvas.NewRectangle(color.Transparent) border.StrokeColor = color.RGBA{255, 255, 255, 255} border.StrokeWidth = 2 border.CornerRadius = 5 statusBox := container.NewStack(border, textLabl) actionBtn := widget.NewButton("Generate", func() { files := []string{keyPath, certPath} // The following structure is basic but can easily be expanded to fit more pairs // 0 = ROOT // 1 = CA // As per spec in the certpair module. var certPair []string if sectigo2025 { certPair = certpair.SectigoNewChain } else { certPair = certpair.SectigoOldChain } allPresent := true if slices.Contains(files, "") { allPresent = false } if !allPresent { textLabl.SetText("One or more files missing!") log.Println("One or more files missing!") return } respText, pfxData := integrityCheckAndGo(keyPath, certPath, certPair[1], certPair[0]) if pfxData == nil { log.Println(respText) textLabl.SetText(respText) return } // Show Save File dialog immediately svDialog := dialog.NewFileSave( func(writer fyne.URIWriteCloser, err error) { 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 } writer.Close() var dnText string = "\nPKCS file saved to: " + writer.URI().Path() + "\n\n" + respText + "\n" textLabl.SetText(dnText) }, w) svDialog.Resize(windowSize) svDialog.SetFileName("certificate_store.pfx") svDialog.SetFilter(storage.NewExtensionFileFilter([]string{".pfx"})) svDialog.Show() }) cancelBtn := widget.NewButton("Exit", func() { log.Println("Quitting...") os.Exit(0) }) // CREATE LAYOUTS bottom := container.NewHBox( actionBtn, // left layout.NewSpacer(), // flexible space cancelBtn, // 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), radioLabel1, caRadio), layout.NewSpacer(), // optional flexible space statusBox, // Add the referenced text container widget.NewLabel(""), // Add empty line for space ) content := container.NewBorder( nil, // top bottom, // bottom nil, nil, // left, right centerContent, ) w.SetContent(content) w.ShowAndRun() } func readFile(path string) []byte { rawData, err := os.ReadFile(path) if err != nil { log.Printf("Error reading contents of %s, %e", path, err) return nil } else { return rawData } } func parsePrivateKey(keyData []byte) any { block, _ := pem.Decode(keyData) if block == nil { log.Fatal("failed to decode key PEM") } privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { log.Fatal(err) } return privateKey } func parseX509(certData []byte) *x509.Certificate { block, _ := pem.Decode(certData) if block == nil { log.Fatal("failed to parse PEM block") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { log.Fatal(err) } return cert } func integrityCheckAndGo(keyPath, certPath, caRootString, caCertString string) (string, []byte) { if _, err := os.Stat(keyPath); err != nil { log.Printf("Error checking Private Keyfile, %e", err) return "Error...", nil } if _, err := os.Stat(certPath); err != nil { log.Printf("Error checking Certificate file, %e", err) return "Error...", nil } keyData := readFile(keyPath) // Read the private key file (PEM/DER) certData := readFile(certPath) // Read the certificate file (PEM/DER) key := parsePrivateKey(keyData) // Convert bytes to Go private key object cert := parseX509(certData) // Convert bytes to Go x509.Certificate caCert := parseX509([]byte(caCertString)) rootCert := parseX509([]byte(caRootString)) checkPublicKey(cert) // Print the information about the key if checkCertKeyPair(cert.PublicKey, key) { log.Println("Private key matches certificate") } else { return "Private key does NOT match certificate", nil } caCertList := []*x509.Certificate{caCert, rootCert} msg, pfxData, err := generatePKCS12(key, cert, caCertList) if err != nil { return msg, nil } return msg, pfxData }