Help us shape the future of Go! Take the 2026 Go Developer Survey

signer

package
v0.2.50 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MPL-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package signer is the CLI-side RFC 9421 signing surface. It is deliberately distinct from chassis/auth/signature/ — that package wraps yaronf/httpsign for both signing and verifying, and httpsign requires a concrete ed25519.PrivateKey at construction time. We need to plug in alternative backends (ssh-agent today, hardware tokens later), so the outbound side owns its own canonicalization here.

The chassis-side verifier (chassis/auth/signature/verifier.go) does NOT use anything from this package — it keeps using httpsign. The only contract between us and the server is the wire format: same covered components, same Signature-Input parameters, same Ed25519 signature bytes.

Index

Constants

This section is empty.

Variables

View Source
var ErrKeyNotInAgent = errors.New("expected key not present in ssh-agent")

ErrKeyNotInAgent signals "agent is reachable but doesn't hold a key matching the one we expect." Most commonly: the user logged out / ssh-add -D'd between enrollment and the next signed call.

View Source
var ErrNoAgent = errors.New("ssh-agent not available (SSH_AUTH_SOCK unset or unreachable)")

ErrNoAgent signals "$SSH_AUTH_SOCK is unset or the socket can't be reached." Surface this as a typed sentinel so the enrollment resolver can distinguish "no agent" from "agent reachable but has no matching key" — the two cases need different operator messages.

Functions

func Fingerprint

func Fingerprint(pub ed25519.PublicKey) string

Fingerprint returns the SHA256 fingerprint of an Ed25519 public key in the canonical ssh-keygen `-lf` format (`SHA256:<base64-no-pad>`). Computes over the SSH wire format, not the raw 32 bytes, so the value matches `ssh-add -l` / `ssh-keygen -lf` output verbatim — letting users cross-reference txco's choice against what their ssh tooling shows.

Falls back to a raw-key SHA256 (still useful as a stable identifier) only if ssh.NewPublicKey somehow fails, which can't happen for a well-formed 32-byte ed25519 key but is handled defensively.

func LoadEd25519PrivateKey

func LoadEd25519PrivateKey(path string, promptPassphrase bool) (ed25519.PrivateKey, error)

LoadEd25519PrivateKey reads path and decodes it to a raw ed25519.PrivateKey. Handles PKCS#8 PEM, OpenSSH PEM, and encrypted OpenSSH (with passphrase prompt when promptPassphrase is true and stdin is a TTY).

Encrypted PKCS#8 is intentionally not supported — too rare to be worth the surface; users get a clear "re-encrypt with `ssh-keygen -p`" message via the underlying ssh.PassphraseMissingError path.

func LoadEd25519PrivateKeyWithPassphrase

func LoadEd25519PrivateKeyWithPassphrase(path string, passphrase []byte) (ed25519.PrivateKey, error)

LoadEd25519PrivateKeyWithPassphrase decodes an encrypted PEM key using the supplied passphrase. Returns a clear error if path is not an encrypted key or the passphrase is wrong.

Types

type AgentSigner

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

AgentSigner signs via an ssh-agent. The private key never leaves the agent's process (or the hardware token it brokers — Yubikeys, Secure Enclave via Secretive, PKCS#11 cards). We only ever send the agent the canonical RFC 9421 signature base; the agent returns a raw Ed25519 signature blob which we set verbatim in the Signature header.

func NewAgentSigner

func NewAgentSigner(keyID string, wantPub ed25519.PublicKey) (*AgentSigner, error)

NewAgentSigner dials $SSH_AUTH_SOCK and locates wantPub. Returns typed sentinels for the two failure modes that need distinct CLI messages: ErrNoAgent (agent unreachable) and ErrKeyNotInAgent (agent reachable, doesn't have wantPub).

func NewAgentSignerFromAgent

func NewAgentSignerFromAgent(keyID string, wantPub ed25519.PublicKey, a agent.Agent) (*AgentSigner, error)

NewAgentSignerFromAgent is the testable constructor; production callers use NewAgentSigner. Accepts any agent.Agent so unit tests can plug in agent.NewKeyring() pre-loaded with the expected key.

func (*AgentSigner) KeyID

func (s *AgentSigner) KeyID() string

KeyID implements Signer.

func (*AgentSigner) PublicKey

func (s *AgentSigner) PublicKey() ed25519.PublicKey

PublicKey implements Signer.

func (*AgentSigner) Sign

func (s *AgentSigner) Sign(req *http.Request, body []byte) error

Sign implements Signer. Same canonical base as FileKeySigner; the signing primitive is `agent.Agent.Sign` instead of `ed25519.Sign`. We strictly validate the returned signature's Format and Blob length before trusting it.

type FileKeySigner

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

FileKeySigner signs RFC 9421 requests with an Ed25519 private key loaded from a PEM file. The reader transparently handles both the legacy PKCS#8 form txco shipped originally AND the OpenSSH form `ssh-keygen` produces, via crypto/ssh.ParseRawPrivateKey. Existing developer keys keep working; new keys can come straight from a standard SSH workflow.

func NewFileKeySigner

func NewFileKeySigner(keyID, path string, promptPassphrase bool) (*FileKeySigner, error)

NewFileKeySigner loads path and returns a signer. Encrypted-key behavior depends on promptPassphrase:

  • true + TTY: prompts the user via term.ReadPassword.
  • true + non-TTY (pipe): returns *PassphraseMissingError so the CLI can surface a clear "this key is encrypted; pass it via --passphrase or run interactively" error.
  • false: never prompts; *PassphraseMissingError straight away.

func NewFileKeySignerFromKey

func NewFileKeySignerFromKey(keyID string, priv ed25519.PrivateKey) (*FileKeySigner, error)

NewFileKeySignerFromKey constructs a signer from an already-loaded private key. Useful for tests and for the enrollment path that generates a fresh key in-memory before persisting it.

func (*FileKeySigner) KeyID

func (s *FileKeySigner) KeyID() string

KeyID implements Signer.

func (*FileKeySigner) PublicKey

func (s *FileKeySigner) PublicKey() ed25519.PublicKey

PublicKey implements Signer.

func (*FileKeySigner) Sign

func (s *FileKeySigner) Sign(req *http.Request, body []byte) error

Sign implements Signer. Walks the standard canonicalize → sign → set-headers path with raw `ed25519.Sign`. Same wire output as the AgentSigner; only the signing primitive differs.

type PassphraseMissingError

type PassphraseMissingError struct{ Path string }

PassphraseMissingError is the typed sentinel callers use to detect "this key is encrypted, you need to supply a passphrase." Mirrors ssh.PassphraseMissingError but is owned by this package so callers don't have to import x/crypto/ssh just for type assertions.

func (*PassphraseMissingError) Error

func (e *PassphraseMissingError) Error() string

Error implements error.

type Signer

type Signer interface {
	// KeyID is the RFC 9421 keyid value the chassis registry uses
	// to look up the public key for verification. Always non-empty
	// for a usable Signer.
	KeyID() string

	// PublicKey is the raw 32-byte Ed25519 public key for this
	// signer, useful for fingerprint display and meta cross-checks.
	PublicKey() ed25519.PublicKey

	// Sign sets the three required headers on req. body is the
	// already-marshaled request body (nil for GETs / empty bodies).
	// Implementations must:
	//   1. compute Content-Digest from body and set the header,
	//   2. compute the RFC 9421 signature base for the covered
	//      components + chosen params,
	//   3. sign the base bytes via the backend (raw ed25519, agent, …),
	//   4. set Signature-Input + Signature headers.
	Sign(req *http.Request, body []byte) error
}

Signer signs an outgoing *http.Request in place. After Sign returns, the request carries Content-Digest, Signature-Input, and Signature headers in the same wire format the chassis verifier expects — regardless of whether the signing key lives in process memory, in an ssh-agent, or on a hardware token.

Jump to

Keyboard shortcuts

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