UNPKG

@meeco/cryppo

Version:

In-browser encryption and decryption. Clone of Ruby Cryppo

242 lines (241 loc) 10.5 kB
"use strict"; 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;