Skip to content

Commit

Permalink
feat: add options fingerprint, trust_level and armor (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoxys authored Mar 14, 2024
1 parent dc0020c commit c0cade6
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 215 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ TARGETARCH ?= amd64
ifneq ("$(TARGETVARIANT)","")
GOARM ?= $(subst v,,$(TARGETVARIANT))
endif
TAGS ?= netgo
TAGS ?= netgo,osusergo

ifndef VERSION
ifneq ($(CI_COMMIT_TAG),)
Expand Down
22 changes: 22 additions & 0 deletions cmd/wp-gpgsign/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ func settingsFlags(settings *plugin.Settings, category string) []cli.Flag {
Destination: &settings.Passphrase,
Category: category,
},
&cli.StringFlag{
Name: "fingerprint",
Usage: "specific fingerprint to be used (subkey)",
EnvVars: []string{"PLUGIN_FINGERPRINT", "GPGSIGN_FINGERPRINT", "GPG_FINGERPRINT"},
Destination: &settings.Fingerprint,
Category: category,
},
&cli.StringFlag{
Name: "trust-level",
Usage: "key owner trust level",
EnvVars: []string{"PLUGIN_TRUST_LEVEL"},
Destination: &settings.TrustLevel,
Value: "unknown",
Category: category,
},
&cli.BoolFlag{
Name: "armor",
Usage: "create ASCII-armored output instead of a binary",
Destination: &settings.Armor,
Value: false,
EnvVars: []string{"PLUGIN_ARMOR"},
},
&cli.BoolFlag{
Name: "detach-sign",
Usage: "creates a detached signature for the file",
Expand Down
24 changes: 23 additions & 1 deletion docs/data/data.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
---
properties:
- name: armor
description: |
Create ASCII-armored output instead of a binary.
type: bool
defaultValue: false
required: false

- name: clear_sign
description: |
Wrap the file in an ASCII-armored signature.
Expand Down Expand Up @@ -27,10 +34,18 @@ properties:
type: list
required: false

- name: fingerprint
description: |
Specific fingerprint to be used. Most like this option is required if a subkey of the given
GPG key should be used. If not set, the fingerprint of the primary key is used.
type: string
required: false

- name: homedir
description: |
GPG home directory. By default, a random home directory is generated in `/tmp`.
GPG home directory.
type: string
defaultValue: $HOME/.gnupg
required: false

- name: key
Expand All @@ -44,3 +59,10 @@ properties:
Passphrase for the GPG private key.
type: string
required: false

- name: trust_level
description: |
Key owner trust level. Supported values: `unknown|never|marginal|full|ultimate`.
type: string
defaultValue: "unknown"
required: false
48 changes: 0 additions & 48 deletions gnupg/gopenpgp.go

This file was deleted.

170 changes: 31 additions & 139 deletions gnupg/gpg.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package gnupg

import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"

"github.com/thegeeklab/wp-plugin-go/trace"
"github.com/rs/zerolog/log"
"golang.org/x/sys/execabs"
)

var ErrGPGDirLookupFailed = errors.New("failed to lookup GPG directories")
var (
ErrDirLookupFailed = errors.New("failed to lookup gpg directories")
ErrInvalidTrustLevel = errors.New("invlaid key owner trust level")
ErrGetKeygripsFailed = errors.New("failed to get keygrips")
)

const (
gpgBin = "/usr/bin/gpg"
gpgconfBin = "/usr/bin/gpgconf"

strictDirPerm = 0o700
strictDirPerm = 0o700
strictFilePerm = 0o600
)

type Client struct {
Expand Down Expand Up @@ -51,10 +56,7 @@ type Dirs struct {
Home string
}

// New creates a new GPG client instance with the provided key and passphrase.
// It initializes the client fields and creates a temporary home directory for
// GPG operations. The home directory permissions are set to 0700 and the
// GNUPGHOME environment variable is set to point to the home directory.
// New creates a new Client instance with the given private key and passphrase.
func New(key, passphrase string) (*Client, error) {
client := &Client{
Key: Key{
Expand All @@ -65,30 +67,29 @@ func New(key, passphrase string) (*Client, error) {
Version: Version{},
}

if err := client.SetHomedir(""); err != nil {
home, err := os.UserHomeDir()
if err != nil {
log.Warn().Msgf("failed to get user home dir: %s: fallback to '/root'", err)

home = "/root"
}

home = filepath.Join(home, ".gnupg")

if err := client.SetHomedir(home); err != nil {
return client, err
}

return client, nil
}

// SetHomedir sets the home directory path for the GPG client.
// It creates the directory if it doesn't exist and sets the
// GNUPGHOME environment variable to point to it.
func (c *Client) SetHomedir(path string) error {
var err error

if path == "" {
path, err = os.MkdirTemp("/tmp", "plugin_gpgsign_")
if err != nil {
return fmt.Errorf("failed to create tmp dir: %w", err)
}

if err := os.Chmod(path, strictDirPerm); err != nil {
return err
}
} else {
err = os.MkdirAll(path, strictDirPerm)
if err != nil {
return fmt.Errorf("failed to create homedir dir: %w", err)
}
err := os.MkdirAll(path, strictDirPerm)
if err != nil {
return fmt.Errorf("failed to create homedir dir: %w", err)
}

c.Homedir = path
Expand All @@ -106,14 +107,14 @@ func (c *Client) GetDirs() error {

cmd.Env = append(os.Environ(), c.Env...)

output, err := cmd.Output()
out, err := cmd.Output()
if err != nil {
return err
}

res := string(output)
res := string(out)
if len(res) > 0 && cmd.ProcessState.ExitCode() != 0 {
return fmt.Errorf("%w: %s", ErrGPGDirLookupFailed, res)
return fmt.Errorf("%w: %s", ErrDirLookupFailed, res)
}

lines := strings.Split(strings.ReplaceAll(res, "\r", ""), "\n")
Expand Down Expand Up @@ -154,12 +155,12 @@ func (c *Client) GetVersion() (*Version, error) {

cmd.Env = append(os.Environ(), c.Env...)

output, err := cmd.CombinedOutput()
out, err := cmd.CombinedOutput()
if err != nil {
return version, err
}

lines := strings.Split(strings.TrimSpace(string(output)), "\n")
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
for _, line := range lines {
switch {
case strings.HasPrefix(line, "gpg (GnuPG) "):
Expand All @@ -174,115 +175,6 @@ func (c *Client) GetVersion() (*Version, error) {
return version, nil
}

// ImportKey imports a GPG key provided via the Key.Content field.
// It runs the gpg --import command to import the key into the keyring.
// Returns an error if the import command fails.
func (c *Client) ImportKey() error {
args := []string{
"--batch",
"--import",
"-",
}

cmd := execabs.Command(
gpgBin,
args...,
)

cmd.Env = append(os.Environ(), c.Env...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = strings.NewReader(c.Key.Content)

trace.Cmd(cmd)

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to import gpg key: %w", err)
}

return nil
}

// SetTrustLevel sets the trust level for the public key in the client.
// It runs the gpg --edit-key command to set the trust level to the
// provided level string. Valid levels are "undefined", "never",
// "marginal", "full", "ultimate". Returns an error if the command fails.
func (c *Client) SetTrustLevel(level string) error {
args := []string{
"--batch",
"--no-tty",
"--command-fd",
"0",
"--edit-key",
c.Key.ID,
}

cmd := execabs.Command(
gpgBin,
args...,
)

cmd.Env = append(os.Environ(), c.Env...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = bytes.NewBuffer([]byte(fmt.Sprintf("trust\n%s\ny\nquit\n", level)))

trace.Cmd(cmd)

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to set trustlevel: %w", err)
}

return nil
}

// SignFile signs the file at the given path with the configured key.
// It supports detached, cleartext, and normal signing based on the
// detach and clear arguments.
func (c *Client) SignFile(detach, clear bool, path string) error {
args := []string{
"--batch",
"--yes",
"--armor",
}

if c.Key.Passphrase != "" {
args = append(args, "--pinentry-mode", "loopback", "--passphrase-fd", "0")
}

switch {
case detach:
args = append(args, "--detach-sign")
case clear:
args = append(args, "--clear-sign")
default:
args = append(args, "--sign")
}

args = append(args, path)

cmd := execabs.Command(
gpgBin,
args...,
)

cmd.Env = append(os.Environ(), c.Env...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if c.Key.Passphrase != "" {
cmd.Stdin = strings.NewReader(c.Key.Passphrase)
}

trace.Cmd(cmd)

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to sign file: %w", err)
}

return nil
}

// Cleanup removes the home directory for the given key, if one was specified.
// It returns any error encountered while removing the directory.
func (c *Client) Cleanup() error {
Expand Down
Loading

0 comments on commit c0cade6

Please sign in to comment.