@meeco/cryppo
Version:
In-browser encryption and decryption. Clone of Ruby Cryppo
242 lines (241 loc) • 10.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.keyLengthFromPrivateKeyPem = exports.keyLengthFromPublicKeyPem = exports.generateEncryptionVerificationArtifacts = exports.decodeDerivationArtifacts = exports.encodeDerivationArtifacts = exports.decodeSafe64Bson = exports.encodeSafe64Bson = exports.decodeSafe64 = exports.encodeSafe64 = exports.deSerialize = exports.serialize = exports.deSerializeDerivedKeyOptions = exports.serializeDerivedKeyOptions = exports.generateRandomBytesString = exports.bytesBufferToBinaryString = exports.binaryStringToBytesBuffer = exports.bytesToUtf8 = exports.bytesToUtf16 = exports.bytesToBinaryString = exports.binaryStringToBytes = exports.utf16ToBytes = exports.utf8ToBytes = exports.encodeUtf8 = exports.decode64 = exports.encode64 = void 0;
var BSON = __importStar(require("bson"));
var buffer_1 = require("buffer");
var node_forge_1 = require("node-forge");
var YAML = __importStar(require("yaml"));
var serialization_versions_1 = require("./serialization-versions");
// Adds support for binary types
YAML.defaultOptions.schema = 'yaml-1.1';
// 65 is the version byte for encryption artefacts encoded with BSON
var ENCRYPTION_ARTEFACTS_CURRENT_VERSION = 'A';
// 75 is the version byte for derivation artefacts encoded with BSON
var DERIVATION_ARTEFACTS_CURRENT_VERSION = 'K';
/**
* Wrapping some node-forge utils in case we ever need to replace it
*/
exports.encode64 = node_forge_1.util.encode64;
exports.decode64 = node_forge_1.util.decode64;
exports.encodeUtf8 = node_forge_1.util.encodeUtf8;
exports.utf8ToBytes = node_forge_1.util.text.utf8.encode;
exports.utf16ToBytes = node_forge_1.util.text.utf16.encode;
exports.binaryStringToBytes = node_forge_1.util.binary.raw.decode;
exports.bytesToBinaryString = function (bytes) {
var binary = '';
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
};
exports.bytesToUtf16 = function (bytes) {
var binary = '';
var utf16Bytes = new Uint16Array(bytes.buffer);
var len = utf16Bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(utf16Bytes[i]);
}
return binary;
};
exports.bytesToUtf8 = function (bytes) {
var binary = '';
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return node_forge_1.util.decodeUtf8(binary);
};
exports.binaryStringToBytesBuffer = function (value) {
return buffer_1.Buffer.from(node_forge_1.util.binary.raw.decode(value));
};
exports.bytesBufferToBinaryString = function (val) {
return node_forge_1.util.createBuffer(val).data;
};
exports.generateRandomBytesString = function (length) {
if (length === void 0) { length = 32; }
return node_forge_1.random.getBytesSync(length);
};
function serializeDerivedKeyOptions(strategy, artifacts, serializationFormat) {
if (serializationFormat === void 0) { serializationFormat = serialization_versions_1.SerializationFormat.latest_version; }
switch (serializationFormat) {
case serialization_versions_1.SerializationFormat.legacy: {
var yaml = encodeYaml(artifacts);
return strategy + "." + encodeSafe64(yaml);
}
default: {
return strategy + "." + encodeSafe64Bson(DERIVATION_ARTEFACTS_CURRENT_VERSION, artifacts);
}
}
}
exports.serializeDerivedKeyOptions = serializeDerivedKeyOptions;
function deSerializeDerivedKeyOptions(serialized) {
var items = serialized.split('.');
// We might get passed an entire encrypted string in which case we just want the key and strategy
if (items.length > 2) {
items = items.slice(-2);
}
var derivationStrategy = items[0], artifacts = items[1];
var serializationArtifacts = decodeArtifactData(artifacts);
return {
derivationStrategy: derivationStrategy,
serializationArtifacts: serializationArtifacts,
};
}
exports.deSerializeDerivedKeyOptions = deSerializeDerivedKeyOptions;
function serialize(strategy, data, artifacts, serializationFormat) {
if (serializationFormat === void 0) { serializationFormat = serialization_versions_1.SerializationFormat.latest_version; }
switch (serializationFormat) {
case serialization_versions_1.SerializationFormat.legacy: {
var yaml = encodeYaml(artifacts);
return strategy + "." + encodeSafe64(data) + "." + encodeSafe64(yaml);
}
default: {
return strategy + "." + encodeSafe64(data) + "." + encodeSafe64Bson(ENCRYPTION_ARTEFACTS_CURRENT_VERSION, artifacts);
}
}
}
exports.serialize = serialize;
function encodeYaml(data) {
// Note the pad and binary replacements are only for backwards compatibility
// with Ruby Cryppo. They technically should not be required and there should
// be a flag to disable them.
var pad = "---\n";
return pad + YAML.stringify(data).replace(/!!binary/g, '!binary');
}
function deSerialize(serialized) {
var items = serialized.split('.');
if (items.length < 2) {
throw new Error('String is not a serialized encrypted string');
}
if (items.length % 2 !== 1) {
throw new Error('Serialized string should have an encryption strategy and pairs of encoded data and artifacts');
}
var encryptionStrategy = items[0];
var decodedPairs = items.slice(1).map(function (item, i) {
if (i % 2 === 0) {
// Base64 encoded encrypted data
return decodeSafe64(item);
}
else {
return decodeArtifactData(item);
}
});
if (!decodedPairs.length) {
throw new Error('No data found to decrypt in serialized string');
}
return {
encryptionStrategy: encryptionStrategy,
decodedPairs: decodedPairs,
};
}
exports.deSerialize = deSerialize;
// tslint:disable-next-line: max-line-length
function decodeArtifactData(text) {
if (decodeSafe64(text).startsWith('---')) {
text = decodeSafe64(text);
return YAML.parse(text.replace(/ !binary/g, ' !!binary'));
}
else {
text = decodeSafe64Bson(text);
// remove version byte before deserializing
return BSON.deserialize(buffer_1.Buffer.from(text, 'base64').slice(1), { promoteBuffers: true });
}
}
/**
* The Ruby version uses url safe base64 encoding.
* RFC 4648 specifies + is encoded as - and / is _
* with the trailing = removed.
*/
function encodeSafe64(data) {
return exports.encode64(data)
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_'); // Convert '/' to '_'
// Not we don't remove the trailing '=' as specified in the spec
// because ruby's Base64.urlsafe_encode64 does not do this
// and we want to maintain compatibility.
}
exports.encodeSafe64 = encodeSafe64;
function decodeSafe64(base64) {
return exports.decode64(base64
.replace(/-/g, '+') // Convert '+' to '-'
.replace(/_/g, '/'));
// Don't bother concatenating an '=' to the result - see above
}
exports.decodeSafe64 = decodeSafe64;
// tslint:disable-next-line: max-line-length
function encodeSafe64Bson(versionByte, artifacts) {
var bsonSerialized = buffer_1.Buffer.concat([buffer_1.Buffer.from(versionByte), BSON.serialize(artifacts)]);
var base64Data = bsonSerialized.toString('base64');
return base64Data
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_'); // Convert '/' to '_'
// Not we don't remove the trailing '=' as specified in the spec
// because ruby's Base64.urlsafe_encode64 does not do this
// and we want to maintain compatibility.
}
exports.encodeSafe64Bson = encodeSafe64Bson;
function decodeSafe64Bson(base64) {
return base64
.replace(/-/g, '+') // Convert '+' to '-'
.replace(/_/g, '/');
// Don't bother concatenating an '=' to the result - see above
}
exports.decodeSafe64Bson = decodeSafe64Bson;
function encodeDerivationArtifacts(artifacts) {
return encodeSafe64(JSON.stringify(artifacts));
}
exports.encodeDerivationArtifacts = encodeDerivationArtifacts;
function decodeDerivationArtifacts(encoded) {
return JSON.parse(decodeSafe64(encoded));
}
exports.decodeDerivationArtifacts = decodeDerivationArtifacts;
/**
* Returns some base64 encoded random bytes that can be used for encryption verification.
*/
function generateEncryptionVerificationArtifacts() {
var token = node_forge_1.random.getBytesSync(16);
var salt = node_forge_1.random.getBytesSync(16);
return {
token: encodeSafe64(token),
salt: encodeSafe64(salt),
};
}
exports.generateEncryptionVerificationArtifacts = generateEncryptionVerificationArtifacts;
function keyLengthFromPublicKeyPem(publicKeyPem) {
var pk = node_forge_1.pki.publicKeyFromPem(publicKeyPem);
// Undocumented functionality but was the only way I could find to get
// key length out of the public key.
// https://github.com/digitalbazaar/forge/blob/master/lib/rsa.js#L1244
var bitLength = pk.n.bitLength();
return bitLength;
}
exports.keyLengthFromPublicKeyPem = keyLengthFromPublicKeyPem;
function keyLengthFromPrivateKeyPem(privateKey) {
var pk = node_forge_1.pki.privateKeyFromPem(privateKey);
// Undocumented functionality but was the only way I could find to get
// key length out of the public key.
// https://github.com/digitalbazaar/forge/blob/master/lib/rsa.js#L1244
var bitLength = pk.n.bitLength();
return bitLength;
}
exports.keyLengthFromPrivateKeyPem = keyLengthFromPrivateKeyPem;