@planetarium/account-aws-kms
Version:
Libplanet account implementation using AWS KMS
274 lines (267 loc) • 8.62 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/AwsKmsAccount.ts
import { SignCommand } from "@aws-sdk/client-kms";
import { Signature as NobleSignature } from "@noble/secp256k1";
// src/crypto/node.ts
import * as nc from "node:crypto";
var crypto = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : void 0;
// src/AwsKmsAccount.ts
import {
Address,
Signature
} from "@planetarium/account";
var AwsKmsAccount = class {
#client;
keyId;
// TODO: This attribute is deprecated. We should remove it and make
// getPublicKey() method the only choice in the future.
/**
* @deprecated Use {@link getPublicKey()} instead.
*/
publicKey;
constructor(keyId, publicKey, client) {
this.keyId = keyId;
this.publicKey = publicKey;
this.#client = client;
}
getAddress() {
return Promise.resolve(Address.deriveFrom(this.publicKey));
}
getPublicKey() {
return Promise.resolve(this.publicKey);
}
async sign(message) {
const digest = await crypto.subtle.digest("SHA-256", message);
const digestArray = new Uint8Array(digest);
const cmd = new SignCommand({
KeyId: this.keyId,
Message: digestArray,
MessageType: "DIGEST",
SigningAlgorithm: "ECDSA_SHA_256"
});
const response = await this.#client.send(cmd);
if (response.Signature == null)
throw new Error("Failed to sign message");
const sig = NobleSignature.fromDER(response.Signature).normalizeS();
return Signature.fromHex(sig.toDERHex());
}
};
__name(AwsKmsAccount, "AwsKmsAccount");
// src/asn1.ts
import {
Any,
BitString,
ObjectIdentifier,
Sequence,
verifySchema
} from "asn1js";
var SubjectPublicKeyInfo = new Sequence({
name: "subjectPublicKeyInfo",
value: [
new Sequence({
name: "algorithm",
value: [
new ObjectIdentifier({
name: "algorithm"
}),
new Any({
name: "parameters",
optional: true
})
]
}),
new BitString({
name: "subjectPublicKey"
})
]
});
function parseSubjectPublicKeyInfo(buf) {
const { result, verified } = verifySchema(buf, SubjectPublicKeyInfo);
if (!verified) {
throw new RangeError("Failed to verify SubjectPublicKeyInfo data");
}
const bitstring = result.valueBlock.value[1];
return bitstring.valueBlock.valueHexView;
}
__name(parseSubjectPublicKeyInfo, "parseSubjectPublicKeyInfo");
// src/AwsKmsKeyStore.ts
import {
CreateKeyCommand,
DescribeKeyCommand,
GetPublicKeyCommand,
KMSInvalidStateException,
ListKeysCommand,
ListResourceTagsCommand,
NotFoundException,
OriginType,
ScheduleKeyDeletionCommand
} from "@aws-sdk/client-kms";
import {
PublicKey as PublicKey2
} from "@planetarium/account";
var AwsKmsKeyStore = class {
#client;
#options;
constructor(client, options = {}) {
this.#client = client;
this.#options = {
listWindow: options.listWindow ?? 100,
scopingTags: options.scopingTags ?? {}
};
}
#isValidKey(metadata) {
return metadata.KeyId != null && metadata.Enabled === true && metadata.DeletionDate == null && metadata.KeySpec === "ECC_SECG_P256K1" && metadata.KeyUsage === "SIGN_VERIFY";
}
#mapMetadata(metadata) {
return {
customKeyStoreId: metadata.CustomKeyStoreId,
description: metadata.Description ?? "",
multiRegion: metadata.MultiRegion ?? false,
origin: metadata.Origin ?? OriginType.AWS_KMS
};
}
async #hasTags(keyId, tags) {
let marker;
const remainTags = new Map(Object.entries(tags));
do {
const cmd = new ListResourceTagsCommand({ KeyId: keyId, Marker: marker });
const result = await this.#client.send(cmd);
marker = result.NextMarker;
if (result.Tags == null)
continue;
for (const { TagKey, TagValue } of result.Tags) {
if (TagKey == null || TagValue == null)
continue;
if (remainTags.has(TagKey)) {
if (remainTags.get(TagKey) === TagValue) {
remainTags.delete(TagKey);
if (remainTags.size < 1)
return true;
} else
return false;
}
}
} while (marker != null);
return remainTags.size < 1;
}
async *list() {
let nextMarker;
const hasScopingTags = Object.keys(this.#options).length > 0;
do {
const listCmd = new ListKeysCommand({
Marker: nextMarker,
Limit: this.#options.listWindow
});
const resp = await this.#client.send(listCmd);
const keys = resp.Keys ?? [];
for (let i = 0; i < keys.length; i += 5) {
const promises = keys.slice(i, i + 5).map(({ KeyId }) => new DescribeKeyCommand({ KeyId })).map((cmd) => this.#client.send(cmd));
const responses = await Promise.all(promises);
for (const resp2 of responses) {
const metadata = resp2.KeyMetadata;
if (metadata == null || !this.#isValidKey(metadata) || hasScopingTags && !this.#hasTags(metadata.KeyId, this.#options.scopingTags)) {
continue;
}
yield {
keyId: metadata.KeyId,
metadata: this.#mapMetadata(metadata),
createdAt: metadata.CreationDate
};
}
}
nextMarker = resp.NextMarker;
} while (nextMarker != null);
}
async get(keyId) {
const descCmd = new DescribeKeyCommand({ KeyId: keyId });
const pubKeyCmd = new GetPublicKeyCommand({ KeyId: keyId });
const descPromise = this.#client.send(descCmd);
const pubKeyPromise = this.#client.send(pubKeyCmd);
let descResp;
let pubKeyResp;
try {
[descResp, pubKeyResp] = await Promise.all([descPromise, pubKeyPromise]);
} catch (e) {
if (e instanceof NotFoundException || e instanceof KMSInvalidStateException) {
return { result: "keyNotFound", keyId };
}
return { result: "error", keyId, message: `${e}` };
}
if (descResp.KeyMetadata == null || !this.#isValidKey(descResp.KeyMetadata) || pubKeyResp.PublicKey == null) {
return { result: "keyNotFound", keyId };
}
const publicKeyBytes = parseSubjectPublicKeyInfo(
pubKeyResp.PublicKey
);
const publicKey = PublicKey2.fromBytes(publicKeyBytes, "uncompressed");
return {
result: "success",
keyId,
account: new AwsKmsAccount(keyId, publicKey, this.#client),
metadata: this.#mapMetadata(descResp.KeyMetadata),
createdAt: descResp.KeyMetadata.CreationDate
};
}
async generate(metadata) {
const cmd = new CreateKeyCommand({
KeySpec: "ECC_SECG_P256K1",
KeyUsage: "SIGN_VERIFY",
CustomKeyStoreId: metadata?.customKeyStoreId,
Description: metadata?.description,
MultiRegion: metadata?.multiRegion,
Origin: metadata?.origin,
Tags: Object.entries(this.#options.scopingTags).map(
([TagKey, TagValue]) => ({ TagKey, TagValue })
)
});
let response;
try {
response = await this.#client.send(cmd);
} catch (e) {
return { result: "error", message: `${e}` };
}
const keyId = response.KeyMetadata?.KeyId;
if (keyId == null) {
return { result: "error", message: "failed to determine keyId" };
}
const pubKeyCmd = new GetPublicKeyCommand({ KeyId: keyId });
let pubKeyResp;
try {
pubKeyResp = await this.#client.send(pubKeyCmd);
} catch (e) {
return { result: "error", message: `${e}` };
}
if (pubKeyResp.PublicKey == null) {
return { result: "error", message: "failed to get public key" };
}
const publicKeyBytes = parseSubjectPublicKeyInfo(
pubKeyResp.PublicKey
);
const publicKey = PublicKey2.fromBytes(publicKeyBytes, "uncompressed");
const account = new AwsKmsAccount(keyId, publicKey, this.#client);
return { result: "success", keyId, account };
}
async delete(keyId) {
const cmd = new ScheduleKeyDeletionCommand({ KeyId: keyId });
try {
await this.#client.send(cmd);
} catch (e) {
if (e instanceof NotFoundException || e instanceof KMSInvalidStateException) {
return { result: "keyNotFound", keyId };
}
return { result: "error", message: `${e}` };
}
return { result: "success", keyId };
}
};
__name(AwsKmsKeyStore, "AwsKmsKeyStore");
// src/index.ts
import { KMSClient as KMSClient3 } from "@aws-sdk/client-kms";
export {
AwsKmsAccount,
AwsKmsKeyStore,
KMSClient3 as KMSClient
};
/*! For license information please see index.js.LEGAL.txt */
//# sourceMappingURL=index.js.map