UNPKG

lemon-core

Version:
235 lines 10.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.KeyVaultService = exports.fromBase64 = exports.isBase64 = void 0; /** * file: `cores/aws-kms-service.ts` * - kms service for AWS KMS. * * * **TODO** * - [x] in VPC lambda, will timeout due to NAT => so, need to support via engine. => use `VPC Endpoints` with kms service. * - [x] error of `not authorized` => use one of below. * 1. add this Role (lemon-todaq-api-prod-ap-northeast-2-lambdaRole) to `Key users`. * 2. add `kms:Decrypt` `kms:Encrypt` in resource of `iamRoleStatements`. * * * @author Ian Kim <ian@lemoncloud.io> * @date 2023-09-30 initial version. * * @copyright (C) lemoncloud.io 2023 - All Rights Reserved. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const engine_1 = require("../../engine"); require("dotenv/config"); const NS = engine_1.$U.NS('AZKV', 'blue'); // NAMESPACE TO BE PRINTED. // type MySigningAlgorithm = SigningAlgorithmSpec; const ALIAS = `lemon-hello-api`; //NOTE - use env[KMS_KEY_ID] to overide. const region = () => engine_1.$engine.environ('REGION', 'ap-northeast-2'); //! get aws client for KMS // const instance = () => { // const _region = region(); // const config = { region: _region }; // return new AWS.KMS(config); // }; const instance = () => { return KeyVaultService.instance(); }; /** * check if base64 string. */ const isBase64 = (text) => /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/.test(text); exports.isBase64 = isBase64; /** * normal base64 to url encoded. */ const fromBase64 = (base64) => base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); exports.fromBase64 = fromBase64; /** * class: `KeyVaultService` * - shared Key Management Service to encrypt/decrypt message. */ class KeyVaultService { constructor(keyId, options) { /** * get name of this */ this.name = () => `KMS`; /** * hello */ this.hello = () => `azure-keyvault-service:${this._keyId}`; /** * get key-id to encrypt. */ this.keyId = () => { if (!this._keyId || typeof this._keyId !== 'string') throw new Error(`.keyId<${typeof this._keyId}> (string) is required!`); return this._keyId; }; this.instance = () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { KeyClient, CryptographyClient, EncryptResult, DecryptResult } = require('@azure/keyvault-keys'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { DefaultAzureCredential } = require('@azure/identity'); const keyVault = process.env.KEY_VAULT_RESOURCE; const vaultUrl = `https://${keyVault}.vault.azure.net/`; const credentials = new DefaultAzureCredential(); const keyClient = new KeyClient(vaultUrl, credentials); return { keyClient, credentials, CryptographyClient, EncryptResult, DecryptResult }; }; /** * get KMS instance in stock */ // public instance() { // if (!this._instance) this._instance = instance(); // return this._instance; // } /** * Encrypt message * * @param {*} message */ this.encrypt = (message) => __awaiter(this, void 0, void 0, function* () { const keyId = this.keyId(); (0, engine_1._inf)(NS, `encrypt(${keyId}, ${message.substring(0, 10)}...)..`); const { keyClient, credentials, CryptographyClient } = this.instance(); const keyVaultKey = yield keyClient.getKey(keyId); const cryptographyClient = new CryptographyClient(keyVaultKey, credentials); try { const EncryptResult = yield cryptographyClient.encrypt({ algorithm: 'RSA1_5', plaintext: Buffer.from(message), }); if (!Buffer.from(EncryptResult.result, 'hex').toString('base64')) { throw new Error(`buffered ${EncryptResult} (string) is required!`); } return Buffer.from(EncryptResult.result, 'hex').toString('base64'); } catch (err) { console.log(err); } }); /** * Decrypt message * * @param {*} encryptedSecret */ this.decrypt = (encryptedSecret) => __awaiter(this, void 0, void 0, function* () { (0, engine_1._inf)(NS, `decrypt(${encryptedSecret.substring(0, 12)}...)..`); const bufferedEncryptedSecret = Buffer.from(encryptedSecret, 'base64'); if (!bufferedEncryptedSecret) { throw new Error(`${bufferedEncryptedSecret} (string) is required!`); } const keyId = this.keyId(); const { keyClient, credentials, CryptographyClient } = this.instance(); const keyVaultKey = yield keyClient.getKey(keyId); const cryptographyClient = new CryptographyClient(keyVaultKey, credentials); try { const decryptedSecret = yield cryptographyClient.decrypt({ algorithm: 'RSA1_5', ciphertext: bufferedEncryptedSecret, }); return decryptedSecret.result.toString(); } catch (err) { console.log(err); } }); /** * make signature by message * * @param {*} message any string * @param forJwtSignature (option) flag to get JWT signature format. */ this.sign = (message, forJwtSignature = true) => __awaiter(this, void 0, void 0, function* () { if (!message || typeof message !== 'string') throw new Error(`@message[${message}] is invalid - kms.sign()`); const keyId = this.keyId(); const { keyClient, credentials, CryptographyClient } = this.instance(); const keyVaultKey = yield keyClient.getKey(keyId); const cryptographyClient = new CryptographyClient(keyVaultKey.id, credentials); (0, engine_1._inf)(NS, `sign(${keyId}, ${message.substring(0, 10)}...)..`); const result = yield cryptographyClient.signData('RS256', Buffer.from(message)); const signature = result.result; return signature; }); /** * verify signature in asymetric way * - it tooks around `30ms` * * @param {*} message any string * @param {*} signature signature of Buffer or string(in base64) */ this.verify = (message, signature) => __awaiter(this, void 0, void 0, function* () { if (!message || typeof message !== 'string') throw new Error(`@message[${message}] is invalid - kms.verify()`); if (!signature) throw new Error(`@signature (string|Buffer) is required - kms.verify()`); const keyId = this.keyId(); const { keyClient, credentials, CryptographyClient } = this.instance(); const keyVaultKey = yield keyClient.getKey(keyId); const cryptographyClient = new CryptographyClient(keyVaultKey.id, credentials); (0, engine_1._inf)(NS, `verify(${keyId}, ${message.substring(0, 10)}...)..`); const result = yield cryptographyClient.verifyData('RS256', Buffer.from(message), Buffer.from(signature)); return result.result; }); /** * retrieve public-key for asymetric verification. * - used to verify signature with JWT library w/o making request to AWS KMS. * - in general, cache this `public-key` to verify locally. * * @param encoding (optional) encoding type */ this.getPublicKey = (encoding = 'base64') => __awaiter(this, void 0, void 0, function* () { const keyId = this.keyId(); (0, engine_1._inf)(NS, `getPublicKey(${keyId})..`); const { keyClient } = this.instance(); const result = yield keyClient.getKey(keyId); return result.toString(); }); keyId = keyId !== null && keyId !== void 0 ? keyId : `${engine_1.$engine.environ(KeyVaultService.ENV_KMS_KEY_ID, KeyVaultService.DEF_KMS_TARGET)}`; this._keyId = keyId; this._options = options; } /** * it should be 'hello lemon' * * # Example * ```sh * # encrypt text * $ aws kms encrypt --profile <profile> --key-id <kms-key-id> --plaintext "hello lemon" --query CiphertextBlob --output text * ``` */ sample() { return __awaiter(this, void 0, void 0, function* () { (0, engine_1._inf)(NS, 'sample()..'); //! check key-id. const message = `hello lemon!`; const KMS_KEY_ID = engine_1.$engine.environ(KeyVaultService.ENV_KMS_KEY_ID, `alias/${ALIAS}`); const keyId = this.keyId(); (0, engine_1._log)(NS, '> key-id =', keyId); const encrypted = yield this.encrypt(message); (0, engine_1._log)(NS, '> encrypted =', encrypted); const decrypted = yield this.decrypt(encrypted); (0, engine_1._log)(NS, '> decrypted =', decrypted, '->', message == decrypted ? 'ok' : 'error'); return { KMS_KEY_ID, keyId, message, encrypted, decrypted }; }); } } exports.KeyVaultService = KeyVaultService; /** * environ name of KMS KEY */ KeyVaultService.ENV_KMS_KEY_ID = 'KMS_KEY_ID'; KeyVaultService.DEF_KMS_TARGET = (_a = process.env.KEY_VAULT_KEYS) !== null && _a !== void 0 ? _a : `key-lemon`; //# sourceMappingURL=azure-keyvault-service.js.map