From 0a9ea80d953cf02fd126b943208174ff76177748 Mon Sep 17 00:00:00 2001 From: Jacob Kapostins Date: Wed, 22 Apr 2026 14:16:59 -0400 Subject: [PATCH] feat: add AwsKmsSigner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- bun.lock | 171 +++++- content/docs/guides/signatures/aws-kms.mdx | 188 ++++++ content/docs/guides/signatures/index.mdx | 16 +- content/docs/guides/signatures/meta.json | 2 +- package.json | 10 + src/index.ts | 1 + src/signatures/index.ts | 8 +- src/signatures/signers/aws-kms.test.ts | 141 +++++ src/signatures/signers/aws-kms.ts | 657 +++++++++++++++++++++ src/signatures/signers/index.ts | 1 + 10 files changed, 1190 insertions(+), 5 deletions(-) create mode 100644 content/docs/guides/signatures/aws-kms.mdx create mode 100644 src/signatures/signers/aws-kms.test.ts create mode 100644 src/signatures/signers/aws-kms.ts diff --git a/bun.lock b/bun.lock index fa00eed..7dcf291 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "pdfbox-ts", @@ -14,6 +13,8 @@ "pkijs": "^3.3.3", }, "devDependencies": { + "@aws-sdk/client-kms": "^3.0.0", + "@aws-sdk/client-secrets-manager": "^3.0.0", "@cantoo/pdf-lib": "^2.6.1", "@google-cloud/kms": "^5.0.0", "@google-cloud/secret-manager": "^6.0.0", @@ -31,16 +32,84 @@ "vitest": "^4.0.16", }, "peerDependencies": { + "@aws-sdk/client-kms": "^3.0.0", + "@aws-sdk/client-secrets-manager": "^3.0.0", "@google-cloud/kms": "^5.0.0", "@google-cloud/secret-manager": "^6.0.0", }, "optionalPeers": [ + "@aws-sdk/client-kms", + "@aws-sdk/client-secrets-manager", "@google-cloud/kms", "@google-cloud/secret-manager", ], }, }, "packages": { + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-kms": ["@aws-sdk/client-kms@3.1034.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.3", "@aws-sdk/credential-provider-node": "^3.972.34", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.33", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.19", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.16", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.31", "@smithy/middleware-retry": "^4.5.4", "@smithy/middleware-serde": "^4.2.19", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.0", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.48", "@smithy/util-defaults-mode-node": "^4.2.53", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.3", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-SJNtvd7ft/LXTN37ELP8hmM2iE4VPOyaoyg81IUyDUXJ/7fmduLBEuwDSgB19i8/aCzwEQwYM8GYF2DbF4a8fw=="], + + "@aws-sdk/client-secrets-manager": ["@aws-sdk/client-secrets-manager@3.1034.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.3", "@aws-sdk/credential-provider-node": "^3.972.34", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.33", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.19", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.16", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.31", "@smithy/middleware-retry": "^4.5.4", "@smithy/middleware-serde": "^4.2.19", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.0", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.48", "@smithy/util-defaults-mode-node": "^4.2.53", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.3", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-t0OVwmzNzkDswx+vFa1r3JWX0FFpzi1vkGXRtLujzeLKarTB92GjAVNqDzrjShAsf3xLLwtWWh7R9cmr6zidZQ=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.974.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.18", "@smithy/core": "^3.23.16", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.3", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W3aJJm2clu8OmsrwMOMnfof13O6LGnbknnZIQeSRbxjqKah2nVvkjbUBBZVhWrt08KC69H7WsINTdrxC/2SXQw=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-rf+AlUxgTeSzQ/4zoS0D+Bt7XvgpY48PnWG8Yg/N9fdMgyK2Jaqa+6tLZp4MYMIMHkGrfAxnbSeb2YLMGFMg6g=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.0", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.24", "tslib": "^2.6.2" } }, "sha512-TR2/lQ3qKFj2EOrsiASzemsNEz2uzZ/SUBf48+U4Cr9a/FZlHfH/hwAeBJNBp1gMyJNxROJZhT3dn1cO+jnYfQ=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.33", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/credential-provider-env": "^3.972.29", "@aws-sdk/credential-provider-http": "^3.972.31", "@aws-sdk/credential-provider-login": "^3.972.33", "@aws-sdk/credential-provider-process": "^3.972.29", "@aws-sdk/credential-provider-sso": "^3.972.33", "@aws-sdk/credential-provider-web-identity": "^3.972.33", "@aws-sdk/nested-clients": "^3.997.1", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-UwdbJbOrgnOxZbshaNZ4DzX35h5wQd33MNYTGzWhN3ORG9lG9KQbDX6l6tDJSAdaGTktJoZPSritmUoW1rYkRA=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.33", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/nested-clients": "^3.997.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WyZuPVoDM1HGNl41eVg8HSSXIB+FGkuuK63GhDbh4TMdfWU03AciWvF/QqOVWvJtWVYaLddANJ+aUklVr2ieuw=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.34", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.29", "@aws-sdk/credential-provider-http": "^3.972.31", "@aws-sdk/credential-provider-ini": "^3.972.33", "@aws-sdk/credential-provider-process": "^3.972.29", "@aws-sdk/credential-provider-sso": "^3.972.33", "@aws-sdk/credential-provider-web-identity": "^3.972.33", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-sPcisURibKU4x0PCWJkWF1KJYm49Cph9dCn/PAnG5FU0wq5Id3g2v7RuEWAtNlKv1Af4gUJYBVGOeNpSEEx41A=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-DURisqWS3bUgiwMXTmzymVNGlcRW0FnbPZ3SZknhmxnCXm3n9idkTJ6T+Uir359KRKtJNFLRViskk8HsSVLi1w=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.33", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/nested-clients": "^3.997.1", "@aws-sdk/token-providers": "3.1034.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-9y9obU4IQWru9f+NiiscUeyCe5ZmQav4FKEb1qfUNrik/C3BzBGUnHQWyPEyXjOX9cb+vx1TYx0qZBtinKdzTA=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.33", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/nested-clients": "^3.997.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-RazhlN0YAkna2T2p2v4YuuRlVBVRNo8V0SL+9JePTWDndEUAeOBAjYeQfAMbtDyCh120+zA0Op6V0jS4dw2+iw=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.32", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.16", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.24", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-dc2O2x0V5pGJhmdQYQveUIFtMZsur7GrGuSgoKM4oQJuEcfvwnJ3sj+ip6WnxR5l6TrX5zkl4KgcgswOy3wAzQ=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.33", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@smithy/core": "^3.23.16", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.3", "tslib": "^2.6.2" } }, "sha512-mqtT3Fo7xanWMk2SbAcKLGGI/q1GHWNrExBj7cnWP2W2mkTMheXB4ntJvwPZ1UxPrQobrsv2dWFXmaOJeSOiDg=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.1", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.33", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/signature-v4-multi-region": "^3.996.20", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.19", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.16", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.31", "@smithy/middleware-retry": "^4.5.4", "@smithy/middleware-serde": "^4.2.19", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.0", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.48", "@smithy/util-defaults-mode-node": "^4.2.53", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.3", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Afc9hc2WZs3X4Jb8dnxyuYiZsLoWRO51roTCRf497gPnAKN2WRdXANu1vaVCTzwnDMOYFXb/cYv4ZSjxqAqcKA=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.13", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.20", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.32", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-MEj6DhEcaO8RgVtFCJ+xpCQnZC3Iesr09avdY75qkMQfckQULu447IegK7Rs1MCGerVBfKnJQ4q+pQq9hI5lng=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1034.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.3", "@aws-sdk/nested-clients": "^3.997.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-8E+KGcD4ET0H9FXJ2/ZWbfFnQNYEkTZZYJxAs1lkdJlve1AYuqaydInIFfvNgoz5GbYtzbK8/ugsSMu5wPm6kA=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.2", "tslib": "^2.6.2" } }, "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.19", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.33", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-ZAfHjpzdbrzkAftC139JoYGfXzDh5HY+AxRzw8pGJ8cULsf+l721sKAMK8mV5NvRETaW/BwghSwQhGgoNgrxMw=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.18", "", { "dependencies": { "@smithy/types": "^4.14.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -287,6 +356,84 @@ "@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.17", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ=="], + + "@smithy/core": ["@smithy/core@3.23.16", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.24", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-JStomOrINQA1VqNEopLsgcdgwd42au7mykKqVr30XFw89wLt9sDxJDi4djVPRwQmmzyTGy/uOvTc2ultMpFi1w=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.31", "", { "dependencies": { "@smithy/core": "^3.23.16", "@smithy/middleware-serde": "^4.2.19", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-KJPdCIN2kOE2aGmqZd7eUTr4WQwOGgtLWgUkswGJggs7rBcQYQjcZMEDa3C0DwbOiXS9L8/wDoQHkfxBYLfiLw=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.4", "", { "dependencies": { "@smithy/core": "^3.23.16", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.3.0", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.3", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-/z7nIFK+ZRW3Ie/l3NEVGdy34LvmEOzBrtBAvgWZ/4PrKX0xP3kWm8pkfcwUk523SqxZhdbQP9JSXgjF77Uhpw=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.19", "", { "dependencies": { "@smithy/core": "^3.23.16", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Q6y+W9h3iYVMCKWDoVge+OC1LKFqbEKaq8SIWG2X2bWJRpd/6dDLyICcNLT6PbjH3Rr6bmg/SeDB25XFOFfeEw=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.6.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-P734cAoTFtuGfWa/R3jgBnGlURt2w9bYEBwQNMKf58sRM9RShirB2mKwLsVP+jlG/wxpCu8abv8NxdUts8tdLA=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.3.0", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-9jKsBYQRPR0xBLgc2415RsA5PIcP2sis4oBdN9s0D13cg1B1284mNTjx9Yc+BEERXzuPm5ObktI96OxsKh8E9A=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.12.12", "", { "dependencies": { "@smithy/core": "^3.23.16", "@smithy/middleware-endpoint": "^4.4.31", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.24", "tslib": "^2.6.2" } }, "sha512-daO7SJn4eM6ArbmrEs+/BTbH7af8AEbSL3OMQdcRvvn8tuUcR5rU2n6DgxIV53aXMS42uwK8NgKKCh5XgqYOPQ=="], + + "@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.48", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hxVRVPYaRDWa6YQdse1aWX1qrksmLsvNyGBKdc32q4jFzSjxYVNWfstknAfR228TnzS4tzgswXRuYIbhXBuXFQ=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.53", "", { "dependencies": { "@smithy/config-resolver": "^4.4.17", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.12", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-ybgCk+9JdBq8pYC8Y6U5fjyS8e4sboyAShetxPNL0rRBtaVl56GSFAxsolVBIea1tXR4LPIzL8i6xqmcf0+DCQ=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.3.3", "", { "dependencies": { "@smithy/service-error-classification": "^4.3.0", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-idjUvd4M9Jj6rXkhqw4H4reHoweuK4ZxYWyOrEp4N2rOF5VtaOlQGLDQJva/8WanNXk9ScQtsAb7o5UHGvFm4A=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.24", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.0", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-na5vv2mBSDzXewLEEoWGI7LQQkfpmFEomBsmOpzLFjqGctm0iMwXY5lAwesY9pIaErkccW0qzEOUcYP+WKneXg=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], @@ -347,6 +494,8 @@ "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -419,6 +568,10 @@ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "fast-xml-builder": ["fast-xml-builder@1.1.5", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA=="], + + "fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], @@ -553,6 +706,8 @@ "pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], @@ -639,6 +794,8 @@ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="], + "stubs": ["stubs@3.0.0", "", {}, "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -697,6 +854,10 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@cantoo/pdf-lib/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -747,6 +908,10 @@ "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -803,6 +968,10 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/content/docs/guides/signatures/aws-kms.mdx b/content/docs/guides/signatures/aws-kms.mdx new file mode 100644 index 0000000..373681f --- /dev/null +++ b/content/docs/guides/signatures/aws-kms.mdx @@ -0,0 +1,188 @@ +--- +title: AWS KMS +description: Sign PDFs with asymmetric keys stored in AWS Key Management Service (KMS), including FIPS 140-2-validated HSM-backed keys. +--- + +# AWS KMS + +Sign PDFs using asymmetric keys stored in AWS Key Management Service (KMS). Ideal for AWS-native deployments where private keys must stay on FIPS-validated hardware for compliance reasons. + + + The private key never leaves KMS — only the digest is sent for signing. + + +## Installation + +The AWS KMS client is an optional peer dependency: + +```bash +npm install @aws-sdk/client-kms +``` + +For loading certificates from AWS Secrets Manager: + +```bash +npm install @aws-sdk/client-secrets-manager +``` + +## Quick Start + +```typescript +import { PDF, AwsKmsSigner } from "@libpdf/core"; +import { readFile, writeFile } from "fs/promises"; + +// Load your DER-encoded certificate (issued by your CA for the KMS key) +const certificate = await readFile("certificate.der"); + +// Create signer with KMS key reference +const signer = await AwsKmsSigner.create({ + keyId: "arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-...", + certificate, +}); + +// Sign the PDF +const pdf = await PDF.load(await readFile("document.pdf")); +const { bytes } = await pdf.sign({ signer }); + +await writeFile("signed.pdf", bytes); +``` + +--- + +## Authentication + +`AwsKmsSigner` uses the [AWS SDK default credential chain](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html). Common authentication methods: + +| Method | Environment | Setup | +| ----------------------- | ------------------------ | ---------------------------------------------------------------------- | +| IAM Role | EC2/ECS/Lambda/EKS | Attach a role; credentials resolve automatically via instance metadata | +| Environment Variables | Any | Set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` | +| Shared Credentials File | Local development | `aws configure` writes `~/.aws/credentials` | +| Web Identity (OIDC) | EKS IRSA, GitHub Actions | Set `AWS_WEB_IDENTITY_TOKEN_FILE` and `AWS_ROLE_ARN` | + +### Required IAM Permissions + +The authenticating principal needs these IAM actions on the key: + +- `kms:Sign` — sign with the key +- `kms:GetPublicKey` — read public key metadata for validation + +Minimal policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["kms:Sign", "kms:GetPublicKey"], + "Resource": "arn:aws:kms:us-east-1:123456789012:key/abcd1234-..." + } + ] +} +``` + +If using the Secrets Manager helper, also grant `secretsmanager:GetSecretValue` on the cert secret. + +--- + +## AwsKmsSigner.create(options) + +Create a new KMS signer instance. Calls `GetPublicKey` internally to determine supported algorithms, select one, and validate that the certificate's public key matches the KMS key. + +### Options + +```typescript +const signer = await AwsKmsSigner.create({ + keyId: "arn:aws:kms:us-east-1:123456789012:key/abcd1234-...", + region: "us-east-1", + certificate, + buildChain: true, +}); +``` + +| Option | Type | Description | +| ------------------ | -------------- | -------------------------------------------------------------------------------------------------------------- | +| `keyId` | `string` | KMS key ARN, key ID, or alias (`alias/my-key`). Required. | +| `region` | `string` | AWS region. Optional if `AWS_REGION` env var is set or a preconfigured `client` is supplied. | +| `signingAlgorithm` | `string` | Optional. Which `SigningAlgorithmSpec` to use when the key supports multiple. Defaults to the first supported. | +| `certificate` | `Uint8Array` | DER-encoded X.509 signing certificate. Required. | +| `certificateChain` | `Uint8Array[]` | Optional. DER-encoded intermediates `[intermediate, ..., root]`. | +| `buildChain` | `boolean` | Optional. Fetch missing chain certs via AIA extensions. Default `false`. | +| `chainTimeout` | `number` | Optional. Timeout (ms) for AIA chain building. Default `15000`. | +| `client` | `KMSClient` | Optional. Pre-configured KMS client instance. | + +### Signing algorithm selection + +AWS KMS keys can support multiple signing algorithms (e.g. an `RSA_2048` key supports `RSASSA_PKCS1_V1_5_SHA_256/384/512` and `RSASSA_PSS_SHA_256/384/512`). `AwsKmsSigner.create()` reads `SigningAlgorithms` from the `GetPublicKey` response and uses the first one unless you pass `signingAlgorithm` explicitly. + +For maximum Adobe Reader compatibility, prefer PKCS#1 v1.5 over PSS: + +```typescript +const signer = await AwsKmsSigner.create({ + keyId: "alias/pdf-signing", + signingAlgorithm: "RSASSA_PKCS1_V1_5_SHA_256", + certificate, +}); +``` + +Supported algorithms: + +- `RSASSA_PKCS1_V1_5_SHA_256` / `SHA_384` / `SHA_512` +- `RSASSA_PSS_SHA_256` / `SHA_384` / `SHA_512` +- `ECDSA_SHA_256` / `SHA_384` / `SHA_512` + +--- + +## AwsKmsSigner.getCertificateFromSecretsManager(secretId, options?) + +Load a PEM or DER-encoded signing certificate from AWS Secrets Manager: + +```typescript +const { cert, chain } = await AwsKmsSigner.getCertificateFromSecretsManager( + "arn:aws:secretsmanager:us-east-1:123456789012:secret:signing-cert-AbCdEf", +); + +const signer = await AwsKmsSigner.create({ + keyId: "alias/pdf-signing", + certificate: cert, + certificateChain: chain, +}); +``` + +Cross-region is supported — pass `{ region: "..." }` if the secret lives in a different region than your default. + + + Never store private keys in Secrets Manager. The private key lives in KMS and never leaves it. + + +--- + +## Creating a signing key in AWS + +Minimal AWS CLI workflow for a new RSA 2048 signing key: + +```bash +aws kms create-key \ + --key-spec RSA_2048 \ + --key-usage SIGN_VERIFY \ + --description "PDF signing key" + +aws kms create-alias \ + --alias-name alias/pdf-signing \ + --target-key-id +``` + +For an AATL-trusted signature, submit a CSR (signed with `kms:Sign` against the KMS public key) to an Adobe Approved Trust List CA that supports HSM attestation, then use the returned certificate as `certificate` above. + +--- + +## Troubleshooting + +**`Certificate public key does not match KMS key`** — The certificate you supplied wasn't issued for the KMS key referenced by `keyId`. Re-issue the certificate against the KMS public key (retrieve it with `aws kms get-public-key --key-id ...`). + +**`Permission denied for key`** — Missing `kms:Sign` or `kms:GetPublicKey` on the key. Check the key policy _and_ the IAM policy of the signing principal. + +**`Key is disabled`** — Enable the KMS key in the AWS console or via `aws kms enable-key`. + +**`Requested signing algorithm is not supported by KMS key`** — The `signingAlgorithm` you passed isn't in `GetPublicKey`'s `SigningAlgorithms` list. Remove the `signingAlgorithm` option to use the default, or choose one from the supported set. diff --git a/content/docs/guides/signatures/index.mdx b/content/docs/guides/signatures/index.mdx index 7b2087a..e33e13f 100644 --- a/content/docs/guides/signatures/index.mdx +++ b/content/docs/guides/signatures/index.mdx @@ -108,7 +108,7 @@ Adds a document timestamp that covers the embedded validation data, enabling ind ## Sign with Cloud KMS -For enterprise environments requiring HSM-backed keys, use `GoogleKmsSigner`: +For enterprise environments requiring HSM-backed keys, use `GoogleKmsSigner` or `AwsKmsSigner`: ```ts import { PDF, GoogleKmsSigner } from "@libpdf/core"; @@ -123,7 +123,19 @@ const signer = await GoogleKmsSigner.create({ const signed = await pdf.sign({ signer }); ``` -See the [Google Cloud KMS guide](/docs/guides/signatures/google-kms) for complete setup instructions. +```ts +import { PDF, AwsKmsSigner } from "@libpdf/core"; + +const signer = await AwsKmsSigner.create({ + keyId: "arn:aws:kms:us-east-1:123456789012:key/abcd1234-...", + certificate: certificateDer, // DER-encoded certificate for this KMS key + buildChain: true, // Auto-fetch intermediate certificates +}); + +const signed = await pdf.sign({ signer }); +``` + +See the [Google Cloud KMS guide](/docs/guides/signatures/google-kms) or [AWS KMS guide](/docs/guides/signatures/aws-kms) for complete setup instructions. ## Sign with Web Crypto API diff --git a/content/docs/guides/signatures/meta.json b/content/docs/guides/signatures/meta.json index ae7c21e..9947614 100644 --- a/content/docs/guides/signatures/meta.json +++ b/content/docs/guides/signatures/meta.json @@ -1,4 +1,4 @@ { "title": "Digital Signatures", - "pages": ["index", "google-kms"] + "pages": ["index", "google-kms", "aws-kms"] } diff --git a/package.json b/package.json index cc26a0f..658463a 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,8 @@ "pkijs": "^3.3.3" }, "devDependencies": { + "@aws-sdk/client-kms": "^3.0.0", + "@aws-sdk/client-secrets-manager": "^3.0.0", "@cantoo/pdf-lib": "^2.6.1", "@google-cloud/kms": "^5.0.0", "@google-cloud/secret-manager": "^6.0.0", @@ -90,10 +92,18 @@ "vitest": "^4.0.16" }, "peerDependencies": { + "@aws-sdk/client-kms": "^3.0.0", + "@aws-sdk/client-secrets-manager": "^3.0.0", "@google-cloud/kms": "^5.0.0", "@google-cloud/secret-manager": "^6.0.0" }, "peerDependenciesMeta": { + "@aws-sdk/client-kms": { + "optional": true + }, + "@aws-sdk/client-secrets-manager": { + "optional": true + }, "@google-cloud/kms": { "optional": true }, diff --git a/src/index.ts b/src/index.ts index 497d5ca..fca7e83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -118,6 +118,7 @@ export type { TimestampAuthority, } from "./signatures"; export { + AwsKmsSigner, CertificateChainError, CryptoKeySigner, GoogleKmsSigner, diff --git a/src/signatures/index.ts b/src/signatures/index.ts index e694e64..e7642d0 100644 --- a/src/signatures/index.ts +++ b/src/signatures/index.ts @@ -37,7 +37,13 @@ export { extractOcspResponderCerts, } from "./revocation"; // Signers -export { CryptoKeySigner, GoogleKmsSigner, P12Signer, type P12SignerOptions } from "./signers"; +export { + AwsKmsSigner, + CryptoKeySigner, + GoogleKmsSigner, + P12Signer, + type P12SignerOptions, +} from "./signers"; // Timestamp export { HttpTimestampAuthority, type HttpTimestampAuthorityOptions } from "./timestamp"; // Types diff --git a/src/signatures/signers/aws-kms.test.ts b/src/signatures/signers/aws-kms.test.ts new file mode 100644 index 0000000..918c641 --- /dev/null +++ b/src/signatures/signers/aws-kms.test.ts @@ -0,0 +1,141 @@ +/** + * Tests for AwsKmsSigner. + * + * Unit tests: Pure logic (algorithm mapping, predicates). + * Integration tests: Real AWS KMS (skipped without AWS credentials). + */ + +import { describe, expect, it } from "vitest"; + +import { KmsSignerError } from "../types"; +import { isRsaPss, mapKmsAlgorithm } from "./aws-kms"; + +// ───────────────────────────────────────────────────────────────────────────── +// Unit Tests: Algorithm Mapping +// ───────────────────────────────────────────────────────────────────────────── + +describe("mapKmsAlgorithm", () => { + describe("RSA PKCS#1 v1.5 algorithms", () => { + it("maps RSASSA_PKCS1_V1_5_SHA_256", () => { + expect(mapKmsAlgorithm("RSASSA_PKCS1_V1_5_SHA_256")).toEqual({ + keyType: "RSA", + signatureAlgorithm: "RSASSA-PKCS1-v1_5", + digestAlgorithm: "SHA-256", + }); + }); + + it("maps RSASSA_PKCS1_V1_5_SHA_384", () => { + expect(mapKmsAlgorithm("RSASSA_PKCS1_V1_5_SHA_384")).toEqual({ + keyType: "RSA", + signatureAlgorithm: "RSASSA-PKCS1-v1_5", + digestAlgorithm: "SHA-384", + }); + }); + + it("maps RSASSA_PKCS1_V1_5_SHA_512", () => { + expect(mapKmsAlgorithm("RSASSA_PKCS1_V1_5_SHA_512")).toEqual({ + keyType: "RSA", + signatureAlgorithm: "RSASSA-PKCS1-v1_5", + digestAlgorithm: "SHA-512", + }); + }); + }); + + describe("RSA-PSS algorithms", () => { + it("maps RSASSA_PSS_SHA_256", () => { + expect(mapKmsAlgorithm("RSASSA_PSS_SHA_256")).toEqual({ + keyType: "RSA", + signatureAlgorithm: "RSA-PSS", + digestAlgorithm: "SHA-256", + }); + }); + + it("maps RSASSA_PSS_SHA_384", () => { + expect(mapKmsAlgorithm("RSASSA_PSS_SHA_384")).toEqual({ + keyType: "RSA", + signatureAlgorithm: "RSA-PSS", + digestAlgorithm: "SHA-384", + }); + }); + + it("maps RSASSA_PSS_SHA_512", () => { + expect(mapKmsAlgorithm("RSASSA_PSS_SHA_512")).toEqual({ + keyType: "RSA", + signatureAlgorithm: "RSA-PSS", + digestAlgorithm: "SHA-512", + }); + }); + }); + + describe("ECDSA algorithms", () => { + it("maps ECDSA_SHA_256", () => { + expect(mapKmsAlgorithm("ECDSA_SHA_256")).toEqual({ + keyType: "EC", + signatureAlgorithm: "ECDSA", + digestAlgorithm: "SHA-256", + }); + }); + + it("maps ECDSA_SHA_384", () => { + expect(mapKmsAlgorithm("ECDSA_SHA_384")).toEqual({ + keyType: "EC", + signatureAlgorithm: "ECDSA", + digestAlgorithm: "SHA-384", + }); + }); + + it("maps ECDSA_SHA_512", () => { + expect(mapKmsAlgorithm("ECDSA_SHA_512")).toEqual({ + keyType: "EC", + signatureAlgorithm: "ECDSA", + digestAlgorithm: "SHA-512", + }); + }); + }); + + describe("error cases", () => { + it("throws on unknown algorithm", () => { + expect(() => mapKmsAlgorithm("UNKNOWN_ALGO")).toThrow(KmsSignerError); + expect(() => mapKmsAlgorithm("UNKNOWN_ALGO")).toThrow( + /Unsupported AWS KMS signing algorithm/, + ); + }); + + it("throws on empty string", () => { + expect(() => mapKmsAlgorithm("")).toThrow(KmsSignerError); + }); + + it("throws on KMS encryption algorithm (not a signing algorithm)", () => { + expect(() => mapKmsAlgorithm("RSAES_OAEP_SHA_256")).toThrow(KmsSignerError); + }); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Unit Tests: RSA-PSS Detection +// ───────────────────────────────────────────────────────────────────────────── + +describe("isRsaPss", () => { + it("returns true for RSA-PSS algorithms", () => { + expect(isRsaPss("RSASSA_PSS_SHA_256")).toBe(true); + expect(isRsaPss("RSASSA_PSS_SHA_384")).toBe(true); + expect(isRsaPss("RSASSA_PSS_SHA_512")).toBe(true); + }); + + it("returns false for PKCS#1 v1.5", () => { + expect(isRsaPss("RSASSA_PKCS1_V1_5_SHA_256")).toBe(false); + expect(isRsaPss("RSASSA_PKCS1_V1_5_SHA_384")).toBe(false); + expect(isRsaPss("RSASSA_PKCS1_V1_5_SHA_512")).toBe(false); + }); + + it("returns false for ECDSA", () => { + expect(isRsaPss("ECDSA_SHA_256")).toBe(false); + expect(isRsaPss("ECDSA_SHA_384")).toBe(false); + expect(isRsaPss("ECDSA_SHA_512")).toBe(false); + }); + + it("returns false for unknown algorithm", () => { + expect(isRsaPss("UNKNOWN")).toBe(false); + expect(isRsaPss("")).toBe(false); + }); +}); diff --git a/src/signatures/signers/aws-kms.ts b/src/signatures/signers/aws-kms.ts new file mode 100644 index 0000000..d783d58 --- /dev/null +++ b/src/signatures/signers/aws-kms.ts @@ -0,0 +1,657 @@ +/** + * AWS KMS signer. + * + * Signs using asymmetric keys stored in AWS Key Management Service (KMS), + * including FIPS 140-2-validated HSM-backed keys. The private key never + * leaves KMS - only the digest is sent for signing. + */ + +import { toArrayBuffer } from "#src/helpers/buffer.ts"; +import { derToPem, isPem, normalizePem, parsePem } from "#src/helpers/pem.ts"; +import { fromBER } from "asn1js"; +import * as pkijs from "pkijs"; + +import { buildCertificateChain } from "../aia"; +import { CertificateChainError, KmsSignerError } from "../types"; +import type { DigestAlgorithm, KeyType, SignatureAlgorithm, Signer } from "../types"; + +// ───────────────────────────────────────────────────────────────────────────── +// Types +// ───────────────────────────────────────────────────────────────────────────── + +/** Full KMSClient type - dynamically imported */ +type KMSClient = import("@aws-sdk/client-kms").KMSClient; +/** Subset of the KMS client methods used for signing */ +type KmsClient = Pick; + +/** Secrets Manager client type - dynamically imported */ +type SecretsManagerClient = import("@aws-sdk/client-secrets-manager").SecretsManagerClient; + +/** Supported AWS KMS signing algorithm specs */ +type AwsSigningAlgorithm = + | "RSASSA_PKCS1_V1_5_SHA_256" + | "RSASSA_PKCS1_V1_5_SHA_384" + | "RSASSA_PKCS1_V1_5_SHA_512" + | "RSASSA_PSS_SHA_256" + | "RSASSA_PSS_SHA_384" + | "RSASSA_PSS_SHA_512" + | "ECDSA_SHA_256" + | "ECDSA_SHA_384" + | "ECDSA_SHA_512"; + +/** Options for AwsKmsSigner.create() */ +interface AwsKmsSignerOptions { + /** KMS key ARN or key ID (e.g. "arn:aws:kms:us-east-1:123456789012:key/abcd-..." or "abcd-...") */ + keyId: string; + + /** + * AWS region of the key. Required if no `client` is supplied and the region is + * not available via the AWS SDK default chain (e.g. `AWS_REGION` env var). + */ + region?: string; + + /** + * Which signing algorithm to use. AWS KMS keys may support multiple algorithms + * (e.g. an RSA_2048 key supports PKCS1_V1_5 and PSS at SHA-256/384/512). If + * omitted, the first algorithm advertised by `GetPublicKey` is used. + */ + signingAlgorithm?: AwsSigningAlgorithm; + + /** DER-encoded X.509 signing certificate */ + certificate: Uint8Array; + + /** Certificate chain [intermediate, ..., root] (optional) */ + certificateChain?: Uint8Array[]; + + /** Build certificate chain via AIA extensions (default: false) */ + buildChain?: boolean; + + /** Timeout for AIA chain building in ms (default: 15000) */ + chainTimeout?: number; + + /** Pre-configured KMS client (optional, uses default credential chain if not provided) */ + client?: KmsClient; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Algorithm Mapping +// ───────────────────────────────────────────────────────────────────────────── + +/** Mapped algorithm info from AWS KMS algorithm */ +interface AlgorithmInfo { + keyType: KeyType; + signatureAlgorithm: SignatureAlgorithm; + digestAlgorithm: DigestAlgorithm; +} + +/** KMS algorithm to our types mapping */ +const KMS_ALGORITHM_MAP: Record = { + // RSA PKCS#1 v1.5 + RSASSA_PKCS1_V1_5_SHA_256: { + keyType: "RSA", + signatureAlgorithm: "RSASSA-PKCS1-v1_5", + digestAlgorithm: "SHA-256", + }, + RSASSA_PKCS1_V1_5_SHA_384: { + keyType: "RSA", + signatureAlgorithm: "RSASSA-PKCS1-v1_5", + digestAlgorithm: "SHA-384", + }, + RSASSA_PKCS1_V1_5_SHA_512: { + keyType: "RSA", + signatureAlgorithm: "RSASSA-PKCS1-v1_5", + digestAlgorithm: "SHA-512", + }, + // RSA-PSS + RSASSA_PSS_SHA_256: { + keyType: "RSA", + signatureAlgorithm: "RSA-PSS", + digestAlgorithm: "SHA-256", + }, + RSASSA_PSS_SHA_384: { + keyType: "RSA", + signatureAlgorithm: "RSA-PSS", + digestAlgorithm: "SHA-384", + }, + RSASSA_PSS_SHA_512: { + keyType: "RSA", + signatureAlgorithm: "RSA-PSS", + digestAlgorithm: "SHA-512", + }, + // ECDSA + ECDSA_SHA_256: { + keyType: "EC", + signatureAlgorithm: "ECDSA", + digestAlgorithm: "SHA-256", + }, + ECDSA_SHA_384: { + keyType: "EC", + signatureAlgorithm: "ECDSA", + digestAlgorithm: "SHA-384", + }, + ECDSA_SHA_512: { + keyType: "EC", + signatureAlgorithm: "ECDSA", + digestAlgorithm: "SHA-512", + }, +}; + +/** + * Map AWS KMS algorithm name to our types. + * + * @param algorithm - The KMS `SigningAlgorithmSpec` + * @returns Algorithm info (keyType, signatureAlgorithm, digestAlgorithm) + * @throws {KmsSignerError} if algorithm is unsupported + * + * @internal Exported for testing + */ +export function mapKmsAlgorithm(algorithm: string): AlgorithmInfo { + const info = KMS_ALGORITHM_MAP[algorithm]; + + if (!info) { + throw new KmsSignerError(`Unsupported AWS KMS signing algorithm: ${algorithm}`); + } + + return info; +} + +/** + * Check if an algorithm uses RSA-PSS. + * + * @internal Exported for testing + */ +export function isRsaPss(algorithm: string): boolean { + return algorithm.startsWith("RSASSA_PSS_"); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Certificate Utilities +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Extract public key PEM from a DER-encoded certificate. + */ +function extractPublicKeyFromCertificate(certDer: Uint8Array): string { + const asn1 = fromBER(toArrayBuffer(certDer)); + + if (asn1.offset === -1) { + throw new KmsSignerError("Failed to parse certificate"); + } + + const cert = new pkijs.Certificate({ schema: asn1.result }); + const spki = cert.subjectPublicKeyInfo.toSchema().toBER(false); + + return derToPem(new Uint8Array(spki), "PUBLIC KEY"); +} + +/** + * Check if two public keys match. + */ +function publicKeysMatch(kmsSpkiPem: string, certPem: string): boolean { + return normalizePem(kmsSpkiPem) === normalizePem(certPem); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Dynamic Imports +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Dynamically import @aws-sdk/client-kms. + */ +async function importKms(): Promise { + try { + return await import("@aws-sdk/client-kms"); + } catch (error) { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion + const code = (error as NodeJS.ErrnoException).code; + + if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") { + throw new KmsSignerError( + "@aws-sdk/client-kms is required. Install with: npm install @aws-sdk/client-kms", + ); + } + + throw error; + } +} + +/** + * Dynamically import @aws-sdk/client-secrets-manager. + */ +async function importSecretsManager(): Promise { + try { + return await import("@aws-sdk/client-secrets-manager"); + } catch (error) { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion + const code = (error as NodeJS.ErrnoException).code; + + if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") { + throw new KmsSignerError( + "@aws-sdk/client-secrets-manager is required. Install with: npm install @aws-sdk/client-secrets-manager", + ); + } + + throw error; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// AWS SDK Error Handling +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of service exceptions thrown by the AWS SDK v3. + */ +interface AwsServiceError extends Error { + name: string; + $metadata?: { httpStatusCode?: number }; +} + +/** + * Type guard for AWS SDK service exceptions. + */ +function isAwsServiceError(error: unknown): error is AwsServiceError { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion + const e = error as AwsServiceError; + + return e instanceof Error && typeof e.name === "string"; +} + +// ───────────────────────────────────────────────────────────────────────────── +// AwsKmsSigner +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Signer that uses AWS KMS for signing operations. + * + * Supports RSA and ECDSA keys stored in AWS KMS, including HSM-backed keys. + * The private key never leaves KMS - only the digest is sent for signing. + * + * Credentials are resolved by the AWS SDK's default credential chain + * (env vars, IAM task role, EC2/Lambda instance profile, shared credentials file). + * + * **Performance note:** Each `sign()` call makes a network request to KMS + * (~50-200ms latency). For bulk signing, consider the performance implications. + * + * @example + * ```typescript + * const signer = await AwsKmsSigner.create({ + * keyId: "arn:aws:kms:us-east-1:123456789012:key/abcd-...", + * region: "us-east-1", + * certificate: certificateDer, + * }); + * + * const pdf = await PDF.load(pdfBytes); + * const { bytes } = await pdf.sign({ signer }); + * ``` + */ +export class AwsKmsSigner implements Signer { + readonly certificate: Uint8Array; + readonly certificateChain: Uint8Array[]; + readonly keyType: KeyType; + readonly signatureAlgorithm: SignatureAlgorithm; + + /** The digest algorithm derived from the chosen KMS signing algorithm */ + readonly digestAlgorithm: DigestAlgorithm; + + /** KMS key ARN or key ID (for logging/debugging) */ + readonly keyId: string; + + /** The AWS KMS signing algorithm used on each `sign()` call */ + readonly kmsSigningAlgorithm: AwsSigningAlgorithm; + + private readonly client: KmsClient; + + private constructor( + client: KmsClient, + keyId: string, + kmsSigningAlgorithm: AwsSigningAlgorithm, + certificate: Uint8Array, + certificateChain: Uint8Array[], + keyType: KeyType, + signatureAlgorithm: SignatureAlgorithm, + digestAlgorithm: DigestAlgorithm, + ) { + this.client = client; + this.keyId = keyId; + this.kmsSigningAlgorithm = kmsSigningAlgorithm; + this.certificate = certificate; + this.certificateChain = certificateChain; + this.keyType = keyType; + this.signatureAlgorithm = signatureAlgorithm; + this.digestAlgorithm = digestAlgorithm; + } + + /** + * Create an AwsKmsSigner from a KMS key reference. + * + * Calls `GetPublicKey` on the KMS key to determine which signing algorithms + * are supported, selects one (the requested `signingAlgorithm` if provided, + * otherwise the first supported), and validates that the certificate's public + * key matches the KMS public key. + * + * @param options - Configuration options (key reference, certificate, etc.) + * @returns A new AwsKmsSigner instance + * @throws {KmsSignerError} if the key is invalid, the requested algorithm is + * unsupported on the key, or the certificate public key does not match. + * + * @example + * ```typescript + * // ARN + * const signer = await AwsKmsSigner.create({ + * keyId: "arn:aws:kms:us-east-1:123456789012:key/abcd-...", + * certificate: certificateDer, + * }); + * + * // Alias + * const signer = await AwsKmsSigner.create({ + * keyId: "alias/my-signing-key", + * region: "us-east-1", + * certificate: certificateDer, + * buildChain: true, + * }); + * ``` + */ + static async create(options: AwsKmsSignerOptions): Promise { + // Create or use provided client. + // Dynamically import KMS only if client was not provided. + let client: KmsClient; + + if (options.client) { + client = options.client; + } else { + const kms = await importKms(); + + client = new kms.KMSClient(options.region ? { region: options.region } : {}); + } + + try { + // Fetch the public key + list of supported signing algorithms. + const kms = await importKms(); + const publicKeyResponse = await client.send( + new kms.GetPublicKeyCommand({ KeyId: options.keyId }), + ); + + if (publicKeyResponse.KeyUsage && publicKeyResponse.KeyUsage !== "SIGN_VERIFY") { + throw new KmsSignerError( + `KMS key ${options.keyId} has usage ${publicKeyResponse.KeyUsage}; only SIGN_VERIFY keys can sign.`, + ); + } + + const supportedAlgorithms = publicKeyResponse.SigningAlgorithms ?? []; + + if (supportedAlgorithms.length === 0) { + throw new KmsSignerError( + `KMS key ${options.keyId} has no signing algorithms. Ensure the key usage is SIGN_VERIFY and the key spec is RSA or ECC.`, + ); + } + + if (options.signingAlgorithm && !supportedAlgorithms.includes(options.signingAlgorithm)) { + throw new KmsSignerError( + `Requested signing algorithm ${options.signingAlgorithm} is not supported by KMS key ${options.keyId}. Supported: ${supportedAlgorithms.join(", ")}`, + ); + } + + // oxlint-disable-next-line typescript/no-unsafe-type-assertion + const chosenAlgorithm = (options.signingAlgorithm ?? + supportedAlgorithms[0]) as AwsSigningAlgorithm; + + const algorithmInfo = mapKmsAlgorithm(chosenAlgorithm); + + // Log warning for RSA-PSS + if (isRsaPss(chosenAlgorithm)) { + console.warn( + "Warning: RSA-PSS signatures may not verify correctly in older PDF readers " + + "(Adobe Acrobat < 2020). Consider using PKCS#1 v1.5 for maximum compatibility.", + ); + } + + // Validate that the certificate's public key matches the KMS public key. + if (!publicKeyResponse.PublicKey) { + throw new KmsSignerError(`Failed to get public key for key: ${options.keyId}`); + } + + const kmsSpkiPem = derToPem(publicKeyResponse.PublicKey, "PUBLIC KEY"); + const certSpkiPem = extractPublicKeyFromCertificate(options.certificate); + + if (!publicKeysMatch(kmsSpkiPem, certSpkiPem)) { + throw new KmsSignerError( + "Certificate public key does not match KMS key. " + + "Ensure the certificate was issued for this KMS key.", + ); + } + + // Build certificate chain if requested + let chainCertsDer: Uint8Array[] = options.certificateChain ?? []; + + if (options.buildChain) { + try { + chainCertsDer = await buildCertificateChain(options.certificate, { + existingChain: chainCertsDer, + timeout: options.chainTimeout, + }); + } catch (error) { + if (error instanceof CertificateChainError) { + console.warn(`Could not complete certificate chain via AIA: ${error.message}`); + } else { + throw error; + } + } + } + + return new AwsKmsSigner( + client, + options.keyId, + chosenAlgorithm, + options.certificate, + chainCertsDer, + algorithmInfo.keyType, + algorithmInfo.signatureAlgorithm, + algorithmInfo.digestAlgorithm, + ); + } catch (error) { + if (error instanceof KmsSignerError) { + throw error; + } + + if (isAwsServiceError(error)) { + switch (error.name) { + case "NotFoundException": + throw new KmsSignerError( + `Key not found: ${options.keyId}. Verify the key ID/ARN and your permissions.`, + error, + ); + + case "AccessDeniedException": + throw new KmsSignerError( + `Permission denied for key: ${options.keyId}. ` + + `Ensure the principal has 'kms:Sign' and 'kms:GetPublicKey' permissions on this key.`, + error, + ); + + case "DisabledException": + throw new KmsSignerError( + `Key is disabled: ${options.keyId}. Only enabled keys can sign.`, + error, + ); + + case "KMSInvalidStateException": + throw new KmsSignerError( + `Key is in an invalid state: ${options.keyId}. ${error.message}`, + error, + ); + } + } + + const message = error instanceof Error ? error.message : String(error); + + throw new KmsSignerError(`Failed to initialize KMS signer: ${message}`); + } + } + + /** + * Loads a signing certificate from AWS Secrets Manager for use with KMS-based signing. + * + * The secret should contain the certificate material in PEM (recommended) or raw + * DER form: + * - If the secret contains PEM-encoded data, all certificates will be parsed. + * The first is used as the signing cert (`cert`), and the remainder returned as + * the optional `chain` (intermediates). + * - If the secret contains raw DER data, it is returned as the signing cert (`cert`). + * + * Private keys must never be stored in Secrets Manager. + * + * @param secretId ARN or name of the Secrets Manager secret. + * @param options Optional client configuration, including a SecretsManagerClient instance. + * @returns An object with `cert` (main certificate bytes) and optional `chain` (intermediates). + * @throws {KmsSignerError} if @aws-sdk/client-secrets-manager is not installed or retrieval fails. + * + * @example + * const { cert, chain } = await AwsKmsSigner.getCertificateFromSecretsManager( + * "arn:aws:secretsmanager:us-east-1:123456789012:secret:signing-cert-AbCdEf" + * ); + * + * const signer = await AwsKmsSigner.create({ + * keyId: "...", + * certificate: cert, + * certificateChain: chain, + * }); + */ + static async getCertificateFromSecretsManager( + secretId: string, + options?: { region?: string; client?: SecretsManagerClient }, + ): Promise<{ + cert: Uint8Array; + chain?: Uint8Array[]; + }> { + // Dynamically import Secrets Manager + const secretsManager = await importSecretsManager(); + + const client = + options?.client ?? + new secretsManager.SecretsManagerClient(options?.region ? { region: options.region } : {}); + + try { + const response = await client.send( + new secretsManager.GetSecretValueCommand({ SecretId: secretId }), + ); + + const payload = + response.SecretString ?? + (response.SecretBinary ? new TextDecoder().decode(response.SecretBinary) : undefined); + + if (!payload) { + throw new KmsSignerError(`Secret is empty: ${secretId}`); + } + + if (isPem(payload)) { + const certs = parsePem(payload).map(block => block.der); + + const [first, ...rest] = certs; + + return { + cert: first, + chain: rest.length > 0 ? rest : undefined, + }; + } + + return { + cert: new TextEncoder().encode(payload), + }; + } catch (error) { + if (error instanceof KmsSignerError) { + throw error; + } + + if (isAwsServiceError(error)) { + switch (error.name) { + case "ResourceNotFoundException": + throw new KmsSignerError( + `Secret not found: ${secretId}. Verify the ARN/name and your permissions.`, + error, + ); + + case "AccessDeniedException": + throw new KmsSignerError( + `Permission denied for secret: ${secretId}. ` + + `Ensure the principal has 'secretsmanager:GetSecretValue' on this secret.`, + error, + ); + } + } + + const message = error instanceof Error ? error.message : String(error); + + throw new KmsSignerError(`Failed to fetch certificate from Secrets Manager: ${message}`); + } + } + + /** + * Sign data using the KMS key. + * + * The data is hashed locally using the selected digest algorithm before being + * sent to KMS for signing. + * + * Signature format: + * - RSA: PKCS#1 v1.5 or PSS signature bytes (AWS KMS returns the raw signature). + * - ECDSA: DER-encoded SEQUENCE {INTEGER r, INTEGER s} (AWS KMS returns DER). + * + * @param data - The data to sign + * @param algorithm - The digest algorithm to use (must match the KMS key) + * @returns The signature bytes + * @throws {KmsSignerError} if the digest algorithm doesn't match the key's algorithm + */ + async sign(data: Uint8Array, algorithm: DigestAlgorithm): Promise { + if (algorithm !== this.digestAlgorithm) { + throw new KmsSignerError( + `Digest algorithm mismatch: this KMS key requires ${this.digestAlgorithm}, ` + + `but ${algorithm} was requested`, + ); + } + + const digestBuffer = await crypto.subtle.digest(algorithm, toArrayBuffer(data)); + const digest = new Uint8Array(digestBuffer); + + try { + const kms = await importKms(); + const response = await this.client.send( + new kms.SignCommand({ + KeyId: this.keyId, + Message: digest, + MessageType: "DIGEST", + SigningAlgorithm: this.kmsSigningAlgorithm, + }), + ); + + if (!response.Signature) { + throw new KmsSignerError("KMS did not return a signature"); + } + + return response.Signature; + } catch (error) { + if (error instanceof KmsSignerError) { + throw error; + } + + if (isAwsServiceError(error)) { + switch (error.name) { + case "DisabledException": + throw new KmsSignerError( + `Key is disabled: ${this.keyId}. Only enabled keys can sign.`, + error, + ); + + case "KMSInvalidStateException": + throw new KmsSignerError( + `Key is in an invalid state: ${this.keyId}. ${error.message}`, + error, + ); + } + } + + const message = error instanceof Error ? error.message : String(error); + + throw new KmsSignerError(`Failed to sign with KMS: ${message}`); + } + } +} diff --git a/src/signatures/signers/index.ts b/src/signatures/signers/index.ts index cbe76f3..a7fd323 100644 --- a/src/signatures/signers/index.ts +++ b/src/signatures/signers/index.ts @@ -4,6 +4,7 @@ export type { DigestAlgorithm, KeyType, SignatureAlgorithm, Signer } from "../types"; export { KmsSignerError, SignerError } from "../types"; +export { AwsKmsSigner } from "./aws-kms"; export { CryptoKeySigner } from "./crypto-key"; export { GoogleKmsSigner } from "./google-kms"; export { P12Signer, type P12SignerOptions } from "./p12";