Socket
Socket
Sign inDemoInstall

yaml-crypt

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

yaml-crypt - npm Package Compare versions

Comparing version 0.0.1 to 0.1.0

lib/yaml-crypt-helper.js

3

.eslintrc.json

@@ -16,4 +16,5 @@ {

"no-tabs": "error",
"no-console": "off"
"no-console": "off",
"no-constant-condition": "off"
}
}

@@ -5,4 +5,7 @@ #!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const process = require('process');
const childProcess = require('child_process');
const tmp = require('tmp');
const argparse = require('argparse');

@@ -12,2 +15,3 @@ const yaml = require('js-yaml');

const yamlcrypt = require('../lib/yaml-crypt');
const yamlcryptHelper = require('../lib/yaml-crypt-helper');

@@ -117,4 +121,12 @@ require('pkginfo')(module);

metavar: '<key-file>',
help: 'Key files to use. Can be given multiple times for decryption to automatically select a matching key'
help: 'Key files to use. Can be given multiple times to automatically select a matching decryption key'
});
parser.addArgument(['-E', '--edit'], {
action: 'storeTrue',
help: 'Open an editor for the given files, transparently decrypting and encrypting the file content'
});
parser.addArgument(['-B', '--base64'], {
action: 'storeTrue',
help: 'Encode/decode values using Base64 encoding before processing'
});
parser.addArgument(['--path'], {

@@ -148,3 +160,3 @@ metavar: '<yaml-path>',

}
if (args.raw && args.file) {
if (args.raw && args.file.length) {
throw new UsageError('no files may be given when --raw is used!');

@@ -155,37 +167,7 @@ }

}
if (args.rm && !args.file) {
if (args.rm && !args.file.length) {
throw new UsageError('option --rm used, but no files given!');
}
try {
const keys = [];
if (args.key) {
keys.push(...args.key.map(key => readKey(key)));
}
if (config.defaultKeyFile) {
keys.push(readKey(config.defaultKeyFile));
}
if (args.file) {
for (const file of args.file) {
const stat = fs.statSync(file);
if (stat.isDirectory()) {
if (args.dir) {
fs.readdirSync(file)
.filter(f => {
if (args.encrypt) {
return plaintextFile(f);
} else if (args.decrypt) {
return encryptedFile(f);
} else {
return plaintextFile(f) || encryptedFile(f);
}
})
.forEach(f => processFile(file + '/' + f, keys, args, options));
} else {
throw new UsageError(`directories will be skipped unless --dir given: ${file}`);
}
} else {
processFile(file, keys, args, options);
}
}
}
_run(args, config, options);
} catch (e) {

@@ -200,2 +182,84 @@ if (args.debug) {

function _run(args, config, options) {
const keys = [];
if (args.key) {
keys.push(...args.key.map(key => readKey(key)));
} else if (config.defaultKeyFile) {
keys.push(readKey(config.defaultKeyFile));
}
if (args.edit) {
for (const file of args.file) {
editFile(file, keys, args);
}
} else if (args.file.length) {
for (const file of args.file) {
processFileArg(file, keys, args);
}
} else {
let encrypt;
if (args.encrypt) {
encrypt = true;
} else if (args.decrypt) {
encrypt = false;
} else {
throw new UsageError('no input files, but no operation (--encrypt/--decrypt) given!');
}
let input;
if (options.stdin) {
input = options.stdin;
} else {
input = readStdin();
}
let output;
if (options.stdout) {
output = options.stdout;
} else {
output = process.stdout;
}
if (args.raw) {
if (encrypt) {
const crypt = yamlcrypt.encrypt(keys[0], { 'base64': args.base64 });
output.write(crypt.encryptRaw(input));
output.write('\n');
} else {
const crypt = yamlcrypt.decrypt(keys[0], { 'base64': args.base64 });
let result = crypt.decryptRaw(input);
output.write(result);
}
} else {
const strs = [];
if (encrypt) {
const crypt = yamlcrypt.encrypt(keys[0], { 'base64': args.base64 });
yaml.safeLoadAll(input, obj => {
yamlcryptHelper.processStrings(obj, args.path, str => new yamlcrypt.Plaintext(str));
const encrypted = crypt.safeDump(obj);
strs.push(encrypted);
});
} else {
const crypt = yamlcrypt.decrypt(keys[0], { 'base64': args.base64 });
crypt.safeLoadAll(input, obj => strs.push(yaml.safeDump(obj)));
}
for (let idx = 0; idx < strs.length; idx++) {
if (idx > 0) {
output.write('---\n');
}
output.write(strs[idx]);
}
}
}
}
function readStdin() {
var buf = Buffer.alloc(1024);
let str = '';
while (true) {
var len = fs.readSync(process.stdin.fd, buf, 0, buf.length);
if (!len) {
break;
}
str += buf.toString('utf8', 0, len);
}
return str;
}
function readKey(keyFile) {

@@ -223,2 +287,25 @@ let raw;

function processFileArg(file, keys, args) {
const stat = fs.statSync(file);
if (stat.isDirectory()) {
if (args.dir) {
fs.readdirSync(file)
.filter(f => {
if (args.encrypt) {
return plaintextFile(f);
} else if (args.decrypt) {
return encryptedFile(f);
} else {
return plaintextFile(f) || encryptedFile(f);
}
})
.forEach(f => processFile(file + '/' + f, keys, args));
} else {
throw new UsageError(`directories will be skipped unless --dir given: ${file}`);
}
} else {
processFile(file, keys, args);
}
}
function processFile(file, keys, args) {

@@ -255,7 +342,10 @@ let encrypt;

}
const strs = [];
let strs = [];
if (encrypt) {
const crypt = yamlcrypt.encrypt(keys[0]);
if (keys.length > 1) {
console.warn('warning: multiple keys given, using first key for encryption!');
}
const crypt = yamlcrypt.encrypt(keys[0], { 'base64': args.base64 });
yaml.safeLoadAll(content, obj => {
processStrings(obj, args.path, str => yamlcrypt.plaintext(str));
yamlcryptHelper.processStrings(obj, args.path, str => new yamlcrypt.Plaintext(str));
const encrypted = crypt.safeDump(obj);

@@ -265,4 +355,17 @@ strs.push(encrypted);

} else {
const crypt = yamlcrypt.decrypt(keys);
crypt.safeLoadAll(content, obj => strs.push(yaml.safeDump(obj)));
let success = false;
for (const key of keys) {
try {
strs = [];
const crypt = yamlcrypt.decrypt(key, { 'base64': args.base64 });
crypt.safeLoadAll(content, obj => strs.push(yaml.safeDump(obj)));
success = true;
break;
} catch (e) {
continue;
}
}
if (!success) {
throw new Error('No matching key to decrypt the given data!');
}
}

@@ -275,26 +378,30 @@ writeYaml(strs, output);

function processStrings(obj, path, callback) {
let subobj = obj;
if (path) {
const parts = path.split('.');
for (let idx = 0; idx < parts.length; idx++) {
const part = parts[idx];
if (idx === parts.length - 1 && typeof subobj[part] === 'string') {
subobj[part] = callback(subobj[part]);
return;
} else {
subobj = subobj[part];
}
function editFile(file, keys, args) {
let content;
try {
content = fs.readFileSync(file);
} catch (e) {
if (e.code === 'ENOENT') {
throw new UsageError(`file does not exist: ${file}`);
} else {
throw e;
}
}
for (const key in subobj) {
if (subobj.hasOwnProperty(key)) {
const value = subobj[key];
if (typeof value === 'string') {
subobj[key] = callback(value);
} else if (typeof value === 'object') {
processStrings(value, null, callback);
}
}
}
const dir = path.dirname(path.resolve(file));
const editor = process.env['EDITOR'] || 'vim';
const opts = { 'base64': args.base64 };
const transformed = yamlcryptHelper.transform(content, keys, opts, str => {
const tmpFile = tmp.fileSync({ 'dir': dir, 'postfix': '.yaml' });
fs.writeSync(tmpFile.fd, str);
fs.closeSync(tmpFile.fd);
childProcess.spawnSync(editor, [tmpFile.name], { 'stdio': 'inherit' });
return fs.readFileSync(tmpFile.name);
});
fs.writeFileSync(file, transformed);
}

@@ -301,0 +408,0 @@

@@ -5,17 +5,25 @@ const yaml = require('js-yaml');

const ALGORITHM_FERNET = 'fernet:0x80';
const DEFAULT_ALGORITHM = ALGORITHM_FERNET;
/**
* Supported algorithms
*/
const algorithms = ['fernet:128'];
const algorithms = [ALGORITHM_FERNET];
/**
* Plain text, unencrypted, using the Fernet cryptography scheme
* Plain text, unencrypted
*/
class PlaintextFernet {
constructor(msg) {
this.msg = msg;
class Plaintext {
constructor(plaintext, ciphertext = null, algorithm = DEFAULT_ALGORITHM) {
if (algorithm !== ALGORITHM_FERNET) {
throw new Error(`unsupported algorithm: ${algorithm}`);
}
this.plaintext = plaintext;
this.ciphertext = ciphertext;
this.algorithm = algorithm;
}
toString() {
return this.msg;
return this.plaintext;
}

@@ -25,12 +33,16 @@ }

/**
* Creates a plaintext object that can be encrypted
* @param {string} str The string value of the object
* @param {string} algorithm The algorithm to use
* Cipher text, encrypted
*/
function plaintext(str, algorithm = 'fernet:128') {
if (algorithm === 'fernet:128') {
return new PlaintextFernet(str);
} else {
throw new Error(`unsupported algorithm: ${algorithm}`);
class Ciphertext {
constructor(ciphertext, algorithm = DEFAULT_ALGORITHM) {
if (algorithm !== ALGORITHM_FERNET) {
throw new Error(`unsupported algorithm: ${algorithm}`);
}
this.ciphertext = ciphertext;
this.algorithm = algorithm;
}
toString() {
return this.ciphertext;
}
}

@@ -41,19 +53,40 @@

* @param {string} key The encryption key
* @param {object} opts Encryption options
*/
function encrypt(key) {
return new _Encryption(key);
function encrypt(key, opts = {}) {
return new _Encryption(key, opts);
}
class _Encryption {
constructor(key) {
this.key = key;
constructor(key, opts) {
opts = opts || {};
const algorithm = opts.algorithm || DEFAULT_ALGORITHM;
const objects = opts.objects;
const base64 = opts.base64;
this.type = _type(key, algorithm, objects, base64);
this.schema = yaml.Schema.create([this.type]);
}
encryptRaw(str) {
return this.type.represent(str);
}
safeDump(obj, opts) {
const type = _fernetType([this.key]);
const schema = yaml.Schema.create([type]);
opts = opts || {};
opts.schema = schema;
opts.schema = this.schema;
return yaml.safeDump(obj, opts);
}
safeDumpAll(objs, opts) {
opts = opts || {};
opts.schema = this.schema;
let str = '';
for (let idx = 0; idx < objs.length; idx++) {
if (idx > 0) {
str += '---\n';
}
str += yaml.safeDump(objs[idx], opts);
}
return str;
}
}

@@ -63,18 +96,26 @@

* Creates an object for decryption
* @param {string[]} keys The decryption keys
* @param {string} key The decryption key
* @param {object} opts Decryption options
*/
function decrypt(keys) {
return new _Decryption(keys);
function decrypt(key, opts = {}) {
return new _Decryption(key, opts);
}
class _Decryption {
constructor(keys) {
this.keys = keys;
constructor(key, opts) {
opts = opts || {};
const algorithm = opts.algorithm || DEFAULT_ALGORITHM;
const objects = opts.objects;
const base64 = opts.base64;
this.type = _type(key, algorithm, objects, base64);
this.schema = yaml.Schema.create([this.type]);
}
decryptRaw(str) {
return this.type.construct(str);
}
safeLoad(str, opts) {
const type = _fernetType(this.keys);
const schema = yaml.Schema.create([type]);
opts = opts || {};
opts.schema = schema;
opts.schema = this.schema;
return yaml.safeLoad(str, opts);

@@ -84,6 +125,4 @@ }

safeLoadAll(str, iterator, opts) {
const type = _fernetType(this.keys);
const schema = yaml.Schema.create([type]);
opts = opts || {};
opts.schema = schema;
opts.schema = this.schema;
return yaml.safeLoadAll(str, iterator, opts);

@@ -93,27 +132,38 @@ }

function _fernetType(keys) {
return new yaml.Type('!yaml-crypt/fernet:128', {
function _type(key, algorithm, objects, base64) {
if (algorithm === ALGORITHM_FERNET) {
return _fernetType(key, objects, base64);
} else {
throw new Error(`unsupported algorithm: ${algorithm}`);
}
}
function _fernetType(key, objects, base64) {
return new yaml.Type('!yaml-crypt/' + ALGORITHM_FERNET, {
kind: 'scalar',
instanceOf: PlaintextFernet,
instanceOf: Plaintext,
resolve: (data) => data !== null,
construct: (data) => _tryDecrypt(keys, data, crypto.fernetDecrypt),
represent: (data) => crypto.fernetEncrypt(keys[0], data.toString())
construct: data => {
const decrypted = crypto.fernetDecrypt(key, data);
const decoded = (base64 ? new Buffer(decrypted, 'base64').toString() : decrypted);
return (objects ? new Plaintext(decoded, data) : decoded);
},
represent: data => {
let encrypted;
if (data.ciphertext) {
encrypted = data.ciphertext;
} else {
const str = data.toString();
const encoded = (base64 ? new Buffer(str).toString('base64') : str);
encrypted = crypto.fernetEncrypt(key, encoded);
}
return (objects ? new Ciphertext(encrypted) : encrypted);
}
});
}
function _tryDecrypt(keys, data, worker) {
for (const key of keys) {
try {
return worker(key, data);
} catch (e) {
continue;
}
}
throw new Error('No matching key to decrypt the given data!');
}
module.exports.algorithms = algorithms;
module.exports.plaintext = plaintext;
module.exports.PlaintextFernet = PlaintextFernet;
module.exports.Plaintext = Plaintext;
module.exports.Ciphertext = Ciphertext;
module.exports.encrypt = encrypt;
module.exports.decrypt = decrypt;
{
"name": "yaml-crypt",
"version": "0.0.1",
"version": "0.1.0",
"description": "Encrypt and decrypt YAML documents",

@@ -21,4 +21,4 @@ "license": "MIT",

"pkginfo": "^0.4.1",
"urlsafe-base64": "^1.0.0",
"write-file-atomic": "^2.3.0"
"tmp": "^0.0.33",
"urlsafe-base64": "^1.0.0"
},

@@ -32,5 +32,4 @@ "scripts": {

"eslint": "^4.10.0",
"mocha": "^4.0.1",
"tmp": "^0.0.33"
"mocha": "^4.0.1"
}
}

@@ -51,2 +51,7 @@ # yaml-crypt

It is also possible to directly open encrypted files in an editor, decrypting them
before opening and encrypting again when saving:
$ yaml-crypt -E my-file.yaml-crypt
## Configuration

@@ -53,0 +58,0 @@

@@ -11,11 +11,13 @@ const mocha = require('mocha');

describe('crypto', () => it('fernetEncrypt() should return the encrypted content', () => {
const result = crypto.fernetEncrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H', 'Hello, world!');
expect(result).to.equal('gAAAAAAAAAABAAECAwQFBgcICQoLDA0OD7nQ_JQsjDx78n7mQ9bW3T-rgiTN7WX3Uq66EDA0qxZDNQppXL6WaOAIW4x8ElmcRg==');
}));
describe('crypto', () => {
it('should return the encrypted content', () => {
const result = crypto.fernetEncrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H', 'Hello, world!');
expect(result).to.equal('gAAAAAAAAAABAAECAwQFBgcICQoLDA0OD7nQ_JQsjDx78n7mQ9bW3T-rgiTN7WX3Uq66EDA0qxZDNQppXL6WaOAIW4x8ElmcRg==');
});
describe('crypto', () => it('fernetDecrypt() should return the decrypted content', () => {
const key = 'aehae5Ui0Eechaeghau9Yoh9jufiep7H';
const encrypted = 'gAAAAAAAAAABAAECAwQFBgcICQoLDA0OD7nQ_JQsjDx78n7mQ9bW3T-rgiTN7WX3Uq66EDA0qxZDNQppXL6WaOAIW4x8ElmcRg==';
expect(crypto.fernetDecrypt(key, encrypted)).to.equal('Hello, world!');
}));
it('should return the decrypted content', () => {
const key = 'aehae5Ui0Eechaeghau9Yoh9jufiep7H';
const encrypted = 'gAAAAAAAAAABAAECAwQFBgcICQoLDA0OD7nQ_JQsjDx78n7mQ9bW3T-rgiTN7WX3Uq66EDA0qxZDNQppXL6WaOAIW4x8ElmcRg==';
expect(crypto.fernetDecrypt(key, encrypted)).to.equal('Hello, world!');
});
});

@@ -17,29 +17,57 @@ const fs = require('fs');

describe('yaml-crypt-cli', () => it('should throw an error when using --path and --raw', () => {
const out = new stream.Writable();
expect(() => yamlcryptcli.run(['--path', 'x', '--raw'], {}, { 'stdout': out })).to.throw();
}));
class Out {
constructor() {
this.str = '';
}
describe('yaml-crypt-cli', () => it('should encrypt the given YAML file', () => {
const keyFile = tmp.fileSync();
fs.writeSync(keyFile.fd, 'aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const input = tmp.fileSync({ 'postfix': '.yaml' });
fs.writeSync(input.fd, yaml.safeDump({ 'first': 'Hello, world!', 'second': 'Hello!' }));
const out = new stream.Writable();
yamlcryptcli.run(['-k', keyFile.name, input.name], {}, { 'stdout': out });
const output = fs.readFileSync(input.name + '-crypt');
const expected = fs.readFileSync('./test/test-2.yaml-crypt');
expect(output.toString('utf8')).to.equal(expected.toString('utf8'));
}));
write(obj) {
if (typeof obj === 'string') {
this.str += obj;
} else {
this.str += obj.toString();
}
}
}
describe('yaml-crypt-cli', () => it('should encrypt only some parts of the given YAML file', () => {
const keyFile = tmp.fileSync();
fs.writeSync(keyFile.fd, 'aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const input = tmp.fileSync({ 'postfix': '.yaml' });
fs.writeSync(input.fd, yaml.safeDump({ 'a': { 'b': { 'c': 'secret' } }, 'x': 'plain' }));
const out = new stream.Writable();
yamlcryptcli.run(['-k', keyFile.name, '--path', 'a.b.c', input.name], {}, { 'stdout': out });
const output = fs.readFileSync(input.name + '-crypt');
const expected = fs.readFileSync('./test/test-3.yaml-crypt');
expect(output.toString('utf8')).to.equal(expected.toString('utf8'));
}));
describe('yaml-crypt-cli', () => {
it('should throw an error when using --path and --raw', () => {
const out = new stream.Writable();
expect(() => yamlcryptcli.run(['--path', 'x', '--raw'], {}, { 'stdout': out })).to.throw();
});
it('should encrypt the given YAML file', () => {
const keyFile = tmp.fileSync();
fs.writeSync(keyFile.fd, 'aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const input = tmp.fileSync({ 'postfix': '.yaml' });
fs.writeSync(input.fd, yaml.safeDump({ 'first': 'Hello, world!', 'second': 'Hello!' }));
const out = new stream.Writable();
yamlcryptcli.run(['-k', keyFile.name, input.name], {}, { 'stdout': out });
const output = fs.readFileSync(input.name + '-crypt');
const expected = fs.readFileSync('./test/test-2.yaml-crypt');
expect(output.toString('utf8')).to.equal(expected.toString('utf8'));
});
it('should encrypt only parts of the YAML file when using --path', () => {
const keyFile = tmp.fileSync();
fs.writeSync(keyFile.fd, 'aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const input = tmp.fileSync({ 'postfix': '.yaml' });
fs.writeSync(input.fd, yaml.safeDump({ 'a': { 'b': { 'c': 'secret' } }, 'x': 'plain' }));
const out = new stream.Writable();
yamlcryptcli.run(['-k', keyFile.name, '--path', 'a.b.c', input.name], {}, { 'stdout': out });
const output = fs.readFileSync(input.name + '-crypt');
const expected = fs.readFileSync('./test/test-3.yaml-crypt');
expect(output.toString('utf8')).to.equal(expected.toString('utf8'));
});
it('should encrypt the whole input when using --raw', () => {
const keyFile = tmp.fileSync();
fs.writeSync(keyFile.fd, 'aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const options = {
'stdin': 'Hello, world!',
'stdout': new Out()
};
yamlcryptcli.run(['-e', '-k', keyFile.name, '--raw'], {}, options);
const expected = 'gAAAAAAAAAABAAECAwQFBgcICQoLDA0OD7nQ_JQsjDx78n7mQ9bW3T-rgiTN7WX3Uq66EDA0qxZDNQppXL6WaOAIW4x8ElmcRg==\n';
expect(options.stdout.str).to.equal(expected);
});
});

@@ -13,14 +13,30 @@ const fs = require('fs');

describe('yaml-crypt', () => it('decrypt() should read the decrypted content', () => {
const yaml = yamlcrypt.decrypt(['aehae5Ui0Eechaeghau9Yoh9jufiep7H']);
const content = fs.readFileSync('./test/test-1.yaml-crypt');
const result = yaml.safeLoad(content);
expect(result.key1.toString()).to.equal('Hello, world!');
}));
describe('yaml-crypt', () => {
it('should read the decrypted content', () => {
const yaml = yamlcrypt.decrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const content = fs.readFileSync('./test/test-1.yaml-crypt');
const result = yaml.safeLoad(content);
expect(result.key1.toString()).to.equal('Hello, world!');
});
describe('yaml-crypt', () => it('encrypt() should return the encrypted content', () => {
const yaml = yamlcrypt.encrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const result = yaml.safeDump({ 'key1': new yamlcrypt.PlaintextFernet('Hello, world!') });
const expected = fs.readFileSync('./test/test-1.yaml-crypt').toString('utf8');
expect(result).to.equal(expected);
}));
it('should return the encrypted content', () => {
const yaml = yamlcrypt.encrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H');
const result = yaml.safeDump({ 'key1': new yamlcrypt.Plaintext('Hello, world!') });
const expected = fs.readFileSync('./test/test-1.yaml-crypt').toString('utf8');
expect(result).to.equal(expected);
});
it('should return the base64 encrypted content', () => {
const yaml = yamlcrypt.encrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H', { 'base64': true });
const result = yaml.safeDump({ 'base64': new yamlcrypt.Plaintext('Hello, world!') });
const expected = fs.readFileSync('./test/test-4.yaml-crypt').toString('utf8');
expect(result).to.equal(expected);
});
it('should read the decrypted base64 content', () => {
const yaml = yamlcrypt.decrypt('aehae5Ui0Eechaeghau9Yoh9jufiep7H', { 'base64': true });
const content = fs.readFileSync('./test/test-4.yaml-crypt');
const result = yaml.safeLoad(content);
expect(result.base64.toString()).to.equal('Hello, world!');
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc