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
JavaScript
;
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