session-open-group-server-l.../dialects/transport/dialect.loki_proxy.js

969 lines
28 KiB
JavaScript

const fs = require('fs');
const stream = require('stream');
const util = require('util');
const crypto = require('crypto');
const inspect = require('util').inspect;
const querystring = require('querystring');
const _ = require('lodash');
const bb = require('bytebuffer');
const Busboy = require('busboy');
const { Readable } = require('stream');
const libsignal = require('libsignal');
const runMiddleware = require('run-middleware');
const IV_LENGTH = 16;
let configUtil
const FILE_SERVER_PRIV_KEY_FILE = 'proxy.key'
const FILE_SERVER_PUB_KEY_FILE = 'proxy.pub'
const libloki_crypt = require('./lib.loki_crypt');
console.log('dialect.loki_proxy - initializing loki_proxy subsystem');
if (!fs.existsSync(FILE_SERVER_PRIV_KEY_FILE)) {
const serverKey = libsignal.curve.generateKeyPair();
console.log('dialect.loki_proxy - no private key, generating new keyPair, saving as', FILE_SERVER_PRIV_KEY_FILE);
fs.writeFileSync(FILE_SERVER_PRIV_KEY_FILE, serverKey.privKey, 'binary');
if (!fs.existsSync(FILE_SERVER_PUB_KEY_FILE)) {
console.log('dialect.loki_proxy - no public key, saving as', FILE_SERVER_PUB_KEY_FILE);
fs.writeFileSync(FILE_SERVER_PUB_KEY_FILE, serverKey.pubKey, 'binary');
}
}
// should have files by this point
if (!fs.existsSync(FILE_SERVER_PUB_KEY_FILE)) {
console.log('dialect.loki_proxy - Have', FILE_SERVER_PRIV_KEY_FILE, 'without', FILE_SERVER_PUB_KEY_FILE);
// maybe nuke FILE_SERVER_PRIV_KEY_FILE and regen
process.exit(1);
}
// load into buffers
const serverPrivKey = fs.readFileSync(FILE_SERVER_PRIV_KEY_FILE);
const serverPubKey = fs.readFileSync(FILE_SERVER_PUB_KEY_FILE);
const serverPubKey64 = bb.wrap(serverPubKey).toString('base64');
console.log('dialect.loki_proxy - serverPubKey', serverPubKey.toString('hex'))
// mount will set this in our module.exports
let cache;
const sendresponse = (json, resp) => {
const ts = Date.now();
const diff = ts-resp.start;
if (!configUtil.isQuiet()) {
if (diff > 1000) {
// this could be to do the client's connection speed
// how because we stop the clock before we send the response...
console.log(`${resp.path} served in ${ts - resp.start}ms`);
}
}
if (json.meta && json.meta.code) {
resp.status(json.meta.code);
}
if (resp.prettyPrint) {
json=JSON.parse(JSON.stringify(json,null,4));
}
//resp.set('Content-Type', 'text/javascript');
resp.type('application/json');
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.json(json);
}
// requestObj.body
// requestObj.headers
// requestObj.method
// requestObj.endpoint
async function createFakeReq(req, requestObj) {
//console.log('decrypted', requestObj);
//console.log('createFakeReq - body set', !!requestObj.body, 'body type', typeof requestObj.body, 'fileUpload set', !!(requestObj.body && requestObj.body.fileUpload));
// take case insensitive content-type and make it all lowercase
if (requestObj.headers) {
const contentTypeHeader = Object.keys(requestObj.headers).reduce(
(prev, key) => key.match(/content-type/i)?key:prev, false
);
// if not what we already expect, and is set
if (contentTypeHeader && contentTypeHeader !== 'content-type' &&
requestObj.headers[contentTypeHeader]) {
// fix it up
if (!configUtil.isQuiet()) {
console.log('old inner headers', requestObj.headers);
}
requestObj.headers['content-type'] = requestObj.headers[contentTypeHeader];
delete requestObj.headers[contentTypeHeader]; // remove the duplicate...
// console.log('new inner headers', requestObj.headers);
}
}
/*
console.log('createFakeReq - inner headers', requestObj.headers, 'header type', typeof requestObj.headers,
'json match', !!(requestObj.headers &&
requestObj.headers['content-type'] &&
requestObj.headers['content-type'].match(/^application\/json/i)));
*/
/*
// works as long as body isn't a string
const debugObj = JSON.parse(JSON.stringify(requestObj));
if (requestObj.body && requestObj.body.fileUpload) {
// clearly not removing it
delete debugObj.body.fileUpload;
}
console.log('rpc without body', debugObj);
*/
// if body is string but really JSON with fileUpload
if (typeof(requestObj.body) ==='string' && requestObj.body.match(/^{[ ]*"fileUpload"[ ]*:[ ]*/)) {
// decode enough to pick up fileUpload
// console.log('createFakeReq - body is string and detected file upload, attempting decoding');
requestObj.body = JSON.parse(requestObj.body);
}
// will need decode a multipart body...
//console.log('decrypted body', requestObj.body);
// handle file uploads
if (requestObj.body && requestObj.body.fileUpload) {
//console.log('detect file upload');
const fupData = Buffer.from(
bb.wrap(requestObj.body.fileUpload, 'base64').toArrayBuffer()
);
requestObj.body = ''; // free memory
//console.log('multipart data', buf2hex(fupData));
// await until busboy has parsed it all
const p = new Promise((resolve, reject) => {
// Desktop attachment:
// 'content-type': 'multipart/form-data; boundary=--------------------------132629963599778911961269'
//
const busboy = new Busboy({ headers: requestObj.headers });
let lastFile
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
if (!configUtil.isQuiet()) {
console.log('Field [' + fieldname + ']: value: ' + inspect(val));
}
});
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
if (!configUtil.isQuiet()) {
console.log('FUP file upload detected', fieldname, filename, encoding, mimetype)
}
// file is stream but it's expecting a buffer...
var buffers = [];
file.on('data', function(chunk) {
// chunk is a buffer since no encoding is set...
//console.log(chunk.length, 'chunk', chunk.toString('hex'));
buffers.push(chunk);
});
// finish is before end...
file.on('end', function() {
// console.log('buffers', buffers.length)
var buffer = Buffer.concat(buffers);
if (!configUtil.isQuiet()) {
console.log('detect file upload', buffer.length, 'bytes');
}
buffers = false; // free memory
const readableInstanceStream = new Readable({
read() {
// console.log('reading stream!')
this.push(buffer);
this.push(null);
// console.log('stream read!')
}
});
readableInstanceStream.length = buffer.length;
// we only handle single file uploads any way...
requestObj.file = {
buffer: readableInstanceStream,
originalname: filename,
mimetype: mimetype,
};
resolve();
});
});
// write and flush
// don't use .toString here, it'll fuck up the encoding
busboy.end(fupData);
// FIXME: we never resolve if no files...
});
await p;
//console.log('fup request', buf2hex(fupData));
// make sure normal JSON gets copied through...
requestObj.body = fupData.toString();
} else {
// emulate the bodyparser json decoder support
if (requestObj.headers && requestObj.headers['content-type'] &&
requestObj.headers['content-type'].match(/^application\/json/i) &&
typeof(requestObj.body) === 'string') {
// console.log('bodyPaser failed. Outer headers', req.headers);
if (!configUtil.isQuiet()) {
console.log('createFakeReq - bodyPaser failed');
}
requestObj.body = JSON.parse(requestObj.body);
}
// non file upload
// console.log('createFakeReq - non-fup request', requestObj); // just debug it all for now
//console.log('non-fup request body', requestObj.body, typeof(requestObj.body));
}
//console.log('setting up fakeReq');
// rewrite request
// console.log('old', req)
const fakeReq = {...req} // shallow copy?
fakeReq.path = '/' + requestObj.endpoint;
//fakeReq.url = fakeReq.path;
//fakeReq.originalUrl = fakeReq.path;
//fakeReq._httpMessage.path = fakeReq.path;
/*
fakeReq.route.path = fakeReq.path;
fakeReq.res.req.method = requestObj.method || 'GET';
fakeReq.res.req.url = fakeReq.path;
fakeReq.res.req.originalUrl = fakeReq.path;
fakeReq.res.path = fakeReq.path;
*/
fakeReq.cookies.request.url = fakeReq.path;
fakeReq.cookies.request.method = requestObj.method || 'GET';
//fakeReq.cookies.request.originalUrl = fakeReq.path;
//fakeReq.cookies.response.path = fakeReq.path;
//console.log('changed path to', fakeReq.path);
fakeReq.method = requestObj.method || 'GET';
//console.log('old headers', req.headers)
fakeReq.headers = requestObj.headers;
fakeReq.body = requestObj.body;
fakeReq.cookies.request.body = requestObj.body;
// disable any token passed by proxy
fakeReq.token = undefined;
fakeReq.cookies.request.token = undefined;
if (requestObj.headers && requestObj.headers['Authorization']) {
fakeReq.token = requestObj.headers['Authorization'].replace(/^Bearer /, '')
fakeReq.cookies.request.token = fakeReq.token;
}
// handle file uploads
fakeReq.file = requestObj.file;
fakeReq.cookies.request.file = requestObj.file;
//fakeReq.res.req = fakeReq;
//fakeReq.cookies.request = fakeReq;
//console.log('createFakeReq - rewrote to', fakeReq.method, fakeReq.path, fakeReq.token?'IDd':'anon')
//console.log('fake', fakeReq)
// might need to split on ? for querystring processing
// seems to be working fine...
if (fakeReq.path.match(/\?/)) {
let questions = fakeReq.path.split('?')
const path = questions.shift();
//fakeReq.query = querystring.parse(questions.join('?'));
fakeReq.cookies.request.query = querystring.parse(questions.join('?'));
}
// this does cause req in handlers to be immutable
// we'll have to adapt...
fakeReq.start = Date.now();
return fakeReq
}
// fix up createReq
function createReq(path, options) {
if (!options) options = {};
const req = _.extend(
{
method: "GET",
host: "",
cookies: {},
query: {},
url: path,
headers: {},
},
options
);
req.method = req.method.toUpperCase();
// req.connection=_req.connection
return req;
}
// fix up createRes
function createRes(callback) {
var res = {
_removedHeader: {},
};
// res=_.extend(res,require('express/lib/response'));
const headers = {};
let code = 200;
res.set = res.header = (x, y) => {
//console.log(`res.set [${x}][${y}]`, arguments.length)
// arguments.length is 1 sometimes...
if (x && y) {
res.setHeader(x, y);
} else {
// handle array/object
for (var key in x) {
res.setHeader(key, x[key]);
}
}
return res;
}
res.setHeader = (x, y) => {
// called for each letter?
// console.log('res.setHeader', x, y)
headers[x] = y;
headers[x.toLowerCase()] = y;
return res;
};
// fix up res.get
res.get=(x) => {
// usually just read content-type
// console.log('res.get', x)
return headers[x]
}
res.redirect = function(_code, url) {
// console.log('res.redirect', _code, url)
if (!_.isNumber(_code)) {
code = 301;
url = _code;
} else {
code = _code;
}
res.setHeader("Location", url);
res.end();
// callback(code,url)
};
res.status = function(number) {
// console.log('res.status', number)
code = number;
return res;
};
res.end = res.send = res.write = function(data) {
//console.log('end/send/write callback', !!callback)
if (callback) callback(code, data, headers);
//else console.error('res.end but no callback')
// else if (!options.quiet){
// _res.send(data)
// }
};
return res;
}
function fixUpMiddleware(app) {
//
// start runMiddleware fixups
//
// fix up runMiddleware
app.runMiddleware = function(path, options, callback) {
//console.log('app.runMiddleware', path)
if (callback) callback = _.once(callback);
if (typeof options == "function") {
callback = options;
options = null;
}
options = options || {};
options.url = path;
let new_req, new_res;
if (options.original_req) {
new_req = options.original_req;
for (var i in options) {
if (i == "original_req") continue;
new_req[i] = options[i];
}
} else {
new_req = createReq(path, options);
}
new_res = createRes(callback);
//console.log('running', new_req.path, 'against app')
this(new_req, new_res);
};
//
// fix ups done
//
}
module.exports = (app, prefix) => {
// set cache based on dispatcher object
cache = app.dispatcher.cache;
// we need config-file-path set up correctly to include this
configUtil = require('../../server/lib/lib.config.js')
// enable runMiddleware
runMiddleware(app); // set it up for fixups
fixUpMiddleware(app); // do the fixups
app.get(prefix + '/loki/v1/public_key', (req, res) => {
res.start = Date.now()
sendresponse({
meta: {
code: 200
},
data: serverPubKey64
}, res);
});
app.get(prefix + '/loki/v2/lsrpc', (req, res) => {
console.error("Processing a dummy GET /loki/v2/lsrpc");
sendresponse({
ciphertext: '',
}, res);
});
app.get(prefix + '/loki/v1/lsrpc', (req, res) => {
res.start = Date.now()
var ephemeralPubKeyHex = ''
const ephemeralPubKey = Buffer.from(
bb.wrap(ephemeralPubKeyHex,'hex').toArrayBuffer()
);
// build symmetrical keypair mix client pub key with server priv key
const symKey = libsignal.curve.calculateAgreement(
ephemeralPubKey,
serverPrivKey
);
if (debugCryptoValues) console.log('symKey', symKey.toString('hex'), symKey.byteLength);
if (!configUtil.isQuiet()) {
console.log('base64 decoding', cipherText64)
}
// base64 decode cipherText64 into buffer
const ivAndCiphertext = Buffer.from(
bb.wrap(cipherText64, 'base64').toArrayBuffer()
);
let decrypted = '{}';
try {
decrypted = libloki_crypt.DecryptGCM(symKey, ivAndCiphertext);
} catch(e) {
console.error('error', e.code, e.message);
return sendresponse({
meta: {
code: 400,
error: e.code + ' ' + e.messsage
},
}, res);
}
if (!configUtil.isQuiet()) {
console.log('decrypted', decrypted);
}
sendresponse({
ciphertext: '',
}, res);
});
function parseBodyPlusJSON(blob) {
try {
let view = new DataView(blob.buffer);
let len = view.getUint32(0, true);
let payload = blob.subarray(4, len + 4);
let json_payload = blob.subarray(4 + len);
let decoder = new TextDecoder("utf-8");
let json_str = decoder.decode(json_payload);
return {
body: payload,
json: JSON.parse(json_str)
}
} catch (err) {
console.log("Error parsing body: ", err);
}
}
function parse_v2_onions(blob) {
let { body, json } = parseBodyPlusJSON(blob);
json.ciphertext = body;
return json;
}
function derive_onion_key(ephemeralPubKeyHex) {
if (!ephemeralPubKeyHex || ephemeralPubKeyHex.length < 64) {
if (!configUtil.isQuiet()) {
console.error('No ephemeral_key in JSON body or sent of at least 65 bytes');
}
throw new Error('No ephemeral_key in JSON body or sent of at least 65 bytes');
}
// decode hex ephemeral key into buffer
// FIXME: needs a try/catch
const ephemeralPubKey = Buffer.from(
bb.wrap(ephemeralPubKeyHex,'hex').toArrayBuffer()
);
// build symmetrical keypair mix client pub key with server priv key
const sym_key = libloki_crypt.makeSymmetricKey(serverPrivKey, ephemeralPubKey);
return sym_key
}
/// Returns request object
function decrypt_onion_req(ciphertext, shared_key) {
let decrypted;
try {
decrypted = libloki_crypt.decryptGCM(shared_key, ciphertext);
} catch(e) {
console.error('decryption error', e.code, e.message);
throw e;
}
let requestObj;
try {
requestObj = JSON.parse(decrypted.toString());
} catch (e) {
if (!configUtil.isQuiet()) {
console.warn('Could not parse JSON', decrypted.toString(), e);
}
throw e;
}
return requestObj;
}
async function process_onion_request(req, res, ciphertext, ephemeral_key, compactRes = false) {
let shared_key;
try {
shared_key = derive_onion_key(ephemeral_key);
} catch (e) {
// Not able to encrypt response at this point
sendresponse({
meta: {
code: 400,
error: e.message,
headers: req.headers,
body: req.body,
},
}, res);
return;
}
try {
let req_obj = decrypt_onion_req(ciphertext, shared_key);
await process_onion_post_decryption(res, req, shared_key, req_obj, compactRes);
} catch (e) {
const code = 400;
const adnResObj = {
meta: {
code,
error: e.code + ' ' + e.messsage
},
}
res.end(encryptResp(JSON.stringify(adnResObj), shared_key, code));
}
}
app.post(prefix + '/loki/v2/lsrpc', async (req, res) => {
// this hack is to work around no json header passed
await req.lokiReady;
let array = new Uint8Array(req.originalBody);
let body = parse_v2_onions(array);
await process_onion_request(req, res, body.ciphertext, body.ephemeral_key);
});
app.post(prefix + '/loki/v3/lsrpc', async (req, res) => {
// this hack is to work around no json header passed
await req.lokiReady;
let array = new Uint8Array(req.originalBody);
let body = parse_v2_onions(array);
// v3 endpoint uses a more compact (base64) encoding for files
const compactRes = true;
await process_onion_request(req, res, body.ciphertext, body.ephemeral_key, compactRes);
})
// Returns ciphertext in base64
function encryptResp(resultBody, symKey, code = 200, headers = {}) {
// encryptGCM handles the textEncoder
const plaintextEnc = JSON.stringify({
body: resultBody,
headers: headers,
status: code
});
const cipheredBuffer = libloki_crypt.encryptGCM(symKey, plaintextEnc);
// convert final buffer to base64
const cipherText64 = bb.wrap(cipheredBuffer).toString('base64');
return cipherText64;
}
function encryptStringResp(responseStr, symKey) {
const cipheredBuffer = libloki_crypt.encryptGCM(symKey, responseStr);
// convert final buffer to base64
const cipherText64 = bb.wrap(cipheredBuffer).toString('base64');
return cipherText64;
}
// Will return the original body if can't decode
function decodeFileBody(resultBody) {
if (!Buffer.isBuffer(resultBody)) {
console.error("resultBody is NOT Buffer");
return resultBody;
}
let bodyBase64 = bb.wrap(resultBody.buffer).toString('base64');
return bodyBase64;
}
function encryptRespV2(resultBody, symKey, code = 200, headers = {}) {
let body = decodeFileBody(resultBody);
let plaintextEnc = JSON.stringify({
body: body,
headers: headers,
status: code
});
return encryptStringResp(plaintextEnc, symKey);
}
async function process_onion_post_decryption(res, req, shared_key, requestObj, compactRes) {
const fakeReq = await createFakeReq(req, requestObj)
const diff = fakeReq.start - res.start;
if (!configUtil.isQuiet()) {
console.log('lsrpc', fakeReq.method, fakeReq.path, 'decoding took', diff, 'ms');
}
fakeReq.runMiddleware(fakeReq.path, (code, resultBody, headers) => {
// never gets here...
const execStart = Date.now();
if (!configUtil.isQuiet()) {
const execDiff = execStart - fakeReq.start;
console.log(fakeReq.method, fakeReq.path, 'lspc execution took', execDiff, 'ms');
// console.log('body', resultBody)
}
/// A bit of a hack to recognise file download requests:
const isFileDownload = fakeReq.path.substr(0, 10) === "/loki/v1/f";
let response;
if (compactRes && isFileDownload) {
response = encryptRespV2(resultBody, shared_key, code, headers);
} else {
response = encryptResp(resultBody, shared_key, code, headers);
}
res.end(response);
if (!configUtil.isQuiet()) {
const respDiff = Date.now() - execStart;
console.log(fakeReq.method, fakeReq.path, 'lspc response took', respDiff, 'ms');
}
});
}
app.post(prefix + '/loki/v1/lsrpc', async (req, res) => {
// this hack is to work around no json header passed
if (Object.keys(req.body) == 0 && req.lokiReady) {
if (!configUtil.isQuiet()) {
console.log('no json header, waiting on request to finish')
}
await req.lokiReady
//console.log('overriding body with', req.originalBody)
if (!configUtil.isQuiet()) {
console.log('request finished', req.originalBody.length.toLocaleString(), 'bytes')
}
try {
req.body = JSON.parse(req.originalBody)
} catch(e) {
console.error(`JSON parse error on`, req.originalBody)
}
}
// console.log('secure_rpc body', req.body, typeof req.body);
if (!req.body || !req.body.ciphertext) {
if (!configUtil.isQuiet()) {
console.warn('not JSON or no ciphertext', req.body);
}
return sendresponse({
meta: {
code: 400,
error: "not JSON or no ciphertext",
headers: req.headers,
body: req.body,
},
}, res);
}
const cipherText64 = req.body.ciphertext;
// base64 decode cipherText64 into buffer
const ciphertext = Buffer.from(
bb.wrap(cipherText64, 'base64').toArrayBuffer()
);
await process_onion_request(req, res, ciphertext, req.body.ephemeral_key);
});
app.post(prefix + '/loki/v1/secure_rpc_debug', async (req, res) => {
// FIXME
const cipherText64 = req.body.cipherText64;
res.start = Date.now()
sendresponse({
meta: {
code: 200
},
data: cipherText64
}, res);
});
// proxy version
app.post(prefix + '/loki/v1/secure_rpc', async (req, res) => {
res.start = Date.now()
// console.log('got secure_rpc', req.path);
// should have debug asks and ephermal key
//console.log('headers', req.headers);
//console.log('secure_rpc body', req.body, typeof req.body);
if (!req.body.cipherText64) {
console.warn('no cipherText64')
return sendresponse({
meta: {
code: 400,
error: "not JSON or no cipherText64",
headers: req.headers,
body: req.body,
},
}, res);
}
// after we get our bearings
// get the base64 body...
const cipherText64 = req.body.cipherText64;
// req.header will always have headers...
const debugHeaders = req.headers['x-loki-file-server-debug-headers'];
const debugCryptoValues = req.headers['x-loki-file-server-debug-crypto-values'];
const debugFup = req.headers['x-loki-file-server-debug-file-upload'];
if (debugHeaders) console.log('headers', req.headers);
if (debugCryptoValues) console.log('cipherText64', cipherText64, cipherText64 && cipherText64.length);
const ephemeralPubKey64 = req.headers['x-loki-file-server-ephemeral-key'];
//console.log('ephemeralPubKey', ephemeralPubKey64);
if (!ephemeralPubKey64 || ephemeralPubKey64.length < 32) {
console.warn('proxy ephemeralPubKey64 error', ephemeralPubKey64)
return sendresponse({
meta: {
code: 400,
error: "No x-loki-file-server-ephemeral-key header sent of at least 32 bytes",
headers: req.headers,
body: req.body,
},
}, res);
}
// decode hex ephemeral key into buffer
// FIXME: needs a try/catch
const ephemeralPubKey = Buffer.from(
bb.wrap(ephemeralPubKey64, 'base64').toArrayBuffer()
);
//console.log('ephemeralPubKey size', ephemeralPubKey.length, ephemeralPubKey.byteLength)
//console.log('serverPrivKey size', serverPrivKey.length, serverPrivKey.byteLength)
if (debugCryptoValues) console.log('ephemeralPubKey', ephemeralPubKey.toString('hex'), ephemeralPubKey.byteLength);
// build symmetrical keypair mix client pub key with server priv key
const symKey = libsignal.curve.calculateAgreement(
ephemeralPubKey,
serverPrivKey
);
if (debugCryptoValues) console.log('symKey', symKey.toString('hex'), symKey.byteLength);
// base64 decode cipherText64 into buffer
const ivAndCiphertext = Buffer.from(
bb.wrap(cipherText64, 'base64').toArrayBuffer()
);
// extract iv
const iv = ivAndCiphertext.slice(0, IV_LENGTH);
if (debugCryptoValues) console.log('iv', iv.toString('hex'), iv.byteLength);
// extra text
const ciphertext = ivAndCiphertext.slice(IV_LENGTH);
if (debugCryptoValues) console.log('ciphertext', ciphertext.toString('hex'), ciphertext.byteLength);
let decrypted = '{}';
try {
decrypted = await libsignal.crypto.decrypt(symKey, ciphertext, iv);
} catch(e) {
console.warn('proxy decrypt error')
return sendresponse({
meta: {
code: 400,
error: e.code + ' ' + e.messsage
},
}, res);
}
let requestObj;
try {
requestObj = JSON.parse(decrypted.toString());
} catch(e) {
console.warn('proxy parse unencrypted error')
sendresponse({
meta: {
code: 400,
error: e.code + ' ' + e.messsage
},
}, res);
return;
}
//console.log('JSON decoded', requestObj);
const fakeReq = await createFakeReq(req, requestObj)
/*
function LokiDHEncryptStream(options) {
if (!(this instanceof LokiDHEncryptStream)) {
return new LokiDHEncryptStream(options);
}
// init Transform
stream.Transform.call(this, options);
}
util.inherits(LokiDHEncryptStream, stream.Transform);
var finalChunk = '';
LokiDHEncryptStream.prototype._transform = function (chunk, enc, cb) {
finalChunk += chunk.toString();
// no push ...
cb();
}
LokiDHEncryptStream.prototype._flush = function(cb) {
this.push('{}')
cb();
}
var encoder = new LokiDHEncryptStream();
encoder.pipe(res);
*/
/*
const oldWrite = res.write
const oldEnd = res.end
res.write = function(chunk) {
console.log('write chunk', chunk)
}
res.end = function(chunk, encoding) {
console.log('end chunk', chunk.toString(), encoding);
// ok we can modify the results, we just need to get the correct contents...
var finalContents = JSON.stringify({
meta: {
code: 200
},
data: requestObj
});
this.setHeader('Content-Length', finalContents.length);
oldEnd.call(this, finalContents, encoding);
}
*/
// redispatch internally
const diff = fakeReq.start - res.start;
if (!configUtil.isQuiet()) {
console.log(fakeReq.method, fakeReq.path, 'decoding took', diff, 'ms');
}
fakeReq.runMiddleware(fakeReq.path, async (code, resultBody, headers) => {
const execStart = Date.now();
if (!configUtil.isQuiet()) {
const execDiff = execStart - fakeReq.start;
console.log(fakeReq.method, fakeReq.path, 'execution took', execDiff, 'ms');
// console.log('body', resultBody)
}
const payloadData = resultBody === undefined ? Buffer.alloc(0) : Buffer.from(
bb.wrap(resultBody).toArrayBuffer()
);
// console.log('payloadData', payloadData)
// if performance problems, we can cache this value for a period of time
const returnIv = crypto.randomBytes(IV_LENGTH);
const iv64 = bb.wrap(returnIv).toString('base64');
// encrypt payloadData with symmetric Key using iv
const cipherBody = await libsignal.crypto.encrypt(symKey, payloadData, returnIv);
// console.log('cipherBody', cipherBody)
// make final buffer for cipherText
const ivAndCiphertext = new Uint8Array(
returnIv.byteLength + cipherBody.byteLength
);
// add iv
ivAndCiphertext.set(new Uint8Array(returnIv));
// add ciphertext after iv position
ivAndCiphertext.set(new Uint8Array(cipherBody), returnIv.byteLength);
// convert final buffer to base64
const cipherText64 = bb.wrap(ivAndCiphertext).toString('base64');
sendresponse({
meta: {
code: 200
},
data: cipherText64,
}, res);
if (!configUtil.isQuiet()) {
const respDiff = Date.now() - execStart;
console.log(fakeReq.method, fakeReq.path, 'response took', respDiff, 'ms');
}
});
});
}