Documentation
¶
Overview ¶
Package openpgpkey mints an ASCII-armored OpenPGP public key from a crypto.Signer. It exists to bridge a KMS/HSM-held signing key (which exposes only a public key plus a remote Sign operation) into the OpenPGP form that the self-update verifier (pkg/setup) and Web Key Directory require.
Why this is needed: the verifier rejects a bare public-key packet ("v4 entity without any identities") — it needs a User ID and a self-signature. Producing that self-signature requires *signing* with the private key. go-crypto signs RSA keys through the crypto.Signer interface, so an opaque KMS-backed signer works without the private key ever leaving the KMS.
Supported key algorithm: **RSA only.**
signer.Public() must return *rsa.PublicKey. The package produces a v4 RSA OpenPGP public-key packet — used by the primary release-signing key path (AWS KMS exposes asymmetric SIGN_VERIFY keys only as RSA) and by `gtb keys generate --algorithm rsa` for the local-signing tutorial flow.
Other key types return ErrUnsupportedKeyType. Ed25519 key generation lives in `internal/cmd/keys/generate.go`, where it goes through `openpgp.NewEntity(... PubKeyAlgoEdDSA ...)` to produce a v4-EdDSA (algorithm 22) key that GnuPG 2.4 and older can import. Bringing Ed25519 into this package would require reaching go-crypto's `internal/ecc` package, which is unreachable from outside the go-crypto module — see docs/development/specs/2026-06-08-keys-mint-command.md D12.
Index ¶
- Variables
- func ArmoredPublicKey(signer crypto.Signer, name, email string, creationTime time.Time) ([]byte, error)
- func Entity(signer crypto.Signer, name, email string, creationTime time.Time) (*openpgp.Entity, error)
- func WKDHash(email string) (string, error)
- func WriteArmoredPublicKey(w io.Writer, signer crypto.Signer, name, email string, creationTime time.Time) error
- func WriteWKDTree(outDir, domain string, opts Options, entries ...Entry) ([]string, error)
- type Entry
- type Method
- type Options
Constants ¶
This section is empty.
Variables ¶
var ErrUnsupportedKeyType = errors.New("unsupported key type: only RSA is supported")
ErrUnsupportedKeyType is returned by ArmoredPublicKey when the signer's Public() returns a key type this package does not handle. Only *rsa.PublicKey is supported in v0.1 — see the package doc.
Functions ¶
func ArmoredPublicKey ¶
func ArmoredPublicKey(signer crypto.Signer, name, email string, creationTime time.Time) ([]byte, error)
ArmoredPublicKey builds a self-signed OpenPGP key from signer and returns its ASCII-armored *public* half — ready to embed (internal/trustkeys/keys) or publish via WKD. creationTime is baked into the key and its self-signature; keep it stable (rotations get a new key, not a new timestamp on the same key).
func Entity ¶
func Entity(signer crypto.Signer, name, email string, creationTime time.Time) (*openpgp.Entity, error)
Entity assembles a self-signed, signing-capable OpenPGP entity around signer: an RSA public-key packet, an RSA private-key packet driven by signer (so KMS-held keys work), a User ID, and a positive-cert self-signature.
Callers that need only the armored public half should prefer ArmoredPublicKey, which wraps Entity + armor.Encode + Entity.Serialize in one call. Direct callers of Entity get both halves and can drive Entity.Serialize / Entity.SerializePrivate themselves — useful when writing both halves to separate files, which is what `gtb keys generate` does for the RSA path.
Supports opaque signers (any crypto.Signer with *rsa.PublicKey from Public()) — go-crypto's RSA signing path dispatches via the crypto.Signer interface, so a KMS-backed signer works.
Non-RSA signers return ErrUnsupportedKeyType. Ed25519 generation is handled separately in `internal/cmd/keys/generate.go` via openpgp.NewEntity with PubKeyAlgoEdDSA — see the package doc for the architectural rationale.
func WKDHash ¶
WKDHash returns the z-base-32 encoded SHA-1 of the lowercased local-part of an email — i.e. the filename used under hu/ in the WKD layout. Exposed so callers can pre-compute the expected URL or hash a UID without writing files.
func WriteArmoredPublicKey ¶
func WriteArmoredPublicKey(w io.Writer, signer crypto.Signer, name, email string, creationTime time.Time) error
WriteArmoredPublicKey writes the same content as ArmoredPublicKey directly to w. The two-function shape exists so callers that want to stream into a file or socket can skip the intermediate buffer; it also lets the test suite inject a failing writer to exercise the error-wrapping branches around armor.Encode / Close.
func WriteWKDTree ¶
WriteWKDTree writes a complete WKD directory layout under outDir.
For each unique Email across entries, it computes the WKD hu/<hash> filename, concatenates that email's binary OpenPGP key packets (sorted by primary-key fingerprint for reproducible output), and writes the resulting file. Alongside hu/ it writes a zero-byte policy file (RFC-required) and, if opts.SubmissionAddress is non-empty, a submission-address file.
The full layout for advanced method (the default):
outDir/.well-known/openpgpkey/<domain>/ ├── policy (zero bytes) ├── submission-address (optional — only when opts.SubmissionAddress != "") └── hu/<z-base-32-hash> (one per unique email)
Direct method drops the <domain>/ level:
outDir/.well-known/openpgpkey/ ├── policy ├── submission-address? └── hu/<z-base-32-hash>
Returns the relative paths (under outDir) of every file written, in the order written, for callers that want to log or assert against the result.
WriteWKDTree refuses to operate outside outDir/.well-known/openpgpkey (path-traversal defence against pathological domain values).
Errors:
- if domain is empty, contains a path separator, or is otherwise invalid as a hostname;
- if an Entry's Email cannot be parsed by net/mail;
- if a key cannot be parsed as armored or binary OpenPGP;
- if no entries are supplied (an empty WKD tree is never useful).
Types ¶
type Entry ¶
type Entry struct {
// Email must contain a parseable RFC 5322 address. Only the
// local-part is used (lowercased, ASCII) for WKD hashing.
Email string
// Keys are the public-key bytes to publish under Email. Each
// element may be either ASCII-armored or already-binary OpenPGP;
// WriteWKDTree auto-detects and dearmors as needed before writing
// the hu/ file (which must contain binary packets per RFC §3.1).
Keys [][]byte
}
Entry binds one email address to the public keys to publish under it. The Email's local part is what gets hashed into the hu/<hash> filename; the Keys are concatenated (binary OpenPGP packets) into that file.
Multiple Entry values with the same Email merge: their Keys are pooled into a single hu/ file. Re-using Entry values with the same Email is therefore equivalent to passing a single Entry with all the keys.
type Method ¶
type Method string
Method selects the WKD URL layout per draft-koch-openpgp-webkey-service §3.1.
const ( // MethodAdvanced is the dedicated-subdomain layout served from // openpgpkey.<domain>; URLs include an extra <domain>/ path // segment (...openpgpkey/<domain>/hu/<hash>). This is the layout // used by the gtb release-signing endpoint at // openpgpkey.phpboyscout.uk. MethodAdvanced Method = "advanced" // MethodDirect is the same-domain layout served from <domain> // itself; URLs omit the extra <domain>/ segment // (...openpgpkey/hu/<hash>). GPG clients fall back to this when // the advanced URL is missing. MethodDirect Method = "direct" )
type Options ¶
type Options struct {
// Method is the URL layout to emit. Zero value defaults to
// MethodAdvanced (the dedicated-subdomain layout).
Method Method
// SubmissionAddress, if non-empty, is written to the domain
// directory's submission-address file. WKS-aware clients fetch
// this to discover where to send key-submission requests. Empty
// means "do not emit submission-address" (still emits policy,
// which is always required by RFC §3.1).
SubmissionAddress string
}
Options configures non-per-entry WKD emission settings.