Skip to content

Sign with AWS KMS

Sign a release manifest with a key held in AWS KMS, where the private half never leaves AWS — every signature is a remote kms:Sign call. The backend is the standalone module gitlab.com/phpboyscout/signing-aws-kms; it implements signing.Backend and registers as aws-kms.

This mirrors Sign with the local backend — the only difference is the backend module and key setup. See Backends and the per-provider module pattern for why the AWS SDK lives in its own module.

Prerequisites

  • An RSA SIGN_VERIFY customer master key in AWS KMS. KMS does not expose Ed25519 for asymmetric signing, so RSA is the only option; use RSA 4096 (the verify side rejects RSA keys below 3072 bits).
  • AWS credentials resolvable by the SDK default chain (environment, ~/.aws/credentials / AWS_PROFILE, IAM role, or OIDC web identity).
  • The IAM principal needs kms:GetPublicKey and kms:Sign on the key.
  • The key's ARN or alias.

Add the backend

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

The blank import runs the backend's init(), which registers it with the signing registry under the name aws-kms.

Resolve a signer

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

signer, err := backend.NewSigner(ctx, "arn:aws:kms:eu-west-2:123456789012:key/abcd-…")
if err != nil {
    return err
}

keyID is the KMS key ARN or alias. Region resolves from AWS_REGION (or the --kms-region CLI flag — see below); the default is eu-west-2.

Sign a manifest and publish the key

Identical to every other backend — the signer is a stdlib crypto.Signer:

import "gitlab.com/phpboyscout/signing/openpgpkey"

pub, err := openpgpkey.ArmoredPublicKey(signer, "Release", "[email protected]", time.Now())
// publish `pub`: embed it in consumers, or serve it via WKD
//   (see "Publish keys via WKD")

sig, err := openpgpkey.DetachSign(signer, pub, bytes.NewReader(manifest), time.Now())
// ship `manifest`, `sig`, and `pub` alongside the release

Consumers verify with signing/verify exactly as in Verify a release.

Programmatic configuration (region, logger)

Outside the registry, construct a configured signer or backend directly with functional options:

import (
    "log/slog"

    awskms "gitlab.com/phpboyscout/signing-aws-kms"
)

// region + logger via options
signer, err := awskms.NewSigner(ctx, "us-east-1", keyARN,
    awskms.WithLogger(slog.Default()))

// or register a configured backend under "aws-kms"
signing.Register(awskms.New(awskms.WithRegion("us-east-1")))

The optional *slog.Logger emits DEBUG-level diagnostics carrying only non-secret identifiers (key id, region, key bit-length, algorithm) — never credentials, the digest, or signature bytes.

CLI flag

The backend implements an optional flag-registration interface that a CLI front-end type-asserts for; it contributes --kms-region. The canonical CLI is:

gtb sign --backend aws-kms --key-id <arn|alias> --kms-region <region> 

Credentials & IAM

No credential logic lives in the backend — it relies entirely on the AWS SDK default chain. For CI, OIDC web-identity federation (AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN) is the recommended keyless path; scope the role's trust policy to the specific pipeline (ref/tag) so a leaked ARN can't be assumed elsewhere.

Limitations

  • RSA only — a non-RSA KMS key returns ErrUnsupportedKMSKeyType.
  • RSASSA-PKCS1-v1_5 only — a caller requesting RSASSA-PSS gets ErrPSSUnsupported (no silent scheme downgrade). This covers the OpenPGP RSA signing path used here.

See also