summaryrefslogtreecommitdiffstats
path: root/internal/cryptokit/openpgp.go
diff options
context:
space:
mode:
authorGab Virebent <gabriel1@virebent.art>2026-08-23 00:26:27 +0200
committerGab Virebent <gabriel1@virebent.art>2026-08-23 00:26:27 +0200
commitbdb8a02bbb3dc0d7c39f5b3ec451abb68e9d093d (patch)
tree796d246f9874641717b5e81a72989961e65a9b9f /internal/cryptokit/openpgp.go
parentc1decadb590c4d79d92bcc4df7772119a5c91244 (diff)
downloadaegis-main.tar.gz
aegis-main.tar.xz
aegis-main.zip
Simplify Usenet posting to signing onlyHEADmain
Diffstat (limited to 'internal/cryptokit/openpgp.go')
-rw-r--r--internal/cryptokit/openpgp.go240
1 files changed, 0 insertions, 240 deletions
diff --git a/internal/cryptokit/openpgp.go b/internal/cryptokit/openpgp.go
deleted file mode 100644
index bf766b5..0000000
--- a/internal/cryptokit/openpgp.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package cryptokit
-
-import (
- "bytes"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-)
-
-const maxOpenPGPMessageBytes = 64 << 20
-
-type limitedBuffer struct {
- buffer bytes.Buffer
- limit int
-}
-
-func (b *limitedBuffer) Write(value []byte) (int, error) {
- if b.buffer.Len() < b.limit {
- remaining := b.limit - b.buffer.Len()
- if len(value) > remaining {
- _, _ = b.buffer.Write(value[:remaining])
- } else {
- _, _ = b.buffer.Write(value)
- }
- }
- return len(value), nil
-}
-
-// OpenPGP support is delegated to the system GnuPG binary. This keeps Aegis
-// compatible with the user's existing OpenPGP installation, including RSA,
-// Ed25519 and X25519 keys, without embedding another keyring. Key material is
-// imported into a temporary 0700 GNUPGHOME and removed after each operation.
-
-func gpgBinary() (string, error) {
- path, err := exec.LookPath("gpg")
- if err != nil {
- return "", errors.New("gpg is required for OpenPGP operations")
- }
- return path, nil
-}
-
-func withGPG(operation func(home string) error) error {
- if _, err := gpgBinary(); err != nil {
- return err
- }
- home, err := os.MkdirTemp("", "aegis-gpg-")
- if err != nil {
- return fmt.Errorf("create temporary OpenPGP home: %w", err)
- }
- defer os.RemoveAll(home)
- if err := os.Chmod(home, 0o700); err != nil {
- return fmt.Errorf("protect temporary OpenPGP home: %w", err)
- }
- return operation(home)
-}
-
-func runGPG(home string, args []string, input []byte, outputLimit int) ([]byte, string, error) {
- binary, err := gpgBinary()
- if err != nil {
- return nil, "", err
- }
- base := []string{
- "--batch", "--no-tty", "--no-options", "--no-auto-check-trustdb",
- "--homedir", home,
- }
- cmd := exec.Command(binary, append(base, args...)...)
- cmd.Stdin = bytes.NewReader(input)
- var stdout limitedBuffer
- var stderr bytes.Buffer
- if outputLimit <= 0 {
- outputLimit = maxOpenPGPMessageBytes
- }
- stdout.limit = outputLimit + 1
- cmd.Stdout = &stdout
- cmd.Stderr = &stderr
- if err := cmd.Run(); err != nil {
- return stdout.buffer.Bytes(), stderr.String(), fmt.Errorf("gpg %s: %w: %s", args[0], err, cleanGPGError(stderr.String()))
- }
- if stdout.buffer.Len() > outputLimit {
- return nil, stderr.String(), fmt.Errorf("OpenPGP output exceeds the %d MiB limit", outputLimit/(1<<20))
- }
- return stdout.buffer.Bytes(), stderr.String(), nil
-}
-
-func cleanGPGError(value string) string {
- value = strings.TrimSpace(value)
- if len(value) > 1000 {
- return value[:1000] + "..."
- }
- return value
-}
-
-func importOpenPGP(home, keyMaterial string) error {
- if strings.TrimSpace(keyMaterial) == "" {
- return errors.New("OpenPGP key material is required")
- }
- _, stderr, err := runGPG(home, []string{"--import"}, []byte(keyMaterial), 1<<20)
- if err != nil {
- return fmt.Errorf("import OpenPGP key: %w", err)
- }
- if strings.Contains(stderr, "no valid OpenPGP data found") {
- return errors.New("no valid OpenPGP key found")
- }
- return nil
-}
-
-func listFingerprints(home string) ([]string, error) {
- output, _, err := runGPG(home, []string{"--with-colons", "--list-keys"}, nil, 1<<20)
- if err != nil {
- return nil, fmt.Errorf("list OpenPGP keys: %w", err)
- }
- var fingerprints []string
- for _, line := range strings.Split(string(output), "\n") {
- fields := strings.Split(line, ":")
- if len(fields) > 9 && fields[0] == "fpr" && fields[9] != "" {
- fingerprints = append(fingerprints, fields[9])
- }
- }
- if len(fingerprints) == 0 {
- return nil, errors.New("no usable OpenPGP key found")
- }
- return fingerprints, nil
-}
-
-func writeGPGFile(home, name string, content []byte) (string, error) {
- path := filepath.Join(home, name)
- file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
- if err != nil {
- return "", fmt.Errorf("create temporary OpenPGP file: %w", err)
- }
- if _, err := file.Write(content); err != nil {
- _ = file.Close()
- return "", fmt.Errorf("write temporary OpenPGP file: %w", err)
- }
- if err := file.Close(); err != nil {
- return "", fmt.Errorf("close temporary OpenPGP file: %w", err)
- }
- return path, nil
-}
-
-// SignOpenPGPDetached returns an ASCII-armored detached signature. The input
-// must contain a private signing key supplied by the user.
-func SignOpenPGPDetached(message []byte, privateKeyArmor string) (string, error) {
- var signature []byte
- err := withGPG(func(home string) error {
- if err := importOpenPGP(home, privateKeyArmor); err != nil {
- return err
- }
- output, _, err := runGPG(home, []string{"--armor", "--detach-sign", "--output", "-"}, message, 1<<20)
- if err != nil {
- return fmt.Errorf("sign OpenPGP message: %w", err)
- }
- signature = output
- return nil
- })
- return string(signature), err
-}
-
-// VerifyOpenPGPDetached verifies an ASCII-armored or binary detached
-// signature using public or private OpenPGP key material supplied by the user.
-func VerifyOpenPGPDetached(message []byte, signature, publicKeyArmor string) error {
- return withGPG(func(home string) error {
- if err := importOpenPGP(home, publicKeyArmor); err != nil {
- return err
- }
- signaturePath, err := writeGPGFile(home, "signature.asc", []byte(signature))
- if err != nil {
- return err
- }
- messagePath, err := writeGPGFile(home, "message.bin", message)
- if err != nil {
- return err
- }
- if _, _, err := runGPG(home, []string{"--verify", signaturePath, messagePath}, nil, 1<<20); err != nil {
- return fmt.Errorf("verify OpenPGP signature: %w", err)
- }
- return nil
- })
-}
-
-// EncryptOpenPGP encrypts a message to every entity in the supplied armored
-// public key ring. If privateSignerArmor is non-empty, it also signs the
-// message with the supplied private key. The result is ASCII armored.
-func EncryptOpenPGP(message []byte, recipientKeyArmor, privateSignerArmor string) (string, error) {
- var encrypted []byte
- err := withGPG(func(home string) error {
- if err := importOpenPGP(home, recipientKeyArmor); err != nil {
- return err
- }
- recipientFingerprints, err := listFingerprints(home)
- if err != nil {
- return err
- }
- args := []string{"--armor", "--trust-model", "always", "--encrypt", "--output", "-"}
- for _, fingerprint := range recipientFingerprints {
- args = append(args, "--recipient", fingerprint)
- }
- if strings.TrimSpace(privateSignerArmor) != "" {
- if err := importOpenPGP(home, privateSignerArmor); err != nil {
- return err
- }
- signerFingerprints, err := listFingerprints(home)
- if err != nil {
- return err
- }
- args = append(args, "--sign", "--local-user", signerFingerprints[0])
- }
- output, _, err := runGPG(home, args, message, maxOpenPGPMessageBytes)
- if err != nil {
- return fmt.Errorf("encrypt OpenPGP message: %w", err)
- }
- encrypted = output
- return nil
- })
- return string(encrypted), err
-}
-
-// DecryptOpenPGP decrypts an ASCII-armored or binary OpenPGP message with a
-// user-provided private key. A bad embedded signature is rejected.
-func DecryptOpenPGP(message []byte, privateKeyArmor string) ([]byte, error) {
- var plaintext []byte
- err := withGPG(func(home string) error {
- if err := importOpenPGP(home, privateKeyArmor); err != nil {
- return err
- }
- output, status, err := runGPG(home, []string{"--status-fd", "2", "--decrypt", "--output", "-"}, message, maxOpenPGPMessageBytes)
- if strings.Contains(status, "[GNUPG:] BADSIG") || strings.Contains(status, "[GNUPG:] ERRSIG") {
- return errors.New("authenticate OpenPGP message: invalid signature")
- }
- if err != nil {
- return fmt.Errorf("decrypt OpenPGP message: %w", err)
- }
- plaintext = output
- return nil
- })
- return plaintext, err
-}