UNPKG

@meeco/cryppo

Version:

In-browser encryption and decryption. Clone of Ruby Cryppo

225 lines 9.33 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); 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; exports.serializeDerivedKeyOptions = serializeDerivedKeyOptions; exports.deSerializeDerivedKeyOptions = deSerializeDerivedKeyOptions; exports.serialize = serialize; exports.deSerialize = deSerialize; exports.encodeSafe64 = encodeSafe64; exports.decodeSafe64 = decodeSafe64; exports.encodeSafe64Bson = encodeSafe64Bson; exports.decodeSafe64Bson = decodeSafe64Bson; exports.encodeDerivationArtifacts = encodeDerivationArtifacts; exports.decodeDerivationArtifacts = decodeDerivationArtifacts; exports.generateEncryptionVerificationArtifacts = generateEncryptionVerificationArtifacts; exports.keyLengthFromPublicKeyPem = keyLengthFromPublicKeyPem; exports.keyLengthFromPrivateKeyPem = keyLengthFromPrivateKeyPem; const bson_1 = require("bson"); const buffer_1 = require("buffer"); const node_forge_1 = __importDefault(require("node-forge")); const yaml_1 = require("yaml"); const serialization_versions_js_1 = require("./serialization-versions.js"); const { pki, random, util } = node_forge_1.default; // 65 is the version byte for encryption artefacts encoded with BSON const ENCRYPTION_ARTEFACTS_CURRENT_VERSION = 'A'; // 75 is the version byte for derivation artefacts encoded with BSON const DERIVATION_ARTEFACTS_CURRENT_VERSION = 'K'; /** * Wrapping some node-forge utils in case we ever need to replace it */ exports.encode64 = util.encode64; exports.decode64 = util.decode64; exports.encodeUtf8 = util.encodeUtf8; exports.utf8ToBytes = util.text.utf8.encode; exports.utf16ToBytes = util.text.utf16.encode; exports.binaryStringToBytes = util.binary.raw.decode; const bytesToBinaryString = (bytes) => { let binary = ''; const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return binary; }; exports.bytesToBinaryString = bytesToBinaryString; const bytesToUtf16 = (bytes) => { let binary = ''; const utf16Bytes = new Uint16Array(bytes.buffer); const len = utf16Bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(utf16Bytes[i]); } return binary; }; exports.bytesToUtf16 = bytesToUtf16; const bytesToUtf8 = (bytes) => { let binary = ''; const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return util.decodeUtf8(binary); }; exports.bytesToUtf8 = bytesToUtf8; const binaryStringToBytesBuffer = (value) => buffer_1.Buffer.from(util.binary.raw.decode(value)); exports.binaryStringToBytesBuffer = binaryStringToBytesBuffer; const bytesBufferToBinaryString = (val) => // @ts-expect-error node-forge createBuffer accepts Uint8Array at runtime util.createBuffer(val).data; exports.bytesBufferToBinaryString = bytesBufferToBinaryString; const generateRandomBytesString = (length = 32) => random.getBytesSync(length); exports.generateRandomBytesString = generateRandomBytesString; function serializeDerivedKeyOptions(strategy, artifacts, serializationFormat = serialization_versions_js_1.SerializationFormat.latest_version) { switch (serializationFormat) { case serialization_versions_js_1.SerializationFormat.legacy: { const yaml = encodeYaml(artifacts); return `${strategy}.${encodeSafe64(yaml)}`; } default: { return `${strategy}.${encodeSafe64Bson(DERIVATION_ARTEFACTS_CURRENT_VERSION, artifacts)}`; } } } function deSerializeDerivedKeyOptions(serialized) { let 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); } const [derivationStrategy, artifacts] = items; const serializationArtifacts = decodeArtifactData(artifacts); return { derivationStrategy, serializationArtifacts, }; } function serialize(strategy, data, artifacts, serializationFormat = serialization_versions_js_1.SerializationFormat.latest_version) { switch (serializationFormat) { case serialization_versions_js_1.SerializationFormat.legacy: { const yaml = encodeYaml(artifacts); return `${strategy}.${encodeSafe64(data)}.${encodeSafe64(yaml)}`; } default: { return `${strategy}.${encodeSafe64(data)}.${encodeSafe64Bson(ENCRYPTION_ARTEFACTS_CURRENT_VERSION, artifacts)}`; } } } 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. const pad = `---\n`; return pad + (0, yaml_1.stringify)(data, { schema: 'yaml-1.1' }).replace(/!!binary/g, '!binary'); } function deSerialize(serialized) { const 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'); } const [encryptionStrategy] = items; const decodedPairs = items.slice(1).map((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, decodedPairs, }; } function decodeArtifactData(text) { if (decodeSafe64(text).startsWith('---')) { text = decodeSafe64(text); return (0, yaml_1.parse)(text.replace(/ !binary/g, ' !!binary'), { schema: 'yaml-1.1' }); } else { text = decodeSafe64Bson(text); // remove version byte before deserializing return bson_1.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 (0, 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. } function decodeSafe64(base64) { return (0, exports.decode64)(base64 .replace(/-/g, '+') // Convert '+' to '-' .replace(/_/g, '/')); // Don't bother concatenating an '=' to the result - see above } function encodeSafe64Bson(versionByte, artifacts) { const bsonSerialized = buffer_1.Buffer.concat([ buffer_1.Buffer.from(versionByte), buffer_1.Buffer.from(bson_1.BSON.serialize(artifacts)), ]); const 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. } function decodeSafe64Bson(base64) { return base64 .replace(/-/g, '+') // Convert '+' to '-' .replace(/_/g, '/'); // Don't bother concatenating an '=' to the result - see above } function encodeDerivationArtifacts(artifacts) { return encodeSafe64(JSON.stringify(artifacts)); } function decodeDerivationArtifacts(encoded) { return JSON.parse(decodeSafe64(encoded)); } /** * Returns some base64 encoded random bytes that can be used for encryption verification. */ function generateEncryptionVerificationArtifacts() { const token = random.getBytesSync(16); const salt = random.getBytesSync(16); return { token: encodeSafe64(token), salt: encodeSafe64(salt), }; } function keyLengthFromPublicKeyPem(publicKeyPem) { const pk = 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 const bitLength = pk.n.bitLength(); return bitLength; } function keyLengthFromPrivateKeyPem(privateKey) { const pk = 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 const bitLength = pk.n.bitLength(); return bitLength; } //# sourceMappingURL=util.js.map