@relaycorp/webcrypto-kms
Version:
WebCrypto-compatible client for Key Management Services like GCP KMS
95 lines • 4.13 kB
JavaScript
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