UNPKG

@relaycorp/webcrypto-kms

Version:

WebCrypto-compatible client for Key Management Services like GCP KMS

95 lines 4.13 kB
import { CreateKeyCommand, GetPublicKeyCommand, KeyUsageType, ScheduleKeyDeletionCommand, SignCommand, } from '@aws-sdk/client-kms'; import { KmsRsaPssProvider } from '../KmsRsaPssProvider'; import { AwsKmsRsaPssPrivateKey } from './AwsKmsRsaPssPrivateKey'; import { KmsError } from '../KmsError'; import { bufferToArrayBuffer } from '../utils/buffer'; import { derDeserialisePublicKey, hash } from '../utils/crypto'; // See: https://docs.aws.amazon.com/kms/latest/developerguide/asymmetric-key-specs.html const SUPPORTED_MODULUS_LENGTHS = [2048, 3072, 4096]; const REQUEST_OPTIONS = { requestTimeout: 3_000 }; export class AwsKmsRsaPssProvider extends KmsRsaPssProvider { client; constructor(client) { super(); this.client = client; // See: https://docs.aws.amazon.com/kms/latest/developerguide/asymmetric-key-specs.html this.hashAlgorithms = ['SHA-256', 'SHA-384', 'SHA-512']; } async onGenerateKey(algorithm) { if (!SUPPORTED_MODULUS_LENGTHS.includes(algorithm.modulusLength)) { throw new KmsError(`Unsupported RSA modulus (${algorithm.modulusLength})`); } const keySpec = `RSA_${algorithm.modulusLength}`; const command = new CreateKeyCommand({ KeySpec: keySpec, KeyUsage: KeyUsageType.SIGN_VERIFY, }); const response = await this.client.send(command, REQUEST_OPTIONS); const keyArn = response.KeyMetadata?.Arn; if (!keyArn) { throw new KmsError('Key creation response is missing KeyMetadata.Arn'); } const privateKey = new AwsKmsRsaPssPrivateKey(keyArn, algorithm.hash.name, this); const publicKeySerialised = await this.retrievePublicKey(privateKey); const publicKey = await derDeserialisePublicKey(publicKeySerialised, algorithm); return { privateKey, publicKey }; } async onExportKey(format, key) { requireAwsKmsKey(key); let keySerialised; if (format === 'raw') { const arnEncoded = Buffer.from(key.arn); keySerialised = bufferToArrayBuffer(arnEncoded); } else if (format === 'spki') { keySerialised = await this.retrievePublicKey(key); } else { throw new KmsError(`Private key cannot be exported as ${format}`); } return keySerialised; } async onImportKey(format, keyData, algorithm) { if (format !== 'raw') { throw new KmsError('Private key can only be exported to raw format'); } const keyArn = Buffer.from(keyData).toString(); return new AwsKmsRsaPssPrivateKey(keyArn, algorithm.hash.name, this); } async onSign(_algorithm, key, data) { requireAwsKmsKey(key); const hashingAlgorithm = key.algorithm.hash.name; const digest = await hash(data, hashingAlgorithm); const awsHashAlgo = `RSASSA_PSS_${hashingAlgorithm.replace('-', '_')}`; const command = new SignCommand({ KeyId: key.arn, Message: Buffer.from(digest), MessageType: 'DIGEST', SigningAlgorithm: awsHashAlgo, }); const output = await this.client.send(command, REQUEST_OPTIONS); return bufferToArrayBuffer(output.Signature); } async onVerify() { throw new KmsError('Signature verification is unsupported'); } async destroyKey(key) { requireAwsKmsKey(key); const command = new ScheduleKeyDeletionCommand({ KeyId: key.arn }); await this.client.send(command, REQUEST_OPTIONS); } async close() { this.client.destroy(); } async retrievePublicKey(key) { const command = new GetPublicKeyCommand({ KeyId: key.arn }); const response = await this.client.send(command, REQUEST_OPTIONS); return bufferToArrayBuffer(response.PublicKey); } } function requireAwsKmsKey(key) { if (!(key instanceof AwsKmsRsaPssPrivateKey)) { throw new KmsError(`Only AWS KMS keys are supported (got ${key.constructor.name})`); } } //# sourceMappingURL=AwsKmsRsaPssProvider.js.map