@meeco/cryppo
Version:
In-browser encryption and decryption. Clone of Ruby Cryppo
225 lines • 9.33 kB
JavaScript
;
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