gpg

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package gpg provides GPG operations by shelling out to the gpg binary.

Index

Constants

View Source
const (

	// TargetTypeKeyserver is the publish target type for keyservers.
	TargetTypeKeyserver = "keyserver"
	// TargetTypeGitHub is the publish target type for GitHub.
	TargetTypeGitHub = "github"
)

Variables

This section is empty.

Functions

func LatestSubkeyIDs

func LatestSubkeyIDs(keys []SubKey) []string

LatestSubkeyIDs returns the key IDs of the most recently created non-revoked S, E, A subkeys. This is used to determine which subkeys to move to a card.

func UsageLabel

func UsageLabel(usage string) string

UsageLabel converts a usage code to a human-readable label.

func ValidateFingerprint

func ValidateFingerprint(fp string) error

ValidateFingerprint checks that fp looks like a valid GPG fingerprint (40 hex chars).

func ValidateKeyID

func ValidateKeyID(id string) error

ValidateKeyID checks that id looks like a valid GPG key ID (16 hex chars).

func ValidateSerial

func ValidateSerial(serial string) error

ValidateSerial checks that serial looks like a valid card serial number (numeric).

func ValidateServerAlias added in v0.1.0

func ValidateServerAlias(alias string) error

ValidateServerAlias checks that an alias is non-empty and contains only lowercase letters, digits, and hyphens.

func WriteAgentConfig added in v0.3.0

func WriteAgentConfig(homeDir string) error

WriteAgentConfig writes gpg.conf and gpg-agent.conf into homeDir to enable loopback pinentry mode. This bypasses pinentry entirely so the application can supply passphrases via --passphrase-fd, which works in any environment (TTY, non-TTY, container, CI). It also tunes gpg-agent's passphrase cache for typical interactive session lengths.

Existing files are overwritten. Permissions are set to 0600.

func YkmanAvailable

func YkmanAvailable() bool

YkmanAvailable returns true if the ykman CLI is installed.

Types

type CardInfo

type CardInfo struct {
	Serial     string
	Model      string
	ReaderName string   // raw Reader field from card-status
	KeyIDs     []string // key grip or key IDs on the card
}

CardInfo holds information from gpg --card-status.

type CardMode

type CardMode string

CardMode determines how keys are provisioned to a card.

const (
	// CardModeSameKeys moves existing subkeys to the card.
	CardModeSameKeys CardMode = "same-keys"
	// CardModeUniqueKeys generates fresh subkeys before moving to card.
	CardModeUniqueKeys CardMode = "unique-keys"
)

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps the gpg binary for key operations.

func New

func New(opts Options) (*Client, error)

New creates a new GPG client. HomeDir is required.

func (*Client) AddUID added in v0.3.0

func (c *Client) AddUID(ctx context.Context, masterFP string, uid string) error

AddUID attaches a new user ID to the master key. The uid argument must be in the form "Name <email>" or "Name (comment) <email>".

func (*Client) AutoDiscoverConfig

func (c *Client) AutoDiscoverConfig(ctx context.Context) (*Config, error)

AutoDiscoverConfig detects the master key fingerprint and populates a default config.

func (*Client) CardStatus

func (c *Client) CardStatus(ctx context.Context) (*CardInfo, error)

CardStatus queries the connected smart card and returns its info.

On systems without pcscd (where scdaemon uses internal CCID via libusb), only one scdaemon at a time can claim a YubiKey. If the user has a long-running gpg-agent for ~/.gnupg (typical for desktop Linux with enable-ssh-support), its scdaemon will hold the card and a fresh scdaemon spawned in the gpgsmith session's GNUPGHOME cannot acquire it.

To handle this, we detect the "No such device" failure mode, kill all conflicting scdaemons system-wide via `gpgconf --kill scdaemon` (which affects every gpg-agent on this user account), and retry once. The killed scdaemons will respawn on the next gpg call from any homedir, so this is a transient interruption — no permanent state change.

func (*Client) DiscoverCard

func (c *Client) DiscoverCard(ctx context.Context) (*YubiKeyEntry, error)

DiscoverCard detects a connected YubiKey and returns an inventory entry. The entry is not automatically added to the inventory.

func (*Client) ExportPubKeyToLocal added in v0.1.0

func (c *Client) ExportPubKeyToLocal(ctx context.Context, masterFP string) error

ExportPubKeyToLocal exports the public key from the current GNUPGHOME and imports it into the user's default ~/.gnupg keyring. It also copies private key stubs for card-bound subkeys so the local keyring can use the card for signing outside the vault session.

func (*Client) ExportSSHPubKey

func (c *Client) ExportSSHPubKey(ctx context.Context, masterFP string) (string, error)

ExportSSHPubKey exports the authentication subkey as an SSH public key to ~/.ssh/gpgsmith-<keyid>.pub.

func (*Client) GenerateMasterKey added in v0.2.0

func (c *Client) GenerateMasterKey(ctx context.Context, opts MasterKeyOpts) (string, error)

GenerateMasterKey creates a new master key (certify-only) using --quick-gen-key. It returns the fingerprint of the newly created key.

func (*Client) GenerateSubkeys

func (c *Client) GenerateSubkeys(ctx context.Context, opts SubkeyOpts) error

GenerateSubkeys creates Sign, Encrypt, and Auth subkeys under the master key.

func (*Client) HasPassphrase added in v0.3.0

func (c *Client) HasPassphrase() bool

HasPassphrase reports whether a passphrase is configured for this client.

func (*Client) HomeDir

func (c *Client) HomeDir() string

HomeDir returns the GNUPGHOME directory.

func (*Client) ListKeys

func (c *Client) ListKeys(ctx context.Context) ([]SubKey, error)

ListKeys returns all keys and subkeys in the keyring.

func (*Client) ListSecretKeys

func (c *Client) ListSecretKeys(ctx context.Context) ([]SubKey, error)

ListSecretKeys returns all secret keys and subkeys in the keyring.

func (*Client) ListUIDs added in v0.3.0

func (c *Client) ListUIDs(ctx context.Context, masterFP string) ([]UID, error)

ListUIDs returns all user IDs on the master key identified by masterFP, in the order gpg reports them. Revoked UIDs are included.

Uses --list-sigs (not --list-keys) so that revoked UIDs still have an associated creation date: gpg's colon output strips field 5 from a revoked uid record, but the original self-signature line that follows it still carries the date the UID was added. The companion rev: line (if present) carries the revocation date.

func (*Client) LoadConfig

func (c *Client) LoadConfig() (*Config, error)

LoadConfig reads the GPG config from GNUPGHOME/gpgsmith.yaml.

func (*Client) LoadInventory

func (c *Client) LoadInventory() (*Inventory, error)

LoadInventory reads the YubiKey inventory from GNUPGHOME/gpgsmith-inventory.yaml.

func (*Client) LoadServerRegistry added in v0.1.0

func (c *Client) LoadServerRegistry() (*ServerRegistry, error)

LoadServerRegistry reads the server registry from GNUPGHOME/gpgsmith-servers.yaml. If the file does not exist, it migrates from publish_targets in the config or initializes with defaults.

func (*Client) LookupGitHub

func (c *Client) LookupGitHub(ctx context.Context, masterFP string) KeyserverLookupResult

LookupGitHub checks if the GPG key is registered on GitHub via `gh gpg-key list`.

func (*Client) LookupKeyservers

func (c *Client) LookupKeyservers(ctx context.Context, masterFP string, servers []string) []KeyserverLookupResult

LookupKeyservers queries multiple keyservers to check if a key is published. Each keyserver query has a 15-second timeout to avoid hanging on unresponsive servers.

func (*Client) MoveToCard

func (c *Client) MoveToCard(ctx context.Context, masterFP string, keyIDs []string) error

MoveToCard transfers subkeys to the connected smart card using a single --edit-key session. keyIDs specifies which subkeys to move (by long key ID). The correct gpg subkey index and card slot are determined automatically from the keyring. All subkeys are moved in one gpg process so the master key passphrase is requested only once.

func (*Client) Publish

func (c *Client) Publish(ctx context.Context, masterFP string, targets []PublishTarget) []PublishResult

Publish sends the public key to all configured publish targets. It continues on failure, collecting results for each target.

func (*Client) Revoke

func (c *Client) Revoke(ctx context.Context, masterFP string, keyID string) error

Revoke revokes a specific subkey by its key ID using --edit-key revkey.

func (*Client) RevokeUID added in v0.3.0

func (c *Client) RevokeUID(ctx context.Context, masterFP string, uid string) error

RevokeUID revokes a user ID on the master key. The uid argument must match the existing UID exactly (use ListUIDs to look it up).

func (*Client) SaveConfig

func (c *Client) SaveConfig(cfg *Config) error

SaveConfig writes the GPG config to GNUPGHOME/gpgsmith.yaml.

func (*Client) SaveInventory

func (c *Client) SaveInventory(inv *Inventory) error

SaveInventory writes the YubiKey inventory to GNUPGHOME/gpgsmith-inventory.yaml.

func (*Client) SaveServerRegistry added in v0.1.0

func (c *Client) SaveServerRegistry(reg *ServerRegistry) error

SaveServerRegistry writes the server registry to GNUPGHOME/gpgsmith-servers.yaml.

func (*Client) SetPrimaryUID added in v0.3.0

func (c *Client) SetPrimaryUID(ctx context.Context, masterFP string, uid string) error

SetPrimaryUID promotes a user ID to primary on the master key. The uid argument must match the existing UID exactly.

func (*Client) YkmanInfo

func (c *Client) YkmanInfo(ctx context.Context) (*YkmanDeviceInfo, error)

YkmanInfo runs `ykman info` and parses the output.

type Config

type Config struct {
	MasterFP       string          `yaml:"master_fp"`
	SubkeyAlgo     string          `yaml:"subkey_algo"`
	SubkeyExpiry   string          `yaml:"subkey_expiry"`
	PublishTargets []PublishTarget `yaml:"publish_targets,omitempty"`
}

Config holds GPG-specific configuration stored inside GNUPGHOME/gpgsmith.yaml.

type Inventory

type Inventory struct {
	YubiKeys []YubiKeyEntry `yaml:"yubikeys"`
}

Inventory holds all known YubiKeys.

func (*Inventory) FindByLabel

func (inv *Inventory) FindByLabel(labelOrSerial string) *YubiKeyEntry

FindByLabel returns the YubiKey entry matching the given label or serial.

type KeyserverLookupResult

type KeyserverLookupResult struct {
	URL   string
	Found bool
	Err   error
}

KeyserverLookupResult holds the result of looking up a key on a keyserver.

type MasterKeyOpts added in v0.2.0

type MasterKeyOpts struct {
	NameReal  string // e.g. "Oleg Tsarev"
	NameEmail string // e.g. "oleg@example.com"
	Algo      string // e.g. "rsa4096", "ed25519" (default: "rsa4096")
	Expiry    string // e.g. "0" (no expiry), "2y" (default: "0")
}

MasterKeyOpts configures master key generation.

type Options

type Options struct {
	Binary     string // gpg binary path (default: "gpg")
	HomeDir    string // GNUPGHOME directory
	Logger     *slog.Logger
	Passphrase string // optional master-key passphrase, used with --pinentry-mode loopback
}

Options configures a new Client.

type PublishResult

type PublishResult struct {
	Target PublishTarget
	Err    error
}

PublishResult holds the outcome of publishing to a single target.

type PublishTarget

type PublishTarget struct {
	Type string `yaml:"type"` // "keyserver" or "github"
	URL  string `yaml:"url,omitempty"`
}

PublishTarget describes where to publish public keys.

func ToPublishTargets added in v0.1.0

func ToPublishTargets(entries []ServerEntry) []PublishTarget

ToPublishTargets converts server entries to PublishTarget slice for use with Publish().

type ServerEntry added in v0.1.0

type ServerEntry struct {
	Alias   string `yaml:"alias"`
	Type    string `yaml:"type"` // "keyserver" or "github"
	URL     string `yaml:"url,omitempty"`
	Enabled bool   `yaml:"enabled"`
}

ServerEntry represents a publish target (keyserver or GitHub) in the registry.

func DefaultServers added in v0.1.0

func DefaultServers() []ServerEntry

DefaultServers returns the built-in server list.

type ServerRegistry added in v0.1.0

type ServerRegistry struct {
	Servers []ServerEntry `yaml:"servers"`
}

ServerRegistry holds all known publish targets.

func (*ServerRegistry) AllServerURLs added in v0.1.0

func (reg *ServerRegistry) AllServerURLs() []string

AllServerURLs returns all unique keyserver URLs from the registry.

func (*ServerRegistry) EnabledServers added in v0.1.0

func (reg *ServerRegistry) EnabledServers() []ServerEntry

EnabledServers returns all servers with Enabled == true.

func (*ServerRegistry) FindByAlias added in v0.1.0

func (reg *ServerRegistry) FindByAlias(alias string) *ServerEntry

FindByAlias returns a pointer to the server entry with the given alias, or nil.

type SubKey

type SubKey struct {
	KeyID       string
	Fingerprint string
	Algorithm   string
	Usage       string // S, E, A, C (may be combined like "SC")
	Created     time.Time
	Expires     time.Time
	CardSerial  string
	Validity    string // validity field from colons output: "r" = revoked, "e" = expired, etc.
}

SubKey represents a GPG key or subkey parsed from --with-colons output.

type SubKeyRef

type SubKeyRef struct {
	KeyID   string    `yaml:"keyid"`
	Usage   string    `yaml:"usage"` // "sign", "encrypt", "auth"
	Created time.Time `yaml:"created"`
	Expires time.Time `yaml:"expires,omitempty"`
}

SubKeyRef is a reference to a subkey stored on a YubiKey.

type SubkeyOpts

type SubkeyOpts struct {
	MasterFP string // master key fingerprint
	Algo     string // e.g. "rsa4096", "ed25519"
	Expiry   string // e.g. "2y", "1y"
}

SubkeyOpts configures subkey generation.

type UID added in v0.3.0

type UID struct {
	Index    int    // 1-based index in the order returned by gpg
	Validity string // validity field: "u" = ultimate, "r" = revoked, "e" = expired
	Created  time.Time
	Revoked  time.Time // zero unless the UID has been revoked
	Hash     string    // 40-char hex hash of the UID (gpg internal identifier)
	UID      string    // "Name (comment) <email>"
}

UID represents a user ID attached to a master key.

type YkmanDeviceInfo

type YkmanDeviceInfo struct {
	DeviceType      string // e.g. "YubiKey 5 NFC"
	Serial          string
	FirmwareVersion string
	FormFactor      string
}

YkmanDeviceInfo holds info from `ykman info`.

type YubiKeyEntry

type YubiKeyEntry struct {
	Serial        string      `yaml:"serial"`
	Label         string      `yaml:"label"`
	Model         string      `yaml:"model"`
	Description   string      `yaml:"description,omitempty"`
	Provisioning  string      `yaml:"provisioning"` // "same-keys" or "unique-keys"
	Subkeys       []SubKeyRef `yaml:"subkeys,omitempty"`
	ProvisionedAt time.Time   `yaml:"provisioned_at"`
	Status        string      `yaml:"status"` // "active" or "revoked"
}

YubiKeyEntry represents a provisioned YubiKey in the inventory.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL