@keyobject/aws-kms uses AWS KMS asymmetric signing keys as ordinary Node.js
KeyObject instances. Private key material stays in KMS; signing calls KMS,
while public-key export and verification use the public key cached when the key
is loaded.
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by becoming a sponsor.
import '@keyobject/aws-kms/register'
import { createPrivateKey, createPublicKey, sign, verify } from 'node:crypto'
import { promisify } from 'node:util'
const signAsync = promisify(sign)
const key = createPrivateKey({
key: new URL('aws-kms:key-id=alias/my-rsa-signer;region=eu-central-1'),
})
const data = Buffer.from('message')
const signature = await signAsync('sha256', data, key)
verify('sha256', data, createPublicKey(key), signature) // true, no KMS callThe alias in this example must identify an RSA SIGN_VERIFY key. See
Supported Keys for the parameters used by other key types.
promisify(sign) also turns validation errors that sign() throws before it
queues its callback into promise rejections, so one await/try boundary handles
both synchronous validation failures and asynchronous signing failures.
An aws-kms: key is deliberately much more expensive than an ordinary local
private key:
createPrivateKey()makes a synchronous KMSGetPublicKeyrequest and parses the result. It blocks the calling JavaScript thread, so load each key once at startup and cache itsKeyObjectinstead of loading it per request.- Every signature makes a remote, billable KMS
Signrequest. Use the callback form ofsign()(promisified as above) orcrypto.subtle.sign()so the main event loop does not wait synchronously for the network round trip. Avoid callback-freesign()andcreateSign().sign()on a server request path. - Async signing still occupies Node's shared crypto work queue and remains
subject to KMS latency, quotas, and cost. Apply backpressure and a bounded
concurrency limit; unbounded
Promise.all()does not increase capacity.UV_THREADPOOL_SIZEis another process-startup tuning option, but that pool is shared with other Node operations and should be changed only after measuring. - To isolate blocking calls from both the main event loop and its async crypto
queue, run synchronous key loading and signing in a bounded
Workerpool. A Worker may retain and sign with the key itself. Alternatively, it may load the key and send the resultingKeyObjectback to the main thread withpostMessage(key), moving just the synchronousGetPublicKeystep off the main thread. Node clonesKeyObjects forpostMessage(); do not put the key in the transfer list.
Workers isolate blocking work but do not make KMS faster. Choose and measure a bounded design appropriate for the application's latency target and KMS quota.
- Node.js
>=26.7.0with the OpenSSL STORE URL-key loader. A functional probe also detects custom builds that omit it. - OpenSSL 3.0 or later for config-based activation. In-process
register()additionally requires OpenSSL 3.5 or later. Stock Node uses its bundled OpenSSL; no system OpenSSL package is needed. - AWS credentials available through the SDK credential chain, plus a region in the key URI or supported AWS/provider configuration.
npm install @keyobject/aws-kms
npx @keyobject/aws-kms checkThe core package selects one exact-version native package at runtime. Prebuilt targets are:
darwin-arm64(macOS 13.5+)linux-arm64(glibc 2.28+)linux-x64(glibc 2.28+)linuxmusl-arm64(experimental)linuxmusl-x64(experimental)
No lifecycle script runs during npm install, and the package performs no
secondary downloads. The package advertises Node.js >=26.7.0 as its runtime
floor for URL-key support. isSupported() and the check command still
exercise the required capability with a functional probe instead of relying on
the version number alone.
See Installation and configuration for platform details, OpenSSL configuration merging, the Node permission model, and troubleshooting.
OpenSSL must know about the provider before an aws-kms: key is loaded. Choose
one route.
The config route works with OpenSSL 3.0 and later:
node --openssl-config="$(npx @keyobject/aws-kms config-path)" app.mjsThe in-process route requires OpenSSL 3.5 or later and must run as a startup preload, before application crypto code or Workers. It mutates OpenSSL's process-wide default property query through an API OpenSSL documents as not thread-safe:
node --import @keyobject/aws-kms/register app.mjsThe preload throws if registration fails. Use the config route on OpenSSL 3.0--3.4 or when Node's permission model should not grant native-addon loading.
npx @keyobject/aws-kms check
npx @keyobject/aws-kms module-path
npx @keyobject/aws-kms config-path
npx @keyobject/aws-kms exec -- node app.mjscheck and exec refuse to replace an OpenSSL config supplied through
OPENSSL_CONF, NODE_OPTIONS, or the current or target Node command line. If
replacing it is intentional, say so explicitly:
npx @keyobject/aws-kms exec --replace-openssl-config -- node app.mjsimport { isSupported } from '@keyobject/aws-kms'
const support = isSupported()
if (!support.ok) {
console.error(support.code, support.reason)
}The result is a discriminated TypeScript union:
{ ok: true }
{ ok: false; code: AwsKmsSupportFailureCode; reason: string }Important failure codes include:
ERR_AWSKMS_UNSUPPORTED_RUNTIME: this Node build cannot load URL keys.ERR_AWSKMS_PERMISSION_DENIED: Node or the filesystem denied the required access.ERR_AWSKMS_RUNTIME_PROBE_FAILED: the functional URL-key probe failed unexpectedly.ERR_AWSKMS_MODULE_NOT_FOUND: the platform is not published or its optional dependency is absent.ERR_AWSKMS_VERSION_MISMATCH: core and native platform package versions differ.ERR_AWSKMS_INVALID_PLATFORM_PACKAGE: platform package identity or metadata is wrong.ERR_AWSKMS_PACKAGE_INTEGRITY/ERR_AWSKMS_TEMP_INTEGRITY: a validated package or private runtime file changed.
aws-kms:key-id=<key id | key ARN | alias | alias ARN>[;region=<region>][?profile=<profile>&endpoint=<URL>]
Examples:
aws-kms:key-id=alias/my-signer
aws-kms:key-id=alias/my-signer;region=eu-central-1
aws-kms:key-id=arn:aws:kms:eu-central-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
aws-kms:key-id=alias/my-signer;region=eu-west-1?profile=production
Client settings resolve in this order: URI attributes, then AWS environment and the selected shared profile, then defaults in the provider's OpenSSL config section. A KMS ARN supplies its region; an explicit conflicting region is rejected. Credential material itself follows the AWS SDK default credential chain.
The URI is trusted configuration, not an untrusted request parameter. In
particular, profile selects credentials and endpoint changes the network
destination for signed AWS requests. See the
Security Policy.
Pass the object in the last column as the first argument to
key.toCryptoKey(algorithm, false, ['sign']):
For RSA, choose hash independently as 'SHA-256', 'SHA-384', or
'SHA-512'. Every listed RSA key size supports every listed hash.
| AWS KMS key spec | Supported signing | WebCrypto algorithm for toCryptoKey() |
|---|---|---|
RSA_2048 |
PKCS#1 v1.5 and PSS | { name: 'RSASSA-PKCS1-v1_5', hash } or { name: 'RSA-PSS', hash } |
RSA_3072 |
PKCS#1 v1.5 and PSS | { name: 'RSASSA-PKCS1-v1_5', hash } or { name: 'RSA-PSS', hash } |
RSA_4096 |
PKCS#1 v1.5 and PSS | { name: 'RSASSA-PKCS1-v1_5', hash } or { name: 'RSA-PSS', hash } |
ECC_NIST_P256 |
ECDSA with SHA-256 | { name: 'ECDSA', namedCurve: 'P-256' } |
ECC_NIST_P384 |
ECDSA with SHA-384 | { name: 'ECDSA', namedCurve: 'P-384' } |
ECC_NIST_P521 |
ECDSA with SHA-512 | { name: 'ECDSA', namedCurve: 'P-521' } |
ECC_NIST_EDWARDS25519 |
Ed25519 (ED25519_SHA_512) |
{ name: 'Ed25519' } |
ML_DSA_44 |
ML-DSA (ML_DSA_SHAKE_256) |
{ name: 'ML-DSA-44' } |
ML_DSA_65 |
ML-DSA (ML_DSA_SHAKE_256) |
{ name: 'ML-DSA-65' } |
ML_DSA_87 |
ML-DSA (ML_DSA_SHAKE_256) |
{ name: 'ML-DSA-87' } |
RSA-PSS also requires saltLength of 32, 48, or 64 for SHA-256, SHA-384, or
SHA-512 respectively in the algorithm passed to subtle.sign(). For ECDSA, the
operation algorithm is
{ name: 'ECDSA', hash: 'SHA-256' }, SHA-384, or SHA-512 respectively; the
curve belongs only to the toCryptoKey() algorithm shown above. A public key uses
the same conversion algorithm with createPublicKey(key).toCryptoKey(algorithm, true, ['verify']).
Ed25519 messages must be between 1 and 4096 bytes. RSA and ECDSA send a digest, and ML-DSA sends a locally computed 64-byte external representative, so those paths do not inherit that message-size limit. AWS documents current ML-DSA behavior and regional availability in ML-DSA keys in AWS KMS.
The provider implements signing and public-key export. It does not implement
decryption or key agreement. Node WebCrypto can use a loaded key through
KeyObject#toCryptoKey() where Node supports the corresponding algorithm.
An application needs only these permissions on the keys it uses:
kms:GetPublicKeywhen a key is loaded;kms:Signwhen a signature is produced.
Verification is local, so the provider does not call kms:Verify or
kms:DescribeKey. Scope both actions to the intended key ARNs and retain normal
KMS key-policy controls.
When the OpenSSL operation requests fips=yes, the provider applies a composite
routing policy:
- its KMS key operations remain selectable under the FIPS property policy;
- digest, SHAKE, SPKI parsing, and local verification are fetched from another
provider with
fips=yes; - AWS SDK clients are forced to AWS KMS FIPS endpoints;
- endpoint overrides and China regions are rejected for that operation.
The host must supply and correctly configure an OpenSSL FIPS implementation.
The @keyobject/aws-kms provider binary is not independently CMVP certified;
its fips=yes declaration describes this routing contract, not a certification
of the provider binary. Consult the official
AWS KMS endpoint table
and AWS KMS data-protection documentation
for the service boundary. AWS also states that KMS ML-DSA operations run in
FIPS 140-3 Security Level 3 validated HSMs in its
ML-DSA documentation.
import {
isSupported,
modulePath,
opensslConfigPath,
register,
version,
} from '@keyobject/aws-kms'| Export | Purpose |
|---|---|
version |
Core package version |
isSupported() |
Non-throwing runtime and native-package capability check |
modulePath() |
Verified absolute path to the platform provider module |
opensslConfigPath() |
Private config shared by the main thread and Workers in one process |
register() |
Idempotent in-process registration; requires OpenSSL 3.5+ |
The side-effect entry point @keyobject/aws-kms/register calls register().
The CLI also exposes check, module-path, config-path, and exec.
The package is ESM. Node's require(esm) support is enabled throughout the
declared runtime range, so require('@keyobject/aws-kms') also works.
On OpenSSL 3.0 through 3.4, register() throws
ERR_AWSKMS_OPENSSL_VERSION; use config-file activation on those runtimes.
Mark @keyobject/aws-kms and its optional platform packages as external. They
resolve native files at runtime and cannot be inlined into a JavaScript bundle.
Source builds are contributor workflows, not the installation interface. They
require CMake 3.25 or newer, an explicit AWSKMS_BACKEND=stub|aws, and have no
CMake install target. See
Contributing.
- Documentation index
- Installation and configuration
- Security policy and deployment guidance
- Real AWS KMS test setup
- Code of Conduct
- Third-party notices
The project is MIT licensed. Native packages include licenses and attribution notices for statically linked dependencies.