UNPKG

dotencr

Version:

Encrypt and decrypt individual lines inside a .env file. Supports multiple encryption keys. Keys and dotenv files can be read text file or read from the environment.

224 lines 9.96 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = __importDefault(require("crypto")); const lodash_1 = __importDefault(require("lodash")); const assert_1 = __importDefault(require("assert")); // import assert from "assert-plus"; /** * Status of an attempt to encrypt or decrypt a dotenv variable */ var StatusCryptoWrapperResult; (function (StatusCryptoWrapperResult) { /** * Operation successful */ StatusCryptoWrapperResult["success"] = "success"; /** * Encryption key could not be found */ StatusCryptoWrapperResult["missingEncryptionKey"] = "missingEncryptionKey"; /** * An exception occurred during the encryption or decryption */ StatusCryptoWrapperResult["exception"] = "exception"; /** * All the encryption keys were tried but none were successful. */ StatusCryptoWrapperResult["wildcardNoKeysMatch"] = "wildcardNoKeysMatch"; })(StatusCryptoWrapperResult = exports.StatusCryptoWrapperResult || (exports.StatusCryptoWrapperResult = {})); /** * Convert an error to something human readable */ exports.statusCryptoWrapperToText = (status, keyName, exception) => { switch (status) { case StatusCryptoWrapperResult.success: return "Success"; case StatusCryptoWrapperResult.missingEncryptionKey: return `Encryption key not found [${keyName}]`; case StatusCryptoWrapperResult.wildcardNoKeysMatch: return `None of the encryption keys could decrypt [${keyName}]`; case StatusCryptoWrapperResult.exception: if (exception) { return `Exception: Variable: [${keyName}], Message: [${exception.message}]`; } else { throw "Expected the parameter [exception] to be defined."; } default: throw "Unhandled value for StatusCryptoWrapperResult"; } }; class CryptoWrapperAes256Gcm { constructor(cryptoMgr, forceIvToFixedValue = false) { /** * Used exclusively for testing to force the IV to be the same value, so encrypted * always match the expected value. If TRUE, this weakens the strength of the encryption. */ this.forceIvToFixedValue = false; this.cryptoMgr = cryptoMgr; this.forceIvToFixedValue = forceIvToFixedValue; } algo() { return CryptoWrapperAes256Gcm.SYM_ENC_ALGO; } newDotEnvVariableName(dv) { if (dv.leftPattern() === "UNENCRYPTED") { return `CIPHERED${dv.separatorLeft}${dv.leftCryptoKeyName()}${dv.separatorLeft}${dv.leftVariableName()}`; } else if (dv.leftPattern() === "CIPHERED") { return `UNENCRYPTED${dv.separatorLeft}${dv.leftCryptoKeyName()}${dv.separatorLeft}${dv.leftVariableName()}`; } else { throw "Unhandled case"; } } encryptWithKeyManager(dotenvPlainTextVariables) { const resultArray = []; for (let i = 0; i < dotenvPlainTextVariables.length; i++) { const p = dotenvPlainTextVariables[i]; const keyName = p.leftCryptoKeyName(); assert_1.default.equal(p.isValid, true); const key = this.cryptoMgr.get(keyName); if (key) { const res = this.encrypt(p, key); resultArray.push(res); } else { resultArray.push({ status: StatusCryptoWrapperResult.missingEncryptionKey, oldDotenvVariableName: p.leftSide, newDotenvVariableName: this.newDotEnvVariableName(p), failureExplanation: [ exports.statusCryptoWrapperToText(StatusCryptoWrapperResult.missingEncryptionKey, this.cryptoMgr.buildDotenvNameFromKeyName(keyName)), ], }); } } return resultArray; } decryptWithKeyManager(dotenvCipherVariables) { const resultArray = []; dotenvCipherVariables.forEach(cipher => { const requestedKeyName = cipher.leftCryptoKeyName(); const failureExplanation = []; if (requestedKeyName === "WILDCARD") { // attempt to use all the keys const keyNames = this.cryptoMgr.allKeys(); let keyFound = false; for (let i = 0; i < keyNames.length; i++) { const key = this.cryptoMgr.get(keyNames[i]); const res = this.decrypt(cipher, key); if (res.status === StatusCryptoWrapperResult.success) { resultArray.push(res); keyFound = true; break; } else { failureExplanation.push(...res.failureExplanation); } } if (!keyFound) { resultArray.push({ status: StatusCryptoWrapperResult.wildcardNoKeysMatch, oldDotenvVariableName: cipher.leftSide, newDotenvVariableName: this.newDotEnvVariableName(cipher), failureExplanation: [ exports.statusCryptoWrapperToText(StatusCryptoWrapperResult.wildcardNoKeysMatch, cipher.leftSide), ...lodash_1.default.uniq(failureExplanation), ], result: undefined, }); } } else { const key = this.cryptoMgr.get(requestedKeyName); if (key) { const res = this.decrypt(cipher, key); resultArray.push(res); } else { resultArray.push({ status: StatusCryptoWrapperResult.missingEncryptionKey, oldDotenvVariableName: cipher.leftSide, newDotenvVariableName: this.newDotEnvVariableName(cipher), failureExplanation: [ exports.statusCryptoWrapperToText(StatusCryptoWrapperResult.missingEncryptionKey, requestedKeyName), ], }); } } }); return resultArray; } encrypt(plainText, key) { try { const IV = this.forceIvToFixedValue ? Buffer.alloc(CryptoWrapperAes256Gcm.SYM_ENC_IV_LENGTH) : crypto_1.default.randomBytes(CryptoWrapperAes256Gcm.SYM_ENC_IV_LENGTH); const cipher = crypto_1.default.createCipheriv(CryptoWrapperAes256Gcm.SYM_ENC_ALGO, key.keyObject, IV, { authTagLength: CryptoWrapperAes256Gcm.SYM_ENC_TAG_LENGTH, }); const cipherBuffer = cipher.update(plainText.rightSide); const cipherText = Buffer.concat([cipherBuffer, cipher.final()]).toString("base64"); return { status: StatusCryptoWrapperResult.success, oldDotenvVariableName: plainText.leftSide, newDotenvVariableName: this.newDotEnvVariableName(plainText), failureExplanation: [], result: `${IV.toString("base64")}.${cipherText}.${cipher .getAuthTag() .toString("base64")}`, }; } catch (e) { const res = { status: StatusCryptoWrapperResult.exception, oldDotenvVariableName: plainText.leftSide, newDotenvVariableName: this.newDotEnvVariableName(plainText), failureExplanation: [ exports.statusCryptoWrapperToText(StatusCryptoWrapperResult.exception, plainText.leftSide, e), ], result: undefined, }; return res; } } decrypt(cipherText, key) { try { const decipher = crypto_1.default.createDecipheriv(CryptoWrapperAes256Gcm.SYM_ENC_ALGO, key.keyObject, Buffer.from(cipherText.rightIV(), "base64"), {}); decipher.setAuthTag(Buffer.from(cipherText.rightSignature(), "base64")); let decrypted = decipher.update(Buffer.from(cipherText.rightCipheredText(), "base64")); decrypted = Buffer.concat([decrypted, decipher.final()]); return { status: StatusCryptoWrapperResult.success, oldDotenvVariableName: cipherText.leftSide, newDotenvVariableName: this.newDotEnvVariableName(cipherText), failureExplanation: [], result: decrypted.toString("ascii"), }; } catch (e) { const res = { status: StatusCryptoWrapperResult.exception, oldDotenvVariableName: cipherText.leftSide, newDotenvVariableName: this.newDotEnvVariableName(cipherText), failureExplanation: [ exports.statusCryptoWrapperToText(StatusCryptoWrapperResult.exception, cipherText.leftSide, e), ], result: undefined, }; return res; } } } exports.CryptoWrapperAes256Gcm = CryptoWrapperAes256Gcm; // GCM mode generates a TAG at the end of the encryption and is needed when decrypting. // The TAG serves as a type of signature to ensure the ciphered text has not been altered. CryptoWrapperAes256Gcm.SYM_ENC_ALGO = "aes-256-gcm"; CryptoWrapperAes256Gcm.SYM_ENC_KEY_LENGTH = 32; // 256 bits CryptoWrapperAes256Gcm.SYM_ENC_IV_LENGTH = 12; // 96 bits CryptoWrapperAes256Gcm.SYM_ENC_TAG_LENGTH = 16; // 128 bits //# sourceMappingURL=crypto_wrapper.js.map