@naturalcycles/nodejs-lib 12.66.0 → 12.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/secrets-decrypt.js +13 -4
- package/dist/bin/secrets-encrypt.js +14 -4
- package/dist/secret/secrets-decrypt.util.d.ts +3 -2
- package/dist/secret/secrets-decrypt.util.js +21 -6
- package/dist/secret/secrets-encrypt.util.d.ts +3 -2
- package/dist/secret/secrets-encrypt.util.js +21 -9
- package/dist/security/crypto.util.d.ts +7 -0
- package/dist/security/crypto.util.js +36 -16
- package/dist/security/secret.util.js +2 -4
- package/dist/util/lruMemoCache.d.ts +1 -2
- package/package.json +1 -1
- package/src/bin/secrets-decrypt.ts +13 -4
- package/src/bin/secrets-encrypt.ts +14 -4
- package/src/secret/secrets-decrypt.util.ts +35 -8
- package/src/secret/secrets-encrypt.util.ts +33 -11
- package/src/security/crypto.util.ts +38 -18
- package/src/security/secret.util.ts +4 -6
- package/src/util/lruMemoCache.ts +1 -1
|
@@ -6,18 +6,22 @@ const colors_1 = require("../colors");
|
|
|
6
6
|
const script_1 = require("../script");
|
|
7
7
|
const secrets_decrypt_util_1 = require("../secret/secrets-decrypt.util");
|
|
8
8
|
(0, script_1.runScript)(() => {
|
|
9
|
-
const { dir, encKey, del } = getDecryptCLIOptions();
|
|
10
|
-
(0, secrets_decrypt_util_1.secretsDecrypt)(dir, encKey, del);
|
|
9
|
+
const { dir, file, encKey, del, jsonMode } = getDecryptCLIOptions();
|
|
10
|
+
(0, secrets_decrypt_util_1.secretsDecrypt)(dir, file, encKey, del, jsonMode);
|
|
11
11
|
});
|
|
12
12
|
function getDecryptCLIOptions() {
|
|
13
13
|
require('dotenv').config();
|
|
14
|
-
let { dir, encKey, encKeyVar, del } = yargs.options({
|
|
14
|
+
let { dir, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
15
15
|
dir: {
|
|
16
16
|
type: 'array',
|
|
17
17
|
desc: 'Directory with secrets. Can be many',
|
|
18
18
|
// demandOption: true,
|
|
19
19
|
default: './secret',
|
|
20
20
|
},
|
|
21
|
+
file: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
24
|
+
},
|
|
21
25
|
encKey: {
|
|
22
26
|
type: 'string',
|
|
23
27
|
desc: 'Encryption key',
|
|
@@ -37,6 +41,11 @@ function getDecryptCLIOptions() {
|
|
|
37
41
|
type: 'boolean',
|
|
38
42
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
39
43
|
},
|
|
44
|
+
jsonMode: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
40
49
|
}).argv;
|
|
41
50
|
if (!encKey) {
|
|
42
51
|
encKey = process.env[encKeyVar];
|
|
@@ -48,5 +57,5 @@ function getDecryptCLIOptions() {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
51
|
-
return { dir: dir, encKey, del };
|
|
60
|
+
return { dir: dir, file, encKey, del, jsonMode };
|
|
52
61
|
}
|
|
@@ -6,12 +6,12 @@ const colors_1 = require("../colors");
|
|
|
6
6
|
const script_1 = require("../script");
|
|
7
7
|
const secrets_encrypt_util_1 = require("../secret/secrets-encrypt.util");
|
|
8
8
|
(0, script_1.runScript)(() => {
|
|
9
|
-
const { pattern, encKey, del } = getEncryptCLIOptions();
|
|
10
|
-
(0, secrets_encrypt_util_1.secretsEncrypt)(pattern, encKey, del);
|
|
9
|
+
const { pattern, file, encKey, del, jsonMode } = getEncryptCLIOptions();
|
|
10
|
+
(0, secrets_encrypt_util_1.secretsEncrypt)(pattern, file, encKey, del, jsonMode);
|
|
11
11
|
});
|
|
12
12
|
function getEncryptCLIOptions() {
|
|
13
13
|
require('dotenv').config();
|
|
14
|
-
let { pattern, encKey, encKeyVar, del } = yargs.options({
|
|
14
|
+
let { pattern, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
15
15
|
pattern: {
|
|
16
16
|
type: 'string',
|
|
17
17
|
array: true,
|
|
@@ -19,6 +19,10 @@ function getEncryptCLIOptions() {
|
|
|
19
19
|
// demandOption: true,
|
|
20
20
|
default: './secret/**',
|
|
21
21
|
},
|
|
22
|
+
file: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
25
|
+
},
|
|
22
26
|
encKey: {
|
|
23
27
|
type: 'string',
|
|
24
28
|
desc: 'Encryption key',
|
|
@@ -37,6 +41,12 @@ function getEncryptCLIOptions() {
|
|
|
37
41
|
del: {
|
|
38
42
|
type: 'boolean',
|
|
39
43
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
44
|
+
default: false,
|
|
45
|
+
},
|
|
46
|
+
jsonMode: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
49
|
+
default: false,
|
|
40
50
|
},
|
|
41
51
|
}).argv;
|
|
42
52
|
if (!encKey) {
|
|
@@ -49,5 +59,5 @@ function getEncryptCLIOptions() {
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
52
|
-
return { pattern: pattern, encKey, del };
|
|
62
|
+
return { pattern: pattern, file, encKey, del, jsonMode };
|
|
53
63
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export interface DecryptCLIOptions {
|
|
2
2
|
dir: string[];
|
|
3
|
+
file?: string;
|
|
3
4
|
encKey: string;
|
|
4
|
-
algorithm?: string;
|
|
5
5
|
del?: boolean;
|
|
6
|
+
jsonMode?: boolean;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
|
|
9
10
|
* Using provided encKey.
|
|
10
11
|
*/
|
|
11
|
-
export declare function secretsDecrypt(dir: string[], encKey: string, del?: boolean): void;
|
|
12
|
+
export declare function secretsDecrypt(dir: string[], file: string | undefined, encKey: string, del?: boolean, jsonMode?: boolean): void;
|
|
@@ -2,22 +2,37 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.secretsDecrypt = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const fs = require("fs-extra");
|
|
6
7
|
const globby = require("globby");
|
|
7
8
|
const colors_1 = require("../colors");
|
|
8
9
|
const crypto_util_1 = require("../security/crypto.util");
|
|
10
|
+
// Debug it like this:
|
|
11
|
+
// yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
12
|
+
// yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets2.plain.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
9
13
|
/**
|
|
10
14
|
* Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
|
|
11
15
|
* Using provided encKey.
|
|
12
16
|
*/
|
|
13
|
-
function secretsDecrypt(dir, encKey, del) {
|
|
14
|
-
|
|
17
|
+
function secretsDecrypt(dir, file, encKey, del = false, jsonMode = false) {
|
|
18
|
+
// If `file` is provided - only this one file is used
|
|
19
|
+
const patterns = file ? [file] : dir.map(d => `${d}/**/*.enc`);
|
|
15
20
|
const filenames = globby.sync(patterns);
|
|
16
21
|
filenames.forEach(filename => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
let plainFilename;
|
|
23
|
+
if (jsonMode) {
|
|
24
|
+
(0, js_lib_1._assert)(filename.endsWith('.json'), `${path.basename(filename)} MUST end with '.json'`);
|
|
25
|
+
(0, js_lib_1._assert)(!filename.endsWith('.plain.json'), `${path.basename(filename)} MUST NOT end with '.plain.json'`);
|
|
26
|
+
plainFilename = filename.replace('.json', '.plain.json');
|
|
27
|
+
const json = (0, crypto_util_1.decryptObject)(JSON.parse(fs.readFileSync(filename, 'utf8')), encKey);
|
|
28
|
+
fs.writeFileSync(plainFilename, JSON.stringify(json, null, 2));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const enc = fs.readFileSync(filename);
|
|
32
|
+
const plain = (0, crypto_util_1.decryptRandomIVBuffer)(enc, encKey);
|
|
33
|
+
plainFilename = filename.slice(0, filename.length - '.enc'.length);
|
|
34
|
+
fs.writeFileSync(plainFilename, plain);
|
|
35
|
+
}
|
|
21
36
|
if (del) {
|
|
22
37
|
fs.unlinkSync(filename);
|
|
23
38
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export interface EncryptCLIOptions {
|
|
2
2
|
pattern: string[];
|
|
3
|
+
file?: string;
|
|
3
4
|
encKey: string;
|
|
4
|
-
algorithm?: string;
|
|
5
5
|
del?: boolean;
|
|
6
|
+
jsonMode?: boolean;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
|
|
9
10
|
* Using provided encKey.
|
|
10
11
|
*/
|
|
11
|
-
export declare function secretsEncrypt(pattern: string[], encKey: string, del?: boolean): void;
|
|
12
|
+
export declare function secretsEncrypt(pattern: string[], file: string | undefined, encKey: string, del?: boolean, jsonMode?: boolean): void;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.secretsEncrypt = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const fs = require("fs-extra");
|
|
6
7
|
const globby = require("globby");
|
|
7
8
|
const colors_1 = require("../colors");
|
|
@@ -10,17 +11,28 @@ const crypto_util_1 = require("../security/crypto.util");
|
|
|
10
11
|
* Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
|
|
11
12
|
* Using provided encKey.
|
|
12
13
|
*/
|
|
13
|
-
function secretsEncrypt(pattern, encKey, del) {
|
|
14
|
-
const patterns =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
function secretsEncrypt(pattern, file, encKey, del = false, jsonMode = false) {
|
|
15
|
+
const patterns = file
|
|
16
|
+
? [file]
|
|
17
|
+
: [
|
|
18
|
+
...pattern,
|
|
19
|
+
`!**/*.enc`, // excluding already encoded
|
|
20
|
+
];
|
|
18
21
|
const filenames = globby.sync(patterns);
|
|
22
|
+
let encFilename;
|
|
19
23
|
filenames.forEach(filename => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if (jsonMode) {
|
|
25
|
+
(0, js_lib_1._assert)(filename.endsWith('.plain.json'), `${path.basename(filename)} MUST end with '.plain.json'`);
|
|
26
|
+
encFilename = filename.replace('.plain', '');
|
|
27
|
+
const json = (0, crypto_util_1.encryptObject)(JSON.parse(fs.readFileSync(filename, 'utf8')), encKey);
|
|
28
|
+
fs.writeFileSync(encFilename, JSON.stringify(json, null, 2));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const plain = fs.readFileSync(filename);
|
|
32
|
+
const enc = (0, crypto_util_1.encryptRandomIVBuffer)(plain, encKey);
|
|
33
|
+
encFilename = `${filename}.enc`;
|
|
34
|
+
fs.writeFileSync(encFilename, enc);
|
|
35
|
+
}
|
|
24
36
|
if (del) {
|
|
25
37
|
fs.unlinkSync(filename);
|
|
26
38
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
import { StringMap } from '@naturalcycles/js-lib';
|
|
2
3
|
/**
|
|
3
4
|
* Using aes-256-cbc
|
|
4
5
|
*/
|
|
@@ -7,6 +8,12 @@ export declare function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: st
|
|
|
7
8
|
* Using aes-256-cbc
|
|
8
9
|
*/
|
|
9
10
|
export declare function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer;
|
|
11
|
+
/**
|
|
12
|
+
* Decrypts all object values.
|
|
13
|
+
* Returns object with decrypted values.
|
|
14
|
+
*/
|
|
15
|
+
export declare function decryptObject(obj: StringMap, secretKey: string): StringMap;
|
|
16
|
+
export declare function encryptObject(obj: StringMap, secretKey: string): StringMap;
|
|
10
17
|
/**
|
|
11
18
|
* Using aes-256-cbc
|
|
12
19
|
*/
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.encryptString = exports.decryptString = exports.decryptRandomIVBuffer = exports.encryptRandomIVBuffer = void 0;
|
|
3
|
+
exports.encryptString = exports.decryptString = exports.encryptObject = exports.decryptObject = exports.decryptRandomIVBuffer = exports.encryptRandomIVBuffer = void 0;
|
|
4
4
|
const crypto = require("crypto");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
const hash_util_1 = require("./hash.util");
|
|
6
7
|
const algorithm = 'aes-256-cbc';
|
|
7
8
|
/**
|
|
8
9
|
* Using aes-256-cbc
|
|
9
10
|
*/
|
|
10
11
|
function encryptRandomIVBuffer(input, secretKeyBase64) {
|
|
11
|
-
|
|
12
|
+
// md5 to match aes-256 key length of 32 bytes
|
|
13
|
+
const key = (0, hash_util_1.md5)(Buffer.from(secretKeyBase64, 'base64'));
|
|
12
14
|
// Random iv to achieve non-deterministic encryption (but deterministic decryption)
|
|
13
|
-
|
|
14
|
-
const iv = crypto.randomBytes(16); // use sync method here for speed
|
|
15
|
+
const iv = crypto.randomBytes(16);
|
|
15
16
|
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
16
17
|
return Buffer.concat([iv, cipher.update(input), cipher.final()]);
|
|
17
18
|
}
|
|
@@ -20,7 +21,8 @@ exports.encryptRandomIVBuffer = encryptRandomIVBuffer;
|
|
|
20
21
|
* Using aes-256-cbc
|
|
21
22
|
*/
|
|
22
23
|
function decryptRandomIVBuffer(input, secretKeyBase64) {
|
|
23
|
-
|
|
24
|
+
// md5 to match aes-256 key length of 32 bytes
|
|
25
|
+
const key = (0, hash_util_1.md5)(Buffer.from(secretKeyBase64, 'base64'));
|
|
24
26
|
// iv is first 16 bytes of encrypted buffer, the rest is payload
|
|
25
27
|
const iv = input.slice(0, 16);
|
|
26
28
|
const payload = input.slice(16);
|
|
@@ -28,32 +30,50 @@ function decryptRandomIVBuffer(input, secretKeyBase64) {
|
|
|
28
30
|
return Buffer.concat([decipher.update(payload), decipher.final()]);
|
|
29
31
|
}
|
|
30
32
|
exports.decryptRandomIVBuffer = decryptRandomIVBuffer;
|
|
33
|
+
/**
|
|
34
|
+
* Decrypts all object values.
|
|
35
|
+
* Returns object with decrypted values.
|
|
36
|
+
*/
|
|
37
|
+
function decryptObject(obj, secretKey) {
|
|
38
|
+
const { key, iv } = getCryptoParams(secretKey);
|
|
39
|
+
const r = {};
|
|
40
|
+
(0, js_lib_1._stringMapEntries)(obj).forEach(([k, v]) => {
|
|
41
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
42
|
+
r[k] = decipher.update(v, 'base64', 'utf8') + decipher.final('utf8');
|
|
43
|
+
});
|
|
44
|
+
return r;
|
|
45
|
+
}
|
|
46
|
+
exports.decryptObject = decryptObject;
|
|
47
|
+
function encryptObject(obj, secretKey) {
|
|
48
|
+
const { key, iv } = getCryptoParams(secretKey);
|
|
49
|
+
const r = {};
|
|
50
|
+
(0, js_lib_1._stringMapEntries)(obj).forEach(([k, v]) => {
|
|
51
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
52
|
+
r[k] = cipher.update(v, 'utf8', 'base64') + cipher.final('base64');
|
|
53
|
+
});
|
|
54
|
+
return r;
|
|
55
|
+
}
|
|
56
|
+
exports.encryptObject = encryptObject;
|
|
31
57
|
/**
|
|
32
58
|
* Using aes-256-cbc
|
|
33
59
|
*/
|
|
34
60
|
function decryptString(str, secretKey) {
|
|
35
|
-
const {
|
|
61
|
+
const { key, iv } = getCryptoParams(secretKey);
|
|
36
62
|
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
37
|
-
|
|
38
|
-
return (decrypted += decipher.final('utf8'));
|
|
63
|
+
return decipher.update(str, 'base64', 'utf8') + decipher.final('utf8');
|
|
39
64
|
}
|
|
40
65
|
exports.decryptString = decryptString;
|
|
41
66
|
/**
|
|
42
67
|
* Using aes-256-cbc
|
|
43
68
|
*/
|
|
44
69
|
function encryptString(str, secretKey) {
|
|
45
|
-
const {
|
|
70
|
+
const { key, iv } = getCryptoParams(secretKey);
|
|
46
71
|
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
47
|
-
|
|
48
|
-
return (encrypted += cipher.final('base64'));
|
|
72
|
+
return cipher.update(str, 'utf8', 'base64') + cipher.final('base64');
|
|
49
73
|
}
|
|
50
74
|
exports.encryptString = encryptString;
|
|
51
75
|
function getCryptoParams(secretKey) {
|
|
52
76
|
const key = (0, hash_util_1.md5)(secretKey);
|
|
53
77
|
const iv = (0, hash_util_1.md5)(secretKey + key).slice(0, 16);
|
|
54
|
-
return {
|
|
55
|
-
}
|
|
56
|
-
function aes256Key(secretKeyBase64) {
|
|
57
|
-
// md5 to match aes-256 key length of 32 bytes
|
|
58
|
-
return (0, hash_util_1.md5)(Buffer.from(secretKeyBase64, 'base64'));
|
|
78
|
+
return { key, iv };
|
|
59
79
|
}
|
|
@@ -69,11 +69,9 @@ exports.loadSecretsFromEncryptedJsonFile = loadSecretsFromEncryptedJsonFile;
|
|
|
69
69
|
*/
|
|
70
70
|
function loadSecretsFromEncryptedJsonFileValues(filePath, secretEncryptionKey) {
|
|
71
71
|
(0, js_lib_1._assert)(fs.existsSync(filePath), `loadSecretsFromEncryptedJsonFileValues() cannot load from path: ${filePath}`);
|
|
72
|
-
|
|
72
|
+
let secrets = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
73
73
|
if (secretEncryptionKey) {
|
|
74
|
-
(0,
|
|
75
|
-
secrets[k] = (0, crypto_util_1.decryptString)(enc, secretEncryptionKey);
|
|
76
|
-
});
|
|
74
|
+
secrets = (0, crypto_util_1.decryptObject)(secrets, secretEncryptionKey);
|
|
77
75
|
}
|
|
78
76
|
Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
|
|
79
77
|
loaded = true;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MemoCache } from '@naturalcycles/js-lib';
|
|
2
2
|
import LRUCache = require('lru-cache');
|
|
3
|
-
export
|
|
4
|
-
}
|
|
3
|
+
export declare type LRUMemoCacheOptions<KEY, VALUE> = Partial<LRUCache.Options<KEY, VALUE>>;
|
|
5
4
|
/**
|
|
6
5
|
* @example
|
|
7
6
|
* Use it like this:
|
package/package.json
CHANGED
|
@@ -6,21 +6,25 @@ import { runScript } from '../script'
|
|
|
6
6
|
import { DecryptCLIOptions, secretsDecrypt } from '../secret/secrets-decrypt.util'
|
|
7
7
|
|
|
8
8
|
runScript(() => {
|
|
9
|
-
const { dir, encKey, del } = getDecryptCLIOptions()
|
|
9
|
+
const { dir, file, encKey, del, jsonMode } = getDecryptCLIOptions()
|
|
10
10
|
|
|
11
|
-
secretsDecrypt(dir, encKey, del)
|
|
11
|
+
secretsDecrypt(dir, file, encKey, del, jsonMode)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
function getDecryptCLIOptions(): DecryptCLIOptions {
|
|
15
15
|
require('dotenv').config()
|
|
16
16
|
|
|
17
|
-
let { dir, encKey, encKeyVar, del } = yargs.options({
|
|
17
|
+
let { dir, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
18
18
|
dir: {
|
|
19
19
|
type: 'array',
|
|
20
20
|
desc: 'Directory with secrets. Can be many',
|
|
21
21
|
// demandOption: true,
|
|
22
22
|
default: './secret',
|
|
23
23
|
},
|
|
24
|
+
file: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
27
|
+
},
|
|
24
28
|
encKey: {
|
|
25
29
|
type: 'string',
|
|
26
30
|
desc: 'Encryption key',
|
|
@@ -40,6 +44,11 @@ function getDecryptCLIOptions(): DecryptCLIOptions {
|
|
|
40
44
|
type: 'boolean',
|
|
41
45
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
42
46
|
},
|
|
47
|
+
jsonMode: {
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
43
52
|
}).argv
|
|
44
53
|
|
|
45
54
|
if (!encKey) {
|
|
@@ -55,5 +64,5 @@ function getDecryptCLIOptions(): DecryptCLIOptions {
|
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
58
|
-
return { dir: dir as any, encKey, del }
|
|
67
|
+
return { dir: dir as any, file, encKey, del, jsonMode }
|
|
59
68
|
}
|
|
@@ -6,15 +6,15 @@ import { runScript } from '../script'
|
|
|
6
6
|
import { EncryptCLIOptions, secretsEncrypt } from '../secret/secrets-encrypt.util'
|
|
7
7
|
|
|
8
8
|
runScript(() => {
|
|
9
|
-
const { pattern, encKey, del } = getEncryptCLIOptions()
|
|
9
|
+
const { pattern, file, encKey, del, jsonMode } = getEncryptCLIOptions()
|
|
10
10
|
|
|
11
|
-
secretsEncrypt(pattern, encKey, del)
|
|
11
|
+
secretsEncrypt(pattern, file, encKey, del, jsonMode)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
15
15
|
require('dotenv').config()
|
|
16
16
|
|
|
17
|
-
let { pattern, encKey, encKeyVar, del } = yargs.options({
|
|
17
|
+
let { pattern, file, encKey, encKeyVar, del, jsonMode } = yargs.options({
|
|
18
18
|
pattern: {
|
|
19
19
|
type: 'string',
|
|
20
20
|
array: true,
|
|
@@ -22,6 +22,10 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
|
22
22
|
// demandOption: true,
|
|
23
23
|
default: './secret/**',
|
|
24
24
|
},
|
|
25
|
+
file: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
desc: 'Single file to decrypt. Useful in jsonMode',
|
|
28
|
+
},
|
|
25
29
|
encKey: {
|
|
26
30
|
type: 'string',
|
|
27
31
|
desc: 'Encryption key',
|
|
@@ -40,6 +44,12 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
|
40
44
|
del: {
|
|
41
45
|
type: 'boolean',
|
|
42
46
|
desc: 'Delete source files after encryption/decryption. Be careful!',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
jsonMode: {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
desc: 'JSON mode. Encrypts only json values, not the whole file',
|
|
52
|
+
default: false,
|
|
43
53
|
},
|
|
44
54
|
}).argv
|
|
45
55
|
|
|
@@ -56,5 +66,5 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
// `as any` because @types/yargs can't handle string[] type properly
|
|
59
|
-
return { pattern: pattern as any, encKey, del }
|
|
69
|
+
return { pattern: pattern as any, file, encKey, del, jsonMode }
|
|
60
70
|
}
|
|
@@ -1,31 +1,58 @@
|
|
|
1
1
|
import * as path from 'path'
|
|
2
|
+
import { _assert } from '@naturalcycles/js-lib'
|
|
2
3
|
import * as fs from 'fs-extra'
|
|
3
4
|
import globby = require('globby')
|
|
4
5
|
import { dimGrey, yellow } from '../colors'
|
|
5
|
-
import { decryptRandomIVBuffer } from '../security/crypto.util'
|
|
6
|
+
import { decryptObject, decryptRandomIVBuffer } from '../security/crypto.util'
|
|
6
7
|
|
|
7
8
|
export interface DecryptCLIOptions {
|
|
8
9
|
dir: string[]
|
|
10
|
+
file?: string
|
|
9
11
|
encKey: string
|
|
10
|
-
algorithm?: string
|
|
11
12
|
del?: boolean
|
|
13
|
+
jsonMode?: boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
// Debug it like this:
|
|
17
|
+
// yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
18
|
+
// yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets2.plain.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
|
|
16
22
|
* Using provided encKey.
|
|
17
23
|
*/
|
|
18
|
-
export function secretsDecrypt(
|
|
19
|
-
|
|
24
|
+
export function secretsDecrypt(
|
|
25
|
+
dir: string[],
|
|
26
|
+
file: string | undefined,
|
|
27
|
+
encKey: string,
|
|
28
|
+
del = false,
|
|
29
|
+
jsonMode = false,
|
|
30
|
+
): void {
|
|
31
|
+
// If `file` is provided - only this one file is used
|
|
32
|
+
const patterns = file ? [file] : dir.map(d => `${d}/**/*.enc`)
|
|
20
33
|
|
|
21
34
|
const filenames = globby.sync(patterns)
|
|
22
35
|
|
|
23
36
|
filenames.forEach(filename => {
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
let plainFilename
|
|
38
|
+
|
|
39
|
+
if (jsonMode) {
|
|
40
|
+
_assert(filename.endsWith('.json'), `${path.basename(filename)} MUST end with '.json'`)
|
|
41
|
+
_assert(
|
|
42
|
+
!filename.endsWith('.plain.json'),
|
|
43
|
+
`${path.basename(filename)} MUST NOT end with '.plain.json'`,
|
|
44
|
+
)
|
|
45
|
+
plainFilename = filename.replace('.json', '.plain.json')
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
const json = decryptObject(JSON.parse(fs.readFileSync(filename, 'utf8')), encKey)
|
|
48
|
+
|
|
49
|
+
fs.writeFileSync(plainFilename, JSON.stringify(json, null, 2))
|
|
50
|
+
} else {
|
|
51
|
+
const enc = fs.readFileSync(filename)
|
|
52
|
+
const plain = decryptRandomIVBuffer(enc, encKey)
|
|
53
|
+
plainFilename = filename.slice(0, filename.length - '.enc'.length)
|
|
54
|
+
fs.writeFileSync(plainFilename, plain)
|
|
55
|
+
}
|
|
29
56
|
|
|
30
57
|
if (del) {
|
|
31
58
|
fs.unlinkSync(filename)
|
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
import * as path from 'path'
|
|
2
|
+
import { _assert } from '@naturalcycles/js-lib'
|
|
2
3
|
import * as fs from 'fs-extra'
|
|
3
4
|
import globby = require('globby')
|
|
4
5
|
import { dimGrey, yellow } from '../colors'
|
|
5
|
-
import { encryptRandomIVBuffer } from '../security/crypto.util'
|
|
6
|
+
import { encryptObject, encryptRandomIVBuffer } from '../security/crypto.util'
|
|
6
7
|
|
|
7
8
|
export interface EncryptCLIOptions {
|
|
8
9
|
pattern: string[]
|
|
10
|
+
file?: string
|
|
9
11
|
encKey: string
|
|
10
|
-
algorithm?: string
|
|
11
12
|
del?: boolean
|
|
13
|
+
jsonMode?: boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
|
|
16
18
|
* Using provided encKey.
|
|
17
19
|
*/
|
|
18
|
-
export function secretsEncrypt(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
export function secretsEncrypt(
|
|
21
|
+
pattern: string[],
|
|
22
|
+
file: string | undefined,
|
|
23
|
+
encKey: string,
|
|
24
|
+
del = false,
|
|
25
|
+
jsonMode = false,
|
|
26
|
+
): void {
|
|
27
|
+
const patterns = file
|
|
28
|
+
? [file]
|
|
29
|
+
: [
|
|
30
|
+
...pattern,
|
|
31
|
+
`!**/*.enc`, // excluding already encoded
|
|
32
|
+
]
|
|
23
33
|
const filenames = globby.sync(patterns)
|
|
34
|
+
let encFilename
|
|
24
35
|
|
|
25
36
|
filenames.forEach(filename => {
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
if (jsonMode) {
|
|
38
|
+
_assert(
|
|
39
|
+
filename.endsWith('.plain.json'),
|
|
40
|
+
`${path.basename(filename)} MUST end with '.plain.json'`,
|
|
41
|
+
)
|
|
42
|
+
encFilename = filename.replace('.plain', '')
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
const json = encryptObject(JSON.parse(fs.readFileSync(filename, 'utf8')), encKey)
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(encFilename, JSON.stringify(json, null, 2))
|
|
47
|
+
} else {
|
|
48
|
+
const plain = fs.readFileSync(filename)
|
|
49
|
+
const enc = encryptRandomIVBuffer(plain, encKey)
|
|
50
|
+
encFilename = `${filename}.enc`
|
|
51
|
+
fs.writeFileSync(encFilename, enc)
|
|
52
|
+
}
|
|
31
53
|
|
|
32
54
|
if (del) {
|
|
33
55
|
fs.unlinkSync(filename)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as crypto from 'crypto'
|
|
2
|
+
import { _stringMapEntries, StringMap } from '@naturalcycles/js-lib'
|
|
2
3
|
import { md5 } from './hash.util'
|
|
3
4
|
|
|
4
5
|
const algorithm = 'aes-256-cbc'
|
|
@@ -7,12 +8,11 @@ const algorithm = 'aes-256-cbc'
|
|
|
7
8
|
* Using aes-256-cbc
|
|
8
9
|
*/
|
|
9
10
|
export function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer {
|
|
10
|
-
|
|
11
|
+
// md5 to match aes-256 key length of 32 bytes
|
|
12
|
+
const key = md5(Buffer.from(secretKeyBase64, 'base64'))
|
|
11
13
|
|
|
12
14
|
// Random iv to achieve non-deterministic encryption (but deterministic decryption)
|
|
13
|
-
|
|
14
|
-
const iv = crypto.randomBytes(16) // use sync method here for speed
|
|
15
|
-
|
|
15
|
+
const iv = crypto.randomBytes(16)
|
|
16
16
|
const cipher = crypto.createCipheriv(algorithm, key, iv)
|
|
17
17
|
|
|
18
18
|
return Buffer.concat([iv, cipher.update(input), cipher.final()])
|
|
@@ -22,7 +22,8 @@ export function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): B
|
|
|
22
22
|
* Using aes-256-cbc
|
|
23
23
|
*/
|
|
24
24
|
export function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): Buffer {
|
|
25
|
-
|
|
25
|
+
// md5 to match aes-256 key length of 32 bytes
|
|
26
|
+
const key = md5(Buffer.from(secretKeyBase64, 'base64'))
|
|
26
27
|
|
|
27
28
|
// iv is first 16 bytes of encrypted buffer, the rest is payload
|
|
28
29
|
const iv = input.slice(0, 16)
|
|
@@ -33,33 +34,52 @@ export function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: string): B
|
|
|
33
34
|
return Buffer.concat([decipher.update(payload), decipher.final()])
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Decrypts all object values.
|
|
39
|
+
* Returns object with decrypted values.
|
|
40
|
+
*/
|
|
41
|
+
export function decryptObject(obj: StringMap, secretKey: string): StringMap {
|
|
42
|
+
const { key, iv } = getCryptoParams(secretKey)
|
|
43
|
+
|
|
44
|
+
const r: StringMap = {}
|
|
45
|
+
_stringMapEntries(obj).forEach(([k, v]) => {
|
|
46
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv)
|
|
47
|
+
r[k] = decipher.update(v, 'base64', 'utf8') + decipher.final('utf8')
|
|
48
|
+
})
|
|
49
|
+
return r
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function encryptObject(obj: StringMap, secretKey: string): StringMap {
|
|
53
|
+
const { key, iv } = getCryptoParams(secretKey)
|
|
54
|
+
|
|
55
|
+
const r: StringMap = {}
|
|
56
|
+
_stringMapEntries(obj).forEach(([k, v]) => {
|
|
57
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv)
|
|
58
|
+
r[k] = cipher.update(v, 'utf8', 'base64') + cipher.final('base64')
|
|
59
|
+
})
|
|
60
|
+
return r
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
/**
|
|
37
64
|
* Using aes-256-cbc
|
|
38
65
|
*/
|
|
39
66
|
export function decryptString(str: string, secretKey: string): string {
|
|
40
|
-
const {
|
|
67
|
+
const { key, iv } = getCryptoParams(secretKey)
|
|
41
68
|
const decipher = crypto.createDecipheriv(algorithm, key, iv)
|
|
42
|
-
|
|
43
|
-
return (decrypted += decipher.final('utf8'))
|
|
69
|
+
return decipher.update(str, 'base64', 'utf8') + decipher.final('utf8')
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
/**
|
|
47
73
|
* Using aes-256-cbc
|
|
48
74
|
*/
|
|
49
75
|
export function encryptString(str: string, secretKey: string): string {
|
|
50
|
-
const {
|
|
76
|
+
const { key, iv } = getCryptoParams(secretKey)
|
|
51
77
|
const cipher = crypto.createCipheriv(algorithm, key, iv)
|
|
52
|
-
|
|
53
|
-
return (encrypted += cipher.final('base64'))
|
|
78
|
+
return cipher.update(str, 'utf8', 'base64') + cipher.final('base64')
|
|
54
79
|
}
|
|
55
80
|
|
|
56
|
-
function getCryptoParams(secretKey: string): {
|
|
81
|
+
function getCryptoParams(secretKey: string): { key: string; iv: string } {
|
|
57
82
|
const key = md5(secretKey)
|
|
58
83
|
const iv = md5(secretKey + key).slice(0, 16)
|
|
59
|
-
return {
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function aes256Key(secretKeyBase64: string): string {
|
|
63
|
-
// md5 to match aes-256 key length of 32 bytes
|
|
64
|
-
return md5(Buffer.from(secretKeyBase64, 'base64'))
|
|
84
|
+
return { key, iv }
|
|
65
85
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs'
|
|
2
|
-
import { _assert,
|
|
2
|
+
import { _assert, StringMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { base64ToString } from '..'
|
|
4
|
-
import {
|
|
4
|
+
import { decryptObject, decryptRandomIVBuffer } from './crypto.util'
|
|
5
5
|
|
|
6
6
|
let loaded = false
|
|
7
7
|
|
|
@@ -93,12 +93,10 @@ export function loadSecretsFromEncryptedJsonFileValues(
|
|
|
93
93
|
`loadSecretsFromEncryptedJsonFileValues() cannot load from path: ${filePath}`,
|
|
94
94
|
)
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
let secrets: StringMap = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
97
97
|
|
|
98
98
|
if (secretEncryptionKey) {
|
|
99
|
-
|
|
100
|
-
secrets[k] = decryptString(enc, secretEncryptionKey)
|
|
101
|
-
})
|
|
99
|
+
secrets = decryptObject(secrets, secretEncryptionKey)
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
|
package/src/util/lruMemoCache.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { MemoCache } from '@naturalcycles/js-lib'
|
|
|
2
2
|
import LRUCache = require('lru-cache')
|
|
3
3
|
|
|
4
4
|
// Partial, to be able to provide default `max`
|
|
5
|
-
export
|
|
5
|
+
export type LRUMemoCacheOptions<KEY, VALUE> = Partial<LRUCache.Options<KEY, VALUE>>
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @example
|