ES2015 classes for LokiServer and FallBackSessionCipher

pull/34/head
sachaaaaa 6 years ago
parent d6534e1bb8
commit ad1cf94526

@ -4,163 +4,152 @@ const fetch = require('node-fetch');
const is = require('@sindresorhus/is');
const { fork } = require('child_process');
module.exports = {
initialize,
};
function getPoWNonce(timestamp, ttl, pubKey, data) {
return new Promise((resolve, reject) => {
// Create forked node process to calculate PoW without blocking main process
const child = fork('./libloki/proof-of-work.js');
// Send data required for PoW to child process
child.send({
timestamp,
ttl,
pubKey,
data,
});
// Handle child process error (should never happen)
child.on('error', err => {
reject(err);
});
// Callback to receive PoW result
child.on('message', msg => {
if (msg.err) {
reject(msg.err);
} else {
child.kill();
resolve(msg.nonce);
}
});
});
}
class LokiServer {
function initialize({ url }) {
if (!is.string(url)) {
throw new Error('WebAPI.initialize: Invalid server url');
constructor({ url }) {
if (!is.string(url)) {
throw new Error('WebAPI.initialize: Invalid server url');
}
this.url = url;
}
return {
connect,
};
async sendMessage(pubKey, data, ttl) {
const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
const timestamp = Math.floor(Date.now() / 1000);
// Nonce is returned as a base64 string to include in header
let nonce;
try {
nonce = await getPoWNonce(timestamp, ttl, pubKey, data64);
} catch (err) {
// Something went horribly wrong
// TODO: Handle gracefully
log.error('Error computing PoW');
}
const options = {
url: `${this.url}/store`,
type: 'POST',
responseType: undefined,
timeout: undefined,
};
function connect() {
return {
sendMessage,
retrieveMessages,
log.info(options.type, options.url);
const fetchOptions = {
method: options.type,
body: data64,
headers: {
'X-Loki-pow-nonce': nonce,
'X-Loki-timestamp': timestamp.toString(),
'X-Loki-ttl': ttl.toString(),
'X-Loki-recipient': pubKey,
},
timeout: options.timeout,
};
function getPoWNonce(timestamp, ttl, pubKey, data) {
return new Promise((resolve, reject) => {
// Create forked node process to calculate PoW without blocking main process
const child = fork('./libloki/proof-of-work.js');
// Send data required for PoW to child process
child.send({
timestamp,
ttl,
pubKey,
data,
});
// Handle child process error (should never happen)
child.on('error', err => {
reject(err);
});
// Callback to receive PoW result
child.on('message', msg => {
if (msg.err) {
reject(msg.err);
} else {
child.kill();
resolve(msg.nonce);
}
});
});
let response;
try {
response = await fetch(options.url, fetchOptions);
} catch (e) {
log.error(options.type, options.url, 0, 'Error');
throw HTTPError('fetch error', 0, e.toString());
}
async function retrieveMessages(pubKey) {
const options = {
url: `${url}/retrieve`,
type: 'GET',
responseType: 'json',
timeout: undefined,
};
log.info(options.type, options.url);
const fetchOptions = {
method: options.type,
headers: {
'X-Loki-recipient': pubKey,
},
timeout: options.timeout,
};
let response;
try {
response = await fetch(options.url, fetchOptions);
} catch (e) {
log.error(options.type, options.url, 0, 'Error');
throw HTTPError('fetch error', 0, e.toString());
}
let result;
if (
options.responseType === 'json' &&
response.headers.get('Content-Type') === 'application/json'
) {
result = await response.json();
} else if (options.responseType === 'arraybuffer') {
result = await response.buffer();
} else {
result = await response.text();
}
let result;
if (
options.responseType === 'json' &&
response.headers.get('Content-Type') === 'application/json'
) {
result = await response.json();
} else if (options.responseType === 'arraybuffer') {
result = await response.buffer();
} else {
result = await response.text();
}
if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success');
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('retrieveMessages: error response', response.status, result);
if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success');
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('sendMessage: error response', response.status, result);
}
async function sendMessage(pubKey, data, ttl) {
const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
const timestamp = Math.floor(Date.now() / 1000);
// Nonce is returned as a base64 string to include in header
let nonce;
try {
nonce = await getPoWNonce(timestamp, ttl, pubKey, data64);
} catch (err) {
// Something went horribly wrong
// TODO: Handle gracefully
log.error('Error computing PoW');
}
async retrieveMessages(pubKey) {
const options = {
url: `${this.url}/retrieve`,
type: 'GET',
responseType: 'json',
timeout: undefined,
};
const options = {
url: `${url}/store`,
type: 'POST',
responseType: undefined,
timeout: undefined,
};
log.info(options.type, options.url);
const fetchOptions = {
method: options.type,
body: data64,
headers: {
'X-Loki-pow-nonce': nonce,
'X-Loki-timestamp': timestamp.toString(),
'X-Loki-ttl': ttl.toString(),
'X-Loki-recipient': pubKey,
},
timeout: options.timeout,
};
let response;
try {
response = await fetch(options.url, fetchOptions);
} catch (e) {
log.error(options.type, options.url, 0, 'Error');
throw HTTPError('fetch error', 0, e.toString());
}
log.info(options.type, options.url);
let result;
if (
options.responseType === 'json' &&
response.headers.get('Content-Type') === 'application/json'
) {
result = await response.json();
} else if (options.responseType === 'arraybuffer') {
result = await response.buffer();
} else {
result = await response.text();
}
const fetchOptions = {
method: options.type,
headers: {
'X-Loki-recipient': pubKey,
},
timeout: options.timeout,
};
if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success');
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('sendMessage: error response', response.status, result);
let response;
try {
response = await fetch(options.url, fetchOptions);
} catch (e) {
log.error(options.type, options.url, 0, 'Error');
throw HTTPError('fetch error', 0, e.toString());
}
let result;
if (
options.responseType === 'json' &&
response.headers.get('Content-Type') === 'application/json'
) {
result = await response.json();
} else if (options.responseType === 'arraybuffer') {
result = await response.buffer();
} else {
result = await response.text();
}
if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success');
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('retrieveMessages: error response', response.status, result);
}
}
@ -177,3 +166,7 @@ function HTTPError(message, providedCode, response, stack) {
}
return e;
}
module.exports = {
LokiServer,
};

@ -1,61 +1,49 @@
/* global window, libsignal, textsecure, StringView, log */
// eslint-disable-next-line func-names
(function() {
(function () {
window.libloki = window.libloki || {};
class FallBackDecryptionError extends Error {}
class FallBackDecryptionError extends Error { }
const IV_LENGTH = 16;
function FallBackSessionCipher(address) {
this.identityKeyString = address.getName();
this.pubKey = StringView.hexToArrayBuffer(address.getName());
class FallBackSessionCipher {
this.encrypt = async plaintext => {
constructor(address) {
this.identityKeyString = address.getName();
this.pubKey = StringView.hexToArrayBuffer(address.getName());
}
async encrypt(plaintext) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const myPrivateKey = myKeyPair.privKey;
const symmetricKey = libsignal.Curve.calculateAgreement(
this.pubKey,
myPrivateKey
);
const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
const ciphertext = await libsignal.crypto.encrypt(
symmetricKey,
plaintext,
iv
);
const ivAndCiphertext = new Uint8Array(
iv.byteLength + ciphertext.byteLength
);
const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv);
const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
return {
type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST, // friend request
type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST,
body: ivAndCiphertext,
registrationId: null,
};
};
this.decrypt = async ivAndCiphertext => {
}
async decrypt(ivAndCiphertext) {
const iv = ivAndCiphertext.slice(0, IV_LENGTH);
const cipherText = ivAndCiphertext.slice(IV_LENGTH);
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const myPrivateKey = myKeyPair.privKey;
const symmetricKey = libsignal.Curve.calculateAgreement(
this.pubKey,
myPrivateKey
);
const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
try {
return await libsignal.crypto.decrypt(symmetricKey, cipherText, iv);
} catch (e) {
throw new FallBackDecryptionError(
`Could not decrypt message from ${
this.identityKeyString
} using FallBack encryption.`
);
}
};
catch (e) {
throw new FallBackDecryptionError(`Could not decrypt message from ${this.identityKeyString} using FallBack encryption.`);
}
}
}
async function getPreKeyBundleForNumber(pubKey) {
@ -78,13 +66,13 @@
// generate and store new prekey
const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
textsecure.storage.put('maxPreKeyId', preKeyId + 1);
const preKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
const newPreKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
await textsecure.storage.protocol.storePreKey(
preKey.keyId,
preKey.keyPair,
newPreKey.keyId,
newPreKey.keyPair,
pubKey
);
resolve({ pubKey: preKey.keyPair.pubKey, keyId: preKeyId });
resolve({ pubKey: newPreKey.keyPair.pubKey, keyId: preKeyId });
}
}),
]);

@ -121,7 +121,7 @@ function MessageReceiver(username, password, signalingKey, options = {}) {
this.signalingKey = signalingKey;
this.username = username;
this.password = password;
this.lokiserver = window.LokiAPI.connect();
this.lokiserver = window.LokiAPI;
if (!options.serverTrustRoot) {
throw new Error('Server trust root is required!');
@ -456,7 +456,7 @@ MessageReceiver.prototype.extend({
if (envelope.source) {
return `${envelope.source}.${
envelope.sourceDevice
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
}
return envelope.id;
@ -844,7 +844,7 @@ MessageReceiver.prototype.extend({
const isMe = envelope.source === textsecure.storage.user.getNumber();
const isLeavingGroup = Boolean(
message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
);
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
@ -886,7 +886,7 @@ MessageReceiver.prototype.extend({
const isMe = envelope.source === textsecure.storage.user.getNumber();
const isLeavingGroup = Boolean(
message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
);
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
@ -958,7 +958,7 @@ MessageReceiver.prototype.extend({
let conversation;
try {
conversation = ConversationController.get(envelope.source);
} catch (e) {}
} catch (e) { }
if (!conversation) {
const accepted = await this.promptUserToAcceptFriendRequest(
envelope.source,

@ -35,7 +35,7 @@ function OutgoingMessage(
this.callback = callback;
this.silent = silent;
this.lokiserver = window.LokiAPI.connect();
this.lokiserver = window.LokiAPI;
this.numbersCompleted = 0;
this.errors = [];

@ -201,11 +201,9 @@ window.WebAPI = initializeWebAPI({
proxyUrl: config.proxyUrl,
});
const {
initialize: initializeLokiAPI,
} = require('./js/modules/loki_message_api');
const { LokiServer } = require('./js/modules/loki_message_api');
window.LokiAPI = initializeLokiAPI({
window.LokiAPI = new LokiServer({
url: config.serverUrl,
});
@ -213,7 +211,7 @@ window.mnemonic = require('./libloki/mnemonic');
// Linux seems to periodically let the event loop stop, so this is a global workaround
setInterval(() => {
window.nodeSetImmediate(() => {});
window.nodeSetImmediate(() => { });
}, 1000);
const { autoOrientImage } = require('./js/modules/auto_orient_image');

Loading…
Cancel
Save