package main import ( "bytes" "fmt" "io" "os" "os/exec" "path/filepath" "strings" ) func openssl_path() (string, error) { path, err := exec.LookPath("openssl") if err != nil { return "", fmt.Errorf("required command not found: openssl") } return path, nil } func openssl_subject_name(commonName string) (string, error) { if strings.ContainsAny(commonName, "\x00\r\n") { return "", fmt.Errorf("certificate name cannot contain control characters") } escaped := strings.NewReplacer(`\`, `\\`, `/`, `\/`).Replace(commonName) return "/CN=" + escaped, nil } func install_private_bytes(path string, contents []byte) error { return write_private_file(path, func(writer io.Writer) error { _, err := writer.Write(contents) return err }) } func (app *App) generate_certificate() error { openssl, err := openssl_path() if err != nil { return app.ui.message(err.Error()) } if _, err := os.Stat(app.paths.CertFile); err == nil { replace, err := app.ui.confirm("A certificate already exists. Replace it? This may affect your registered identity on servers.") if err != nil || !replace { return err } } else if !os.IsNotExist(err) { return app.ui.message("Could not inspect the certificate: " + err.Error()) } commonName, cancelled, err := app.ui.input("Enter a name for your certificate, such as your username:", "barnard", false) if err != nil || cancelled { return err } commonName = strings.TrimSpace(commonName) if commonName == "" { commonName = "barnard" } subject, err := openssl_subject_name(commonName) if err != nil { return app.ui.message(err.Error()) } keyFile, err := os.CreateTemp(app.paths.ConfigDir, ".certificate-key-") if err != nil { return app.ui.message("Failed to create a certificate: " + err.Error()) } keyPath := keyFile.Name() keyFile.Close() defer os.Remove(keyPath) certificateFile, err := os.CreateTemp(app.paths.ConfigDir, ".certificate-public-") if err != nil { return app.ui.message("Failed to create a certificate: " + err.Error()) } certificatePath := certificateFile.Name() certificateFile.Close() defer os.Remove(certificatePath) command := exec.Command(openssl, "req", "-x509", "-newkey", "rsa:2048", "-keyout", keyPath, "-out", certificatePath, "-days", "3650", "-nodes", "-subj", subject) if output, err := command.CombinedOutput(); err != nil { app.log_line("OpenSSL certificate generation failed: " + strings.TrimSpace(string(output))) return app.ui.message("Failed to generate certificate.") } privateKey, err := os.ReadFile(keyPath) if err != nil { return app.ui.message("Failed to read generated private key: " + err.Error()) } certificate, err := os.ReadFile(certificatePath) if err != nil { return app.ui.message("Failed to read generated certificate: " + err.Error()) } combined := append(append(append([]byte(nil), privateKey...), '\n'), certificate...) if err := install_private_bytes(app.paths.CertFile, combined); err != nil { return app.ui.message("Failed to install generated certificate: " + err.Error()) } app.log_line("Generated certificate " + app.paths.CertFile) return app.ui.message("Certificate generated successfully.") } func (app *App) view_certificate() error { openssl, err := openssl_path() if err != nil { return app.ui.message(err.Error()) } if _, err := os.Stat(app.paths.CertFile); os.IsNotExist(err) { return app.ui.message("No certificate found: " + app.paths.CertFile) } else if err != nil { return app.ui.message("Could not inspect the certificate: " + err.Error()) } command := exec.Command(openssl, "x509", "-in", app.paths.CertFile, "-noout", "-subject", "-dates", "-fingerprint") output, err := command.CombinedOutput() if err != nil || strings.TrimSpace(string(output)) == "" { return app.ui.message("Could not read certificate information.") } return app.ui.message(strings.TrimSpace(string(output))) } func validate_certificate_pair(openssl, path string) error { certificateCheck := exec.Command(openssl, "x509", "-in", path, "-noout") if err := certificateCheck.Run(); err != nil { return fmt.Errorf("the file does not contain a valid PEM certificate") } keyCheck := exec.Command(openssl, "pkey", "-in", path, "-check", "-noout") if err := keyCheck.Run(); err != nil { return fmt.Errorf("the file does not contain a valid private key") } certificatePublic, err := exec.Command(openssl, "x509", "-in", path, "-pubkey", "-noout").Output() if err != nil { return fmt.Errorf("could not read the certificate public key") } keyPublic, err := exec.Command(openssl, "pkey", "-in", path, "-pubout").Output() if err != nil { return fmt.Errorf("could not read the private key public key") } if !bytes.Equal(bytes.TrimSpace(certificatePublic), bytes.TrimSpace(keyPublic)) { return fmt.Errorf("the certificate and private key do not match") } return nil } func (app *App) import_certificate() error { openssl, err := openssl_path() if err != nil { return app.ui.message(err.Error()) } rawPath, cancelled, err := app.ui.input("Enter the full path to a PEM file containing both the certificate and private key:", "", false) if err != nil || cancelled { return err } rawPath = strings.TrimSpace(rawPath) if rawPath == "" { return nil } path, err := expand_user_path(rawPath) if err != nil { return app.ui.message("Could not resolve certificate path: " + err.Error()) } info, err := os.Stat(path) if os.IsNotExist(err) { return app.ui.message("File not found: " + path) } if err != nil { return app.ui.message("Could not inspect certificate file: " + err.Error()) } if !info.Mode().IsRegular() { return app.ui.message("Certificate path is not a regular file: " + path) } contents, err := os.ReadFile(path) if err != nil { return app.ui.message("Failed to read certificate: " + err.Error()) } validationFile, err := os.CreateTemp(app.paths.ConfigDir, ".certificate-import-") if err != nil { return app.ui.message("Failed to prepare certificate validation: " + err.Error()) } validationPath := validationFile.Name() defer os.Remove(validationPath) if err := validationFile.Chmod(0600); err != nil { validationFile.Close() return app.ui.message("Failed to protect certificate validation file: " + err.Error()) } if _, err := validationFile.Write(contents); err != nil { validationFile.Close() return app.ui.message("Failed to prepare certificate validation: " + err.Error()) } if err := validationFile.Close(); err != nil { return app.ui.message("Failed to prepare certificate validation: " + err.Error()) } if err := validate_certificate_pair(openssl, validationPath); err != nil { return app.ui.message(err.Error() + ".") } if _, err := os.Stat(app.paths.CertFile); err == nil { samePath := false sourcePath, sourceErr := filepath.EvalSymlinks(path) destinationPath, destinationErr := filepath.EvalSymlinks(app.paths.CertFile) if sourceErr == nil && destinationErr == nil { samePath = sourcePath == destinationPath } if samePath { return app.ui.message("That certificate is already the active Barnard certificate.") } replace, err := app.ui.confirm("A certificate already exists. Replace it?") if err != nil || !replace { return err } } else if !os.IsNotExist(err) { return app.ui.message("Could not inspect the existing certificate: " + err.Error()) } if err := install_private_bytes(app.paths.CertFile, contents); err != nil { return app.ui.message("Failed to import certificate: " + err.Error()) } app.log_line("Imported certificate " + app.paths.CertFile) return app.ui.message("Certificate imported successfully.") } func (app *App) manage_certificate() error { options := []string{"Generate", "View", "Import", "Go Back"} for { selection, cancelled, err := app.ui.menu(options) if err != nil || cancelled || selection == len(options)-1 { return err } switch options[selection] { case "Generate": err = app.generate_certificate() case "View": err = app.view_certificate() case "Import": err = app.import_certificate() } if err != nil { return err } } }