awskms

package module
v0.6.2 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 16 Imported by: 3

README

signing-aws-kms

AWS KMS signing backend for the phpboyscout signing module

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base. Docs: signing.go.phpboyscout.uk


gitlab.com/phpboyscout/go/signing-aws-kms is the AWS KMS signing backend for gitlab.com/phpboyscout/go/signing.

It implements the signing.Backend contract, wrapping a KMS-held key as a crypto.Signer. The private half never leaves AWS: every signing operation is a remote kms:Sign call. Blank-import to activate.

Two key types are supported, serving two release paths:

Key spec Path Notes
RSA_4096 (SIGN_VERIFY) OpenPGP manifest signing Sign takes a pre-hashed digest
ECC_NIST_EDWARDS25519 minisign artefact signing Sign takes the message itself, crypto.Hash(0)

The Ed25519 path exists because the Rust consumers (cargo-binstall, rtb-update) speak minisign, not OpenPGP. Hash the artefact first and sign the digest, because KMS caps its Sign message at 4096 bytes, so a multi-MB archive can never be the message. go/signing's minisign package does this for you, and HSM custody is identical to the RSA key's.

import (
    "gitlab.com/phpboyscout/go/signing"
    _ "gitlab.com/phpboyscout/go/signing-aws-kms" // registers "aws-kms"
)

backend, _ := signing.Get("aws-kms")
signer, _ := backend.NewSigner(ctx, "arn:aws:kms:eu-west-2:123456789012:key/abcd-…")
// use signer with gitlab.com/phpboyscout/go/signing/openpgpkey to sign a release manifest

Why a separate module

This is one of a family of per-provider backend modules. Each cloud/remote backend lives in its own module so a consumer pulls only the SDK(s) it actually uses: importing signing-aws-kms adds the AWS SDK and nothing else; a project that only verifies releases imports neither AWS nor this module. See the backend-module pattern.

  • Footprint: signing, the AWS SDK, and the toolkit's own go/awsclient and go/errors. No go-tool-base, no other cloud SDK, no Cobra or Viper; a dependency-footprint test fails the build if one appears.
  • Seams: AWS credentials via the SDK default chain; the region defaults to eu-west-2 and does not follow AWS_REGION (set it with WithRegion or --kms-region; see the how-to); an optional *slog.Logger for DEBUG diagnostics (WithLogger).

Documentation

Licence

MIT. See LICENSE.

Documentation

Overview

Package kms is the AWS KMS backend for pkg/signing. Blank-import to activate; the init() call registers the backend under the name "aws-kms" against pkg/signing's global registry.

import _ "gitlab.com/phpboyscout/go/signing/kms"

The backend wraps a KMS-held asymmetric signing key as a crypto.Signer. The private half never leaves AWS — every signing operation is a remote kms:Sign call.

Two key types are supported, serving two different release paths:

  • **RSA SIGN_VERIFY** — drives pkg/openpgpkey to mint an OpenPGP-armored public key and sign the checksums manifest.
  • **ECC_NIST_EDWARDS25519** — signs release artefacts for the Rust consumers, which speak minisign rather than OpenPGP. Hash the artefact first and sign the digest: KMS caps its Sign message at 4096 bytes, so a multi-MB archive can never be the message. go/signing's minisign package does this, and the resulting HSM custody is identical to the RSA key's — the private half never materialises.

Ed25519 keys are not usable with pkg/openpgpkey: go-crypto's OpenPGP Ed25519 branch requires a concrete *ed25519.PrivateKey and offers no crypto.Signer fall-through, so it cannot be driven by an opaque KMS signer at all.

The AWS configuration is resolved by go/awsclient, and its lifecycle by go/clientlifecycle — one implementation for the estate rather than a copy per adapter (org spec 0003). The default rung resolves PER CALL and holds no credentials between mints (0003 P-12): one kms:Sign per mint, nothing retained. A consumer that already holds a config, a client, or a shared resolver injects it through the ladder rather than resolving again.

Backend-specific CLI flag:

--kms-region <region>   AWS region. Default: eu-west-2.

The default is applied as an explicit region, so AWS_REGION and the shared config's region are not consulted. Name another with --kms-region or WithRegion, or inject a resolved config with NewFromConfig or NewFromSource.

Credentials are resolved from the AWS SDK default chain (env vars / ~/.aws/credentials / IAM Roles Anywhere / OIDC web identity). Users with multiple profiles can either set AWS_PROFILE before invoking the minter or assume a role explicitly via `aws sts assume-role` and export the resulting credentials.

Example

Example shows that blank-importing this module registers the "aws-kms" backend with the signing registry, so it can be resolved by name. From the resolved Backend, NewSigner(ctx, keyID) returns a crypto.Signer over a KMS RSA SIGN_VERIFY key (keyID is the key ARN or alias) — that call needs AWS credentials, so it is shown in the how-to rather than run here.

package main

import (
	"fmt"

	"gitlab.com/phpboyscout/go/signing"

	_ "gitlab.com/phpboyscout/go/signing-aws-kms"
)

func main() {
	backend, err := signing.Get("aws-kms")
	if err != nil {
		panic(err)
	}

	fmt.Println(backend.Name())
}
Output:
aws-kms

Index

Examples

Constants

View Source
const (
	// BackendName is the identifier the zero-conf default registers under, and
	// the one a caller selects this backend by with "--backend aws-kms".
	BackendName = "aws-kms"
)

Variables

View Source
var (
	// ErrUnsupportedKMSKeyType is returned by NewSigner when the KMS
	// key's public half is neither RSA nor Ed25519 — for instance one
	// of the ECC_NIST_P* curves.
	ErrUnsupportedKMSKeyType = errors.NewSentinel("signing-aws-kms.unsupported_k_m_s_key_type", "KMS key is neither RSA nor Ed25519; only RSA SIGN_VERIFY and ECC_NIST_EDWARDS25519 keys are supported")

	// ErrKeyUnusable is returned by NewSigner when the KMS key's
	// KeyUsage is not SIGN_VERIFY. An RSA ENCRYPT_DECRYPT key parses
	// exactly like a signing key and would otherwise fail per signature.
	ErrKeyUnusable = errors.NewSentinel("signing-aws-kms.key_unusable", "KMS key usage is not SIGN_VERIFY")

	// ErrKeyRotated is returned by Sign when KMS reports signing with a
	// different key than the one Public() was resolved from, as happens
	// when an alias is repointed. Mint a new signer.
	ErrKeyRotated = errors.NewSentinel("signing-aws-kms.key_rotated", "KMS signed with a different key than the signer resolved")

	// ErrUnsupportedHashFunc is returned by the signer when the
	// caller requests a hash function KMS RSA Sign does not map to.
	ErrUnsupportedHashFunc = errors.NewSentinel("signing-aws-kms.unsupported_hash_func", "unsupported hash function; KMS RSA Sign accepts SHA-256 / 384 / 512 only")

	// ErrEd25519RequiresUnhashed is returned when a caller asks an
	// Ed25519 signer for a pre-hashed signature. EdDSA signs the
	// message itself, so the standard library's convention is
	// crypto.Hash(0); honouring any other hash would sign the wrong
	// bytes.
	ErrEd25519RequiresUnhashed = errors.NewSentinel("signing-aws-kms.ed25519_requires_unhashed", "Ed25519 signing requires crypto.Hash(0) — EdDSA signs the message, not a digest")

	// ErrMessageTooLarge is returned when an Ed25519 Sign call's
	// message exceeds the KMS Sign API's 4096-byte cap. Sign a digest
	// of the artefact instead of the artefact itself.
	ErrMessageTooLarge = errors.NewSentinel("signing-aws-kms.message_too_large", "message exceeds the KMS Sign 4096-byte limit; sign a digest instead")

	// ErrUnexpectedSignatureEncoding is returned when KMS returns an
	// Ed25519 signature that is not the raw 64-byte form every
	// consumer requires.
	ErrUnexpectedSignatureEncoding = errors.NewSentinel("signing-aws-kms.unexpected_signature_encoding", "KMS returned a non-raw Ed25519 signature")

	// ErrPSSUnsupported is returned by the signer when the caller requests
	// RSASSA-PSS via *rsa.PSSOptions. This backend only implements
	// RSASSA-PKCS1-v1_5; it refuses rather than silently downgrading the
	// signature scheme behind the caller's back.
	ErrPSSUnsupported = errors.NewSentinel("signing-aws-kms.p_s_s_unsupported", "RSASSA-PSS is not supported; this KMS signer only implements RSASSA-PKCS1-v1_5")

	// ErrNotConfigured is returned by the ladder constructors for a wiring
	// fault caught at construction: an empty backend name, a nil client, a
	// nil source, or a config with no region.
	ErrNotConfigured = errors.NewSentinel("signing-aws-kms.not_configured", "aws-kms backend is not configured")
)

Exported sentinel errors so callers (and tests) can errors.Is against specific failure modes.

Functions

func New

func New(opts ...Option) signing.Backend

New constructs the AWS KMS Backend with the supplied options, named "aws-kms" and resolving its configuration per call from the ambient chain. Importing this package has already registered an "aws-kms" default, and signing.Register panics on a duplicate, so use the result directly:

backend := awskms.New(awskms.WithRegion("us-east-1"), awskms.WithLogger(l))

It is the NewDefault rung under the fixed name; a caller who needs a differently named instance, or to supply its own client, config or shared resolver, uses the ladder directly.

func NewDefault added in v0.5.0

func NewDefault(name string, opts ...Option) (signing.Backend, error)

NewDefault builds a named backend that resolves its own configuration per call from the ambient AWS chain, holding no credentials between mints. This is the rung the blank-import default uses.

func NewFromClient added in v0.5.0

func NewFromClient(name string, client kmsClient, opts ...Option) (signing.Backend, error)

NewFromClient builds a named backend around a KMS client the caller already holds — a shared client, a custom endpoint, an assumed-role client. The client is used as-is.

func NewFromConfig added in v0.5.0

func NewFromConfig(name string, cfg aws.Config, opts ...Option) (signing.Backend, error)

NewFromConfig builds a named backend around an aws.Config the caller resolved. The client is constructed with kms.NewFromConfig (no I/O). The region is checked now rather than left to fail per call.

func NewFromSource added in v0.5.0

func NewFromSource(name string, src configSource, opts ...Option) (signing.Backend, error)

NewFromSource builds a named backend that resolves its AWS configuration through the supplied source. It is the sharing seam (org 0003 P-5): a consumer resolves one chain with go/awsclient — memoised or per-call — and hands the same source to the config, signing and encryption adapters.

func NewSigner

func NewSigner(ctx context.Context, region, keyID string, opts ...Option) (crypto.Signer, error)

NewSigner is the programmatic constructor for callers that don't want to go through the global signing registry. Most callers should use signing.Get("aws-kms") instead; this exists for integration tests and for tool authors wiring signing into their own command structure.

region is the AWS region the key lives in (an explicit positional value; WithRegion in opts overrides it when non-empty). It builds a one-off default backend and mints from it.

Types

type Option

type Option func(*settings)

Option configures the backend or a programmatic signer.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger injects a *slog.Logger for diagnostic (DEBUG-level) output. nil disables logging. Consistent with the signing module's seam conventions; gtb passes slog.New(<its handler>).

Logging policy: DEBUG records carry only non-secret identifiers — the KMS key id, AWS region, key bit-length, and signing algorithm. The library never logs credentials, the message digest, or the signature bytes. Nothing is logged above DEBUG.

func WithRegion

func WithRegion(region string) Option

WithRegion overrides the AWS region (default "eu-west-2"). An empty string is ignored so the default — or a flag-bound value — survives.

Jump to

Keyboard shortcuts

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