Skip to content

feat: add AwsKmsSigner#63

Open
JKapostins wants to merge 1 commit intoLibPDF-js:mainfrom
JKapostins:feat/aws-kms-signer
Open

feat: add AwsKmsSigner#63
JKapostins wants to merge 1 commit intoLibPDF-js:mainfrom
JKapostins:feat/aws-kms-signer

Conversation

@JKapostins
Copy link
Copy Markdown

@JKapostins JKapostins commented Apr 22, 2026

Summary

Adds an AwsKmsSigner alongside the existing GoogleKmsSigner for signing PDFs with asymmetric keys stored in AWS Key Management Service, including FIPS 140-2-validated HSM-backed keys. The private key never leaves KMS — only the digest is sent for signing.

import { PDF, AwsKmsSigner } from "@libpdf/core";

const signer = await AwsKmsSigner.create({
  keyId: "arn:aws:kms:us-east-1:123456789012:key/abcd-...",
  certificate,
});

const { bytes } = await pdf.sign({ signer });

Why

Downstream consumers that run on AWS have been forced to stand up a separate GCP project just for signing-key storage. Since the CA/Browser Forum's June 2023 rules, AATL CAs require FIPS-HSM-backed keys — so P12Signer alone isn't enough for AATL-trusted signatures in production. This unblocks the AWS-native path.

Scope

Mirrors GoogleKmsSigner exactly:

  • AwsKmsSigner.create({ keyId, region?, certificate, certificateChain?, buildChain?, chainTimeout?, client?, signingAlgorithm? })
  • Static helper AwsKmsSigner.getCertificateFromSecretsManager(secretId, options?)
  • Same Signer interface, same cert/SPKI validation (pkijs + asn1js, via the existing #src/helpers/*)
  • Same AIA chain building via buildCertificateChain (opt-in buildChain)
  • Dynamic imports with ERR_MODULE_NOT_FOUNDKmsSignerError guidance
  • Error mapping adapted from gRPC status codes → AWS SDK service exception names (NotFoundException, AccessDeniedException, DisabledException, KMSInvalidStateException, ResourceNotFoundException)

Differences from GoogleKmsSigner

  1. Multi-algorithm keys. AWS KMS keys can support multiple signing algorithms per key (RSA_2048RSASSA_PKCS1_V1_5_SHA_{256,384,512} + RSASSA_PSS_SHA_{256,384,512}). create() reads SigningAlgorithms from GetPublicKey and picks the first one unless the caller passes signingAlgorithm explicitly. GCP's per-key-version algorithm is single-valued so this nuance doesn't apply there.
  2. No credential tempfile shuffle. The AWS SDK reads AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/role metadata from the default chain directly — no GOOGLE_APPLICATION_CREDENTIALS_CONTENTS-style base64 tempfile writing is needed.

Files

  • New: src/signatures/signers/aws-kms.ts (class + helpers)
  • New: src/signatures/signers/aws-kms.test.ts (16 unit tests — algorithm mapping + isRsaPss predicate, matches the google-kms.test.ts pattern)
  • New: content/docs/guides/signatures/aws-kms.mdx (setup guide mirroring google-kms.mdx)
  • Modified: src/signatures/signers/index.ts, src/signatures/index.ts, src/index.ts — add the AwsKmsSigner export
  • Modified: package.json@aws-sdk/client-kms + @aws-sdk/client-secrets-manager added as optional peer dependencies (same pattern as the @google-cloud/* peers)
  • Modified: content/docs/guides/signatures/index.mdx, meta.json — link the new page

Testing

  • bun run typecheck — clean
  • bun run lint — clean (the 4 pre-existing warnings are unchanged)
  • bun run test:run src/signatures/signers/ — 65 tests pass (16 new, 37 google-kms, 12 crypto-key)
  • bun run build — produces a valid dist bundle
  • End-to-end validated in a downstream consumer (documenso/documenso#2718): a self-hosted Documenso instance deployed with @libpdf/core + this class. Created an RSA 2048 asymmetric KMS key, generated a self-signed cert against it (signing the CSR via KMS), signed a PDF end-to-end. Verified the embedded PKCS#7 CMS signature against the KMS public key with openssl cms -verify -binary → "CMS Verification successful".

Happy to split into smaller PRs or iterate on the API shape if preferred.

Adds an `AwsKmsSigner` alongside `GoogleKmsSigner` for signing PDFs with
keys stored in AWS Key Management Service, including HSM-backed keys.
The private key never leaves KMS — only the digest is sent for signing.

Mirrors the GoogleKmsSigner shape:
  - `AwsKmsSigner.create({ keyId, region?, certificate, ... })`
  - `AwsKmsSigner.getCertificateFromSecretsManager(secretId, options?)`
  - Same `Signer` interface, same cert/SPKI validation, same AIA chain
    building, same internal error mapping (adapted from gRPC → AWS SDK
    service exception names).

Differences from Google's API:
  - AWS KMS keys can support multiple signing algorithms per key
    (RSA_2048 → PKCS1_V1_5_SHA_{256,384,512} + PSS_SHA_{256,384,512}).
    `AwsKmsSigner.create()` reads `SigningAlgorithms` from GetPublicKey
    and picks the first one unless the caller passes
    `signingAlgorithm` explicitly.
  - AWS SDK credentials resolve via the default chain (IAM role, env
    vars, instance profile) — no equivalent of
    `GOOGLE_APPLICATION_CREDENTIALS` handling is needed.

Adds `@aws-sdk/client-kms` and `@aws-sdk/client-secrets-manager` as
optional peer dependencies (mirrors the `@google-cloud/*` peer-dep
pattern). Unit tests cover algorithm mapping and the RSA-PSS predicate.

Validated end-to-end in a downstream consumer (Documenso): generated a
self-signed cert against an AWS KMS key, signed a PDF, verified the
embedded PKCS#7 CMS signature against the KMS public key via
`openssl cms -verify -binary`.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 22, 2026

@JKapostins is attempting to deploy a commit to the mythie's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant