ES2015 classes for LokiServer and FallBackSessionCipher

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

@ -4,26 +4,7 @@ const fetch = require('node-fetch');
const is = require('@sindresorhus/is');
const { fork } = require('child_process');
module.exports = {
initialize,
};
function initialize({ url }) {
if (!is.string(url)) {
throw new Error('WebAPI.initialize: Invalid server url');
}
return {
connect,
};
function connect() {
return {
sendMessage,
retrieveMessages,
};
function getPoWNonce(timestamp, ttl, pubKey, data) {
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');
@ -51,13 +32,35 @@ function initialize({ url }) {
}
});
});
}
class LokiServer {
constructor({ url }) {
if (!is.string(url)) {
throw new Error('WebAPI.initialize: Invalid server url');
}
this.url = url;
}
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');
}
async function retrieveMessages(pubKey) {
const options = {
url: `${url}/retrieve`,
type: 'GET',
responseType: 'json',
url: `${this.url}/store`,
type: 'POST',
responseType: undefined,
timeout: undefined,
};
@ -65,7 +68,11 @@ function initialize({ 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,
@ -96,27 +103,14 @@ function initialize({ url }) {
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('retrieveMessages: 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');
throw HTTPError('sendMessage: error response', response.status, result);
}
async retrieveMessages(pubKey) {
const options = {
url: `${url}/store`,
type: 'POST',
responseType: undefined,
url: `${this.url}/retrieve`,
type: 'GET',
responseType: 'json',
timeout: undefined,
};
@ -124,11 +118,7 @@ function initialize({ 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,
@ -159,8 +149,7 @@ function initialize({ url }) {
return result;
}
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('sendMessage: error response', response.status, result);
}
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) {
class FallBackSessionCipher {
constructor(address) {
this.identityKeyString = address.getName();
this.pubKey = StringView.hexToArrayBuffer(address.getName());
}
this.encrypt = async plaintext => {
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!');
@ -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