UNPKG

@planetarium/account-aws-kms

Version:

Libplanet account implementation using AWS KMS

273 lines (266 loc) 8.62 kB
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/browser.ts var crypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : 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.browser.mjs.LEGAL.txt */ //# sourceMappingURL=index.browser.mjs.map