Linting and other housekeeping stuff

pull/23/head
sachaaaaa 7 years ago
parent d91f04930c
commit 4b9fcb98d4

@ -2,6 +2,7 @@ build/**
components/**
coverage/**
dist/**
mnemonic_languages/**
# these aren't ready yet, pulling files in one-by-one
libtextsecure/test/*.js

@ -28,6 +28,7 @@ js/libsignal-protocol-worker.js
libtextsecure/libsignal-protocol.js
libtextsecure/test/blanket_mocha.js
test/blanket_mocha.js
mnemonic_languages/**
# Test fixtures
test/fixtures.js

@ -87,9 +87,7 @@ module.exports = grunt => {
dest: 'js/libtextsecure.js',
},
libloki: {
src: [
'libloki/libloki-protocol.js',
],
src: ['libloki/libloki-protocol.js'],
dest: 'js/libloki.js',
},
libtextsecuretest: {

@ -3,4 +3,4 @@
Run `window.getAccountManager().addMockContact()` in the debugger console. This will print the fake contact public key.
Behind the scenes, this also emulates that we're already received the contact's prekeys by generating them and saving them in our db.
Copy/paste that public key in the search bar to start chatting.
The messages should have a "v" tick to mark that the message was correctly sent (you need to run the httpserver from /mockup_servers)
The messages should have a "v" tick to mark that the message was correctly sent (you need to run the httpserver from /mockup_servers)

@ -1,12 +1,12 @@
Loki Messenger
==========================
# Loki Messenger
Loki Messenger allows for truly decentralized and end to end and private encrypted chats, Loki Messenger is built to handle both online and fully Asynchronous offline messages , Loki messenger implements the Signal protocol for message encryption, Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Loki messenger is routed through [Lokinet](https://github.com/loki-project/loki-network).
## Summary
Loki messenger integrates directly with Loki Service Nodes, which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as both federated servers which store messages offline, and a set of nodes which allow for mixnet functionality obfuscating users IP Addresses. For a full understanding of how Loki messenger works, read the [Loki whitepaper](https://loki.network/wp-content/uploads/2018/08/LokiWhitepaperV3_1.pdf)
**Online Messages**
**Online Messages**
If Alice and Bob are both online they can simply resolve each others public keys, to introduction sets, this functionality is handled by interfacing with Lokinet. With the appropriate introduction sets Alice and Bob can create a path and using onion routing pass messages through the Loki network without giving away personally identifiable information like their IP address.
@ -18,7 +18,7 @@ Offline messaging uses Swarms, given any users public key the user can resolve a
Spam protections for Loki Messenger are based on a Proof of Work which is attached to any message that exceeds a default size or Time To Live, this process is discussed further in the [Loki whitepaper](https://loki.network/wp-content/uploads/2018/08/LokiWhitepaperV3_1.pdf).
## Want to Contribute? Found a Bug or Have a feature request?
## Want to Contribute? Found a Bug or Have a feature request?
Please search for any [existing issues](https://github.com/loki-project/loki-messenger/issues) that describe your bugs in order to avoid duplicate submissions. Submissions can be made by making a pull request to our development branch, if you don't know where to start contributing , try reading the Github issues page for ideas.

@ -823,11 +823,13 @@
},
"sendMessageDisabled": {
"message": "Waiting for friend request approval",
"description": "Placeholder text in the message entry field when it is disabled while we are waiting for a friend request approval"
"description":
"Placeholder text in the message entry field when it is disabled while we are waiting for a friend request approval"
},
"sendMessageFriendRequest": {
"message": "Hi there! This is <insert name here> !",
"description": "Placeholder text in the message entry field when it is the first message sent to that contact"
"description":
"Placeholder text in the message entry field when it is the first message sent to that contact"
},
"groupMembers": {
"message": "Group members"

@ -57,7 +57,7 @@
unreadCount: 0,
verified: textsecure.storage.protocol.VerifiedStatus.DEFAULT,
keyExchangeCompleted: false,
friendRequestStatus: { allowSending: true, unlockTimestamp: null }
friendRequestStatus: { allowSending: true, unlockTimestamp: null },
};
},
@ -352,9 +352,7 @@
},
isKeyExchangeCompleted() {
if (!this.isPrivate()) {
throw new Error(
'isKeyExchangeCompleted not implemented for groups'
);
throw new Error('isKeyExchangeCompleted not implemented for groups');
}
if (this.isMe()) {
@ -365,11 +363,9 @@
},
setKeyExchangeCompleted(completed) {
if (typeof completed !== 'boolean') {
throw new Error(
'setKeyExchangeCompleted expects a bool'
);
throw new Error('setKeyExchangeCompleted expects a bool');
}
this.set({ keyExchangeCompleted: completed });
},
getFriendRequestStatus() {
@ -386,7 +382,10 @@
const friendRequestStatus = this.getFriendRequestStatus();
if (friendRequestStatus) {
if (!friendRequestStatus.allowSending) {
const delay = Math.max(friendRequestStatus.unlockTimestamp - Date.now(), 0);
const delay = Math.max(
friendRequestStatus.unlockTimestamp - Date.now(),
0
);
setTimeout(() => {
this.onFriendRequestTimedOut();
}, delay);
@ -394,14 +393,14 @@
}
},
onFriendRequestAccepted() {
this.save({ friendRequestStatus: null })
this.save({ friendRequestStatus: null });
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'chat');
},
onFriendRequestTimedOut() {
let friendRequestStatus = this.getFriendRequestStatus();
const friendRequestStatus = this.getFriendRequestStatus();
friendRequestStatus.allowSending = true;
this.save({ friendRequestStatus })
this.save({ friendRequestStatus });
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'friend-request');
},
@ -414,14 +413,16 @@
}
friendRequestStatus.allowSending = false;
const delayMs = 100 * 1000 ;//(60 * 60 * 1000 * friendRequestLockDuration);
const delayMs = 60 * 60 * 1000 * friendRequestLockDuration;
friendRequestStatus.unlockTimestamp = Date.now() + delayMs;
this.trigger('disable:input', true);
this.trigger('change:placeholder', 'disabled');
setTimeout(() => { this.onFriendRequestTimedOut() }, delayMs);
setTimeout(() => {
this.onFriendRequestTimedOut();
}, delayMs);
this.save({ friendRequestStatus })
this.save({ friendRequestStatus });
},
isUnverified() {
if (this.isPrivate()) {
@ -672,8 +673,8 @@
validateNumber() {
if (this.isPrivate()) {
if (this.id.length == (33 * 2)) // 33 bytes in hex
{
if (this.id.length === 33 * 2) {
// 33 bytes in hex
this.set({ id: this.id });
return null;
}

@ -1,3 +1,5 @@
/* global log */
const fetch = require('node-fetch');
const is = require('@sindresorhus/is');
const { fork } = require('child_process');
@ -34,12 +36,12 @@ function initialize({ url }) {
});
// Handle child process error (should never happen)
child.on('error', (err) => {
child.on('error', err => {
reject(err);
});
// Callback to receive PoW result
child.on('message', (msg) => {
child.on('message', msg => {
if (msg.err) {
reject(msg.err);
} else {
@ -47,7 +49,6 @@ function initialize({ url }) {
resolve(msg.nonce);
}
});
});
}
@ -57,11 +58,11 @@ function initialize({ url }) {
let nonce;
try {
nonce = await getPoWNonce(timestamp, ttl, pubKey, data);
} catch(err) {
} catch (err) {
// Something went horribly wrong
// TODO: Handle gracefully
console.log("Error computing PoW");
};
log.error('Error computing PoW');
}
const options = {
url: `${url}/send_message`,
@ -69,7 +70,7 @@ function initialize({ url }) {
responseType: undefined,
timeout: undefined,
};
log.info(options.type, options.url);
const fetchOptions = {
@ -88,8 +89,7 @@ function initialize({ url }) {
let response;
try {
response = await fetch(options.url, fetchOptions);
}
catch(e) {
} catch (e) {
log.error(options.type, options.url, 0, 'Error');
throw HTTPError('fetch error', 0, e.toString());
}
@ -109,15 +109,10 @@ function initialize({ url }) {
if (response.status >= 0 && response.status < 400) {
log.info(options.type, options.url, response.status, 'Success');
return [result, response.status];
} else {
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError(
'sendMessage: error response',
response.status,
result
);
}
};
log.error(options.type, options.url, response.status, 'Error');
throw HTTPError('sendMessage: error response', response.status, result);
}
}
}

@ -44,18 +44,32 @@ const migrations = [
transaction.db.createObjectStore('sessions');
transaction.db.createObjectStore('identityKeys');
const preKeys = transaction.db.createObjectStore('preKeys', { keyPath: 'id'});
const preKeys = transaction.db.createObjectStore('preKeys', {
keyPath: 'id',
});
preKeys.createIndex('recipient', 'recipient', { unique: true });
transaction.db.createObjectStore('signedPreKeys');
transaction.db.createObjectStore('items');
const contactPreKeys = transaction.db.createObjectStore('contactPreKeys', { keyPath: 'id', autoIncrement : true });
contactPreKeys.createIndex('identityKeyString', 'identityKeyString', { unique: false });
const contactPreKeys = transaction.db.createObjectStore(
'contactPreKeys',
{ keyPath: 'id', autoIncrement: true }
);
contactPreKeys.createIndex('identityKeyString', 'identityKeyString', {
unique: false,
});
contactPreKeys.createIndex('keyId', 'keyId', { unique: false });
const contactSignedPreKeys = transaction.db.createObjectStore('contactSignedPreKeys', { keyPath: 'id', autoIncrement : true });
contactSignedPreKeys.createIndex('identityKeyString', 'identityKeyString', { unique: false });
const contactSignedPreKeys = transaction.db.createObjectStore(
'contactSignedPreKeys',
{ keyPath: 'id', autoIncrement: true }
);
contactSignedPreKeys.createIndex(
'identityKeyString',
'identityKeyString',
{ unique: false }
);
contactSignedPreKeys.createIndex('keyId', 'keyId', { unique: false });
window.log.info('creating debug log');

@ -156,7 +156,7 @@ function _createSocket(url, { certificateAuthority, proxyUrl, signature }) {
let headers;
if (signature) {
headers = {
signature
signature,
};
}

@ -184,16 +184,18 @@
database: Whisper.Database,
model: ContactPreKey,
fetchBy(filter) {
return this.fetch({ conditions: filter, });
return this.fetch({ conditions: filter });
},
});
const ContactSignedPreKey = Model.extend({ storeName: 'contactSignedPreKeys' });
const ContactSignedPreKey = Model.extend({
storeName: 'contactSignedPreKeys',
});
const ContactSignedPreKeyCollection = Backbone.Collection.extend({
storeName: 'contactSignedPreKeys',
database: Whisper.Database,
model: ContactSignedPreKey,
fetchBy(filter) {
return this.fetch({ conditions: filter, });
return this.fetch({ conditions: filter });
},
});
@ -217,7 +219,7 @@
item.fetch().then(() => {
resolve(item.get('value'));
}, reject);
});*/
}); */
},
/* Returns a prekeypair object or undefined */
@ -244,7 +246,10 @@
return new Promise(resolve => {
prekey.fetch().then(
() => {
window.log.info('Successfully fetched prekey for recipient :', contactIdentityKeyString);
window.log.info(
'Successfully fetched prekey for recipient :',
contactIdentityKeyString
);
resolve({
pubKey: prekey.get('publicKey'),
privKey: prekey.get('privateKey'),
@ -280,19 +285,25 @@
loadContactPreKeys(filters) {
const contactPreKeys = new ContactPreKeyCollection();
return new Promise((resolve, reject) => {
contactPreKeys.fetchBy(filters).then(() => {
resolve(
contactPreKeys.map(prekey => ({
id: prekey.get('id'),
keyId: prekey.get('keyId'),
publicKey: prekey.get('publicKey'),
identityKeyString: prekey.get('identityKeyString'),
}))
);
}).fail(e => {
window.log.error('Failed to fetch signed prekey with filters', filters);
reject(e);
});
contactPreKeys
.fetchBy(filters)
.then(() => {
resolve(
contactPreKeys.map(prekey => ({
id: prekey.get('id'),
keyId: prekey.get('keyId'),
publicKey: prekey.get('publicKey'),
identityKeyString: prekey.get('identityKeyString'),
}))
);
})
.fail(e => {
window.log.error(
'Failed to fetch signed prekey with filters',
filters
);
reject(e);
});
});
},
storeContactPreKey(pubKey, preKey) {
@ -377,22 +388,28 @@
loadContactSignedPreKeys(filters) {
const contactSignedPreKeys = new ContactSignedPreKeyCollection();
return new Promise((resolve, reject) => {
contactSignedPreKeys.fetchBy(filters).then(() => {
resolve(
contactSignedPreKeys.map(prekey => ({
id: prekey.get('id'),
identityKeyString: prekey.get('identityKeyString'),
publicKey: prekey.get('publicKey'),
signature: prekey.get('signature'),
created_at: prekey.get('created_at'),
keyId: prekey.get('keyId'),
confirmed: prekey.get('confirmed'),
}))
);
}).fail(e => {
window.log.error('Failed to fetch signed prekey with filters', filters);
reject(e);
});
contactSignedPreKeys
.fetchBy(filters)
.then(() => {
resolve(
contactSignedPreKeys.map(prekey => ({
id: prekey.get('id'),
identityKeyString: prekey.get('identityKeyString'),
publicKey: prekey.get('publicKey'),
signature: prekey.get('signature'),
created_at: prekey.get('created_at'),
keyId: prekey.get('keyId'),
confirmed: prekey.get('confirmed'),
}))
);
})
.fail(e => {
window.log.error(
'Failed to fetch signed prekey with filters',
filters
);
reject(e);
});
});
},
loadContactSignedPreKey(pubKey) {

@ -181,7 +181,7 @@
showFriendRequest({ pubKey, message, accept, decline }) {
const dialog = new Whisper.ConfirmationDialogView({
title: `${pubKey} sent you a friend request:`,
message: message,
message,
okText: 'Accept',
cancelText: 'Decline',
resolve: accept,

@ -136,7 +136,7 @@
this.lazyUpdateVerified = _.debounce(
this.model.updateVerified.bind(this.model),
1000 // one second
);
);
this.throttledGetProfiles = _.throttle(
this.model.getProfiles.bind(this.model),
1000 * 60 * 5 // five minutes
@ -168,7 +168,7 @@
color: this.model.getColor(),
avatarPath: this.model.getAvatarPath(),
isVerified: this.model.isVerified(),
isKeysPending: this.model.isKeyExchangeCompleted() == false,
isKeysPending: this.model.isKeyExchangeCompleted() === false,
isMe: this.model.isMe(),
isGroup: !this.model.isPrivate(),
expirationSettingName,
@ -286,7 +286,9 @@
},
onDisableInput(disable) {
this.$('button.emoji, button.microphone, button.paperclip, .send-message').attr('disabled', disable);
this.$(
'button.emoji, button.microphone, button.paperclip, .send-message'
).attr('disabled', disable);
},
onChangePlaceholder(type) {

@ -5,6 +5,7 @@
/* global Whisper: false */
/* global textsecure: false */
/* global Signal: false */
/* global StringView: false */
// eslint-disable-next-line func-names
(function() {
@ -182,7 +183,7 @@
selectAContact: i18n('selectAContact'),
searchForPeopleOrGroups: i18n('searchForPeopleOrGroups'),
settings: i18n('settings'),
identityKey: StringView.arrayBufferToHex(identityKey)
identityKey: StringView.arrayBufferToHex(identityKey),
};
},
events: {

@ -26,12 +26,13 @@
this.$('#error').hide();
window.mnemonic.get_languages().forEach(language => {
this.$('#mnemonic-language').append($('<option>', {
value: language,
text: language.charAt(0).toUpperCase() + language.slice(1),
}));
this.$('#mnemonic-language').append(
$('<option>', {
value: language,
text: language.charAt(0).toUpperCase() + language.slice(1),
})
);
});
},
events: {
'validation input.number': 'onValidation',
@ -44,7 +45,10 @@
},
register() {
this.accountManager
.registerSingleDevice(this.$('#mnemonic').val(), this.$('#mnemonic-language').val())
.registerSingleDevice(
this.$('#mnemonic').val(),
this.$('#mnemonic-language').val()
)
.then(() => {
this.$el.trigger('openInbox');
})

@ -1,4 +1,4 @@
/* global window, libsignal, textsecure, OutgoingMessage */
/* global window, libsignal, textsecure, StringView, log */
// eslint-disable-next-line func-names
(function() {
@ -8,58 +8,70 @@
const IV_LENGTH = 16;
FallBackSessionCipher = function (address) {
function FallBackSessionCipher(address) {
this.identityKeyString = address.getName();
this.pubKey = StringView.hexToArrayBuffer(address.getName());
this.encrypt = async (plaintext) => {
this.encrypt = async 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 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
);
ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
return {
type : 6, //friend request
body : ivAndCiphertext,
registrationId : null
type: 6, // friend request
body: ivAndCiphertext,
registrationId: null,
};
},
this.decrypt = async (ivAndCiphertext) => {
};
this.decrypt = async 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.`
);
}
}
};
}
getPreKeyBundleForNumber = async function(pubKey) {
async function getPreKeyBundleForNumber(pubKey) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
const identityKey = myKeyPair.pubKey;
// Retrieve ids. The ids stored are always the latest generated + 1
const signedKeyId = textsecure.storage.get('signedKeyId', 1) - 1;
const [signedKey, preKey] = await Promise.all([
textsecure.storage.protocol.loadSignedPreKey(signedKeyId),
new Promise(async (resolve, reject) => {
new Promise(async resolve => {
// retrieve existing prekey if we already generated one for that recipient
const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContactIdentityKeyString(pubKey);
const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContactIdentityKeyString(
pubKey
);
if (storedPreKey) {
resolve({ pubKey: storedPreKey.pubKey, keyId: storedPreKey.keyId });
} else {
@ -67,17 +79,21 @@
const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
textsecure.storage.put('maxPreKeyId', preKeyId + 1);
const preKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
await textsecure.storage.protocol.storePreKey(preKey.keyId, preKey.keyPair, pubKey);
await textsecure.storage.protocol.storePreKey(
preKey.keyId,
preKey.keyPair,
pubKey
);
resolve({ pubKey: preKey.keyPair.pubKey, keyId: preKeyId });
}
})
}),
]);
const preKeyMessage = new textsecure.protobuf.PreKeyBundleMessage({
identityKey: new Uint8Array(identityKey),
deviceId: 1, // TODO: fetch from somewhere
preKeyId: preKey.keyId,
signedKeyId,
deviceId: 1, // TODO: fetch from somewhere
preKeyId: preKey.keyId,
signedKeyId,
preKey: new Uint8Array(preKey.pubKey),
signedKey: new Uint8Array(signedKey.pubKey),
signature: new Uint8Array(signedKey.signature),
@ -86,30 +102,52 @@
return preKeyMessage;
}
savePreKeyBundleForNumber = async function({ pubKey, preKeyId, preKey, signedKeyId, signedKey, signature }) {
const signedKeyPromise = new Promise(async (resolve) => {
const existingSignedKeys = await textsecure.storage.protocol.loadContactSignedPreKeys({ identityKeyString: pubKey, keyId: signedKeyId });
if (!existingSignedKeys || (existingSignedKeys instanceof Array && existingSignedKeys.length == 0))
{
async function savePreKeyBundleForNumber({
pubKey,
preKeyId,
preKey,
signedKeyId,
signedKey,
signature,
}) {
const signedKeyPromise = new Promise(async resolve => {
const existingSignedKeys = await textsecure.storage.protocol.loadContactSignedPreKeys(
{ identityKeyString: pubKey, keyId: signedKeyId }
);
if (
!existingSignedKeys ||
(existingSignedKeys instanceof Array && existingSignedKeys.length === 0)
) {
const signedPreKey = {
keyId: signedKeyId,
publicKey: signedKey,
signature,
};
await textsecure.storage.protocol.storeContactSignedPreKey(pubKey, signedPreKey);
await textsecure.storage.protocol.storeContactSignedPreKey(
pubKey,
signedPreKey
);
}
resolve();
});
const preKeyPromise = new Promise(async (resolve) => {
const existingPreKeys = textsecure.storage.protocol.loadContactPreKeys({ identityKeyString: pubKey, keyId: preKeyId });
if (!existingPreKeys || (existingPreKeys instanceof Array && existingPreKeys.length == 0))
{
const preKeyPromise = new Promise(async resolve => {
const existingPreKeys = textsecure.storage.protocol.loadContactPreKeys({
identityKeyString: pubKey,
keyId: preKeyId,
});
if (
!existingPreKeys ||
(existingPreKeys instanceof Array && existingPreKeys.length === 0)
) {
const preKeyObject = {
publicKey: preKey,
keyId: preKeyId,
}
await textsecure.storage.protocol.storeContactPreKey(pubKey, preKeyObject);
};
await textsecure.storage.protocol.storeContactPreKey(
pubKey,
preKeyObject
);
}
resolve();
});
@ -117,34 +155,33 @@
await Promise.all([signedKeyPromise, preKeyPromise]);
}
sendEmptyMessageWithPreKeys = async function(pubKey) {
async function sendEmptyMessageWithPreKeys(pubKey) {
// empty content message
const content = new textsecure.protobuf.Content();
// will be called once the transmission succeeded or failed
const callback = res => {
if (res.errors.length > 0) {
res.errors.forEach(error => console.error(error));
res.errors.forEach(error => log.error(error));
} else {
console.log('empty message sent successfully');
log.notice('empty message sent successfully');
}
};
// send an empty message. The logic in ougoing_message will attach the prekeys.
const outgoingMessage = new textsecure.OutgoingMessage(
null, //server
Date.now(), //timestamp,
[pubKey], //numbers
content, //message
true, //silent
callback, //callback
);
null, // server
Date.now(), // timestamp,
[pubKey], // numbers
content, // message
true, // silent
callback // callback
);
await outgoingMessage.sendToNumber(pubKey);
}
window.libloki.FallBackSessionCipher = FallBackSessionCipher;
window.libloki.getPreKeyBundleForNumber = getPreKeyBundleForNumber;
window.libloki.FallBackDecryptionError = FallBackDecryptionError;
window.libloki.savePreKeyBundleForNumber = savePreKeyBundleForNumber;
window.libloki.sendEmptyMessageWithPreKeys = sendEmptyMessageWithPreKeys;
})();
})();

@ -10,28 +10,26 @@ module.exports = {
class MnemonicError extends Error {}
function hexToUint8Array(e) {
if (e.length % 2 != 0)
throw "Hex string has invalid length!";
if (e.length % 2 != 0) throw 'Hex string has invalid length!';
for (var t = new Uint8Array(e.length / 2), r = 0; r < e.length / 2; ++r)
t[r] = parseInt(e.slice(2 * r, 2 * r + 2), 16);
return t
return t;
}
function Uint8ArrayToHex(e) {
for (var t = [], r = 0; r < e.length; ++r)
t.push(("0" + e[r].toString(16)).slice(-2));
return t.join("")
t.push(('0' + e[r].toString(16)).slice(-2));
return t.join('');
}
function sc_reduce32(e) {
var t = hexToUint8Array(e);
if (32 !== t.length)
throw "Invalid input length";
if (32 !== t.length) throw 'Invalid input length';
var r = sc_reduce32_module._malloc(32);
sc_reduce32_module.HEAPU8.set(t, r),
sc_reduce32_module.ccall("sc_reduce32", "void", ["number"], [r]);
sc_reduce32_module.ccall('sc_reduce32', 'void', ['number'], [r]);
var o = sc_reduce32_module.HEAPU8.subarray(r, r + 32);
return sc_reduce32_module._free(r),Uint8ArrayToHex(o);
return sc_reduce32_module._free(r), Uint8ArrayToHex(o);
}
/*
mnemonic.js : Converts between 4-byte aligned strings and a human-readable
@ -44,41 +42,45 @@ function sc_reduce32(e) {
var mn_default_wordset = 'english';
function mn_get_checksum_index(words, prefix_len) {
var trimmed_words = "";
for (var i = 0; i < words.length; i++) {
trimmed_words += words[i].slice(0, prefix_len);
}
var checksum = crc32.unsigned(trimmed_words);
var index = checksum % words.length;
return index;
var trimmed_words = '';
for (var i = 0; i < words.length; i++) {
trimmed_words += words[i].slice(0, prefix_len);
}
var checksum = crc32.unsigned(trimmed_words);
var index = checksum % words.length;
return index;
}
function mn_encode(str, wordset_name) {
'use strict';
wordset_name = wordset_name || mn_default_wordset;
var wordset = mn_words[wordset_name];
var out = [];
var n = wordset.words.length;
for (var j = 0; j < str.length; j += 8) {
str = str.slice(0, j) + mn_swap_endian_4byte(str.slice(j, j + 8)) + str.slice(j + 8);
}
for (var i = 0; i < str.length; i += 8) {
var x = parseInt(str.substr(i, 8), 16);
var w1 = (x % n);
var w2 = (Math.floor(x / n) + w1) % n;
var w3 = (Math.floor(Math.floor(x / n) / n) + w2) % n;
out = out.concat([wordset.words[w1], wordset.words[w2], wordset.words[w3]]);
}
if (wordset.prefix_len > 0) {
out.push(out[mn_get_checksum_index(out, wordset.prefix_len)]);
}
return out.join(' ');
'use strict';
wordset_name = wordset_name || mn_default_wordset;
var wordset = mn_words[wordset_name];
var out = [];
var n = wordset.words.length;
for (var j = 0; j < str.length; j += 8) {
str =
str.slice(0, j) +
mn_swap_endian_4byte(str.slice(j, j + 8)) +
str.slice(j + 8);
}
for (var i = 0; i < str.length; i += 8) {
var x = parseInt(str.substr(i, 8), 16);
var w1 = x % n;
var w2 = (Math.floor(x / n) + w1) % n;
var w3 = (Math.floor(Math.floor(x / n) / n) + w2) % n;
out = out.concat([wordset.words[w1], wordset.words[w2], wordset.words[w3]]);
}
if (wordset.prefix_len > 0) {
out.push(out[mn_get_checksum_index(out, wordset.prefix_len)]);
}
return out.join(' ');
}
function mn_swap_endian_4byte(str) {
'use strict';
if (str.length !== 8) throw new MnemonicError('Invalid input length: ' + str.length);
return str.slice(6, 8) + str.slice(4, 6) + str.slice(2, 4) + str.slice(0, 2);
'use strict';
if (str.length !== 8)
throw new MnemonicError('Invalid input length: ' + str.length);
return str.slice(6, 8) + str.slice(4, 6) + str.slice(2, 4) + str.slice(0, 2);
}
function mn_decode(str, wordset_name) {
@ -89,10 +91,17 @@ function mn_decode(str, wordset_name) {
var n = wordset.words.length;
var wlist = str.split(' ');
var checksum_word = '';
if (wlist.length < 12) throw new MnemonicError("You've entered too few words, please try again");
if ((wordset.prefix_len === 0 && (wlist.length % 3 !== 0)) ||
(wordset.prefix_len > 0 && (wlist.length % 3 === 2))) throw new MnemonicError("You've entered too few words, please try again");
if (wordset.prefix_len > 0 && (wlist.length % 3 === 0)) throw new MnemonicError("You seem to be missing the last word in your private key, please try again");
if (wlist.length < 12)
throw new MnemonicError("You've entered too few words, please try again");
if (
(wordset.prefix_len === 0 && wlist.length % 3 !== 0) ||
(wordset.prefix_len > 0 && wlist.length % 3 === 2)
)
throw new MnemonicError("You've entered too few words, please try again");
if (wordset.prefix_len > 0 && wlist.length % 3 === 0)
throw new MnemonicError(
'You seem to be missing the last word in your private key, please try again'
);
if (wordset.prefix_len > 0) {
// Pop checksum from mnemonic
checksum_word = wlist.pop();
@ -106,48 +115,60 @@ function mn_decode(str, wordset_name) {
w3 = wordset.words.indexOf(wlist[i + 2]);
} else {
w1 = wordset.trunc_words.indexOf(wlist[i].slice(0, wordset.prefix_len));
w2 = wordset.trunc_words.indexOf(wlist[i + 1].slice(0, wordset.prefix_len));
w3 = wordset.trunc_words.indexOf(wlist[i + 2].slice(0, wordset.prefix_len));
w2 = wordset.trunc_words.indexOf(
wlist[i + 1].slice(0, wordset.prefix_len)
);
w3 = wordset.trunc_words.indexOf(
wlist[i + 2].slice(0, wordset.prefix_len)
);
}
if (w1 === -1 || w2 === -1 || w3 === -1) {
throw MnemonicError("invalid word in mnemonic");
throw MnemonicError('invalid word in mnemonic');
}
var x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n);
if (x % n != w1) throw new MnemonicError('Something went wrong when decoding your private key, please try again');
var x = w1 + n * ((n - w1 + w2) % n) + n * n * ((n - w2 + w3) % n);
if (x % n != w1)
throw new MnemonicError(
'Something went wrong when decoding your private key, please try again'
);
out += mn_swap_endian_4byte(('0000000' + x.toString(16)).slice(-8));
}
// Verify checksum
if (wordset.prefix_len > 0) {
var index = mn_get_checksum_index(wlist, wordset.prefix_len);
var expected_checksum_word = wlist[index];
if (expected_checksum_word.slice(0, wordset.prefix_len) !== checksum_word.slice(0, wordset.prefix_len)) {
throw new MnemonicError("Your private key could not be verified, please try again");
if (
expected_checksum_word.slice(0, wordset.prefix_len) !==
checksum_word.slice(0, wordset.prefix_len)
) {
throw new MnemonicError(
'Your private key could not be verified, please try again'
);
}
}
return out;
}
var mn_words = {
'english': {
english: {
prefix_len: 3,
words: require('../mnemonic_languages/english'),
},
'electrum': {
electrum: {
prefix_len: 0,
words: require('../mnemonic_languages/electrum'),
},
'spanish': {
spanish: {
prefix_len: 4,
words: require('../mnemonic_languages/spanish'),
},
'portuguese': {
portuguese: {
prefix_len: 4,
words: require('../mnemonic_languages/portuguese'),
},
'japanese': {
japanese: {
prefix_len: 3,
words: require('../mnemonic_languages/japanese'),
}
},
};
function get_languages() {
@ -161,7 +182,9 @@ for (var i in mn_words) {
}
mn_words[i].trunc_words = [];
for (var j = 0; j < mn_words[i].words.length; ++j) {
mn_words[i].trunc_words.push(mn_words[i].words[j].slice(0, mn_words[i].prefix_len));
mn_words[i].trunc_words.push(
mn_words[i].words[j].slice(0, mn_words[i].prefix_len)
);
}
}
}

@ -22,8 +22,8 @@ function incrementNonce(nonce) {
// Convert a Uint8Array to a base64 string
function bufferToBase64(buf) {
function mapFn(ch) {
return String.fromCharCode(ch);
};
return String.fromCharCode(ch);
}
const binaryString = Array.prototype.map.call(buf, mapFn).join('');
return bb.btoa(binaryString);
}
@ -36,7 +36,9 @@ function bigIntToUint8Array(bigInt) {
n = NONCE_LEN - (idx + 1);
// 256 ** n is the value of one bit in arr[idx], modulus to carry over
// (bigInt / 256**n) % 256;
const uint8Val = (bigInt.divide((new BigInteger('256')).pow(n))).mod(new BigInteger('256'));
const uint8Val = bigInt
.divide(new BigInteger('256').pow(n))
.mod(new BigInteger('256'));
arr[idx] = uint8Val.intValue();
}
return arr;
@ -45,14 +47,11 @@ function bigIntToUint8Array(bigInt) {
// Compare two Uint8Arrays, return true if arr1 is > arr2
function greaterThan(arr1, arr2) {
// Early exit if lengths are not equal. Should never happen
if (arr1.length !== arr2.length)
return false;
if (arr1.length !== arr2.length) return false;
for (let i = 0, len = arr1.length; i < len; i += 1) {
if (arr1[i] > arr2[i])
return true;
if (arr1[i] < arr2[i])
return false;
if (arr1[i] > arr2[i]) return true;
if (arr1[i] < arr2[i]) return false;
}
return false;
}
@ -60,7 +59,9 @@ function greaterThan(arr1, arr2) {
// Return nonce that hashes together with payload lower than the target
function calcPoW(timestamp, ttl, pubKey, data) {
const leadingString = timestamp.toString() + ttl.toString() + pubKey;
const leadingArray = new Uint8Array(bb.wrap(leadingString, 'binary').toArrayBuffer());
const leadingArray = new Uint8Array(
bb.wrap(leadingString, 'binary').toArrayBuffer()
);
// Payload constructed from concatenating timestamp, ttl and pubkey strings,
// converting to Uint8Array and then appending to the message data array
const payload = new Uint8Array(leadingArray.length + data.length);
@ -68,37 +69,54 @@ function calcPoW(timestamp, ttl, pubKey, data) {
payload.set(data, leadingArray.length);
// payloadLength + NONCE_LEN
const totalLen = (new BigInteger(payload.length.toString())).add(new BigInteger(NONCE_LEN.toString()));
const totalLen = new BigInteger(payload.length.toString()).add(
new BigInteger(NONCE_LEN.toString())
);
// ttl * totalLen
const ttlMult = (new BigInteger(ttl.toString())).multiply(totalLen);
const ttlMult = new BigInteger(ttl.toString()).multiply(totalLen);
// ttlMult / (2^16 - 1)
const innerFrac = ttlMult.divide((new BigInteger('2').pow(16)).subtract(new BigInteger('1')));
const innerFrac = ttlMult.divide(
new BigInteger('2').pow(16).subtract(new BigInteger('1'))
);
// totalLen + innerFrac
const lenPlusInnerFrac = totalLen.add(innerFrac);
// NONCE_TRIALS * lenPlusInnerFrac
const denominator = (new BigInteger(NONCE_TRIALS.toString())).multiply(lenPlusInnerFrac);
const denominator = new BigInteger(NONCE_TRIALS.toString()).multiply(
lenPlusInnerFrac
);
// 2^64 - 1
const two64 = (new BigInteger('2').pow(64)).subtract(new BigInteger('1'));
const two64 = new BigInteger('2').pow(64).subtract(new BigInteger('1'));
// two64 / denominator
const targetNum = two64.divide(denominator);
const target = bigIntToUint8Array(targetNum);
let nonce = new Uint8Array(NONCE_LEN);
let trialValue = bigIntToUint8Array(new BigInteger(Number.MAX_SAFE_INTEGER.toString()));
const initialHash = new Uint8Array(bb.wrap(hash(payload), 'hex').toArrayBuffer());
let trialValue = bigIntToUint8Array(
new BigInteger(Number.MAX_SAFE_INTEGER.toString())
);
const initialHash = new Uint8Array(
bb.wrap(hash(payload), 'hex').toArrayBuffer()
);
const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
innerPayload.set(initialHash, NONCE_LEN);
let resultHash;
while (greaterThan(trialValue, target)) {
nonce = incrementNonce(nonce);
innerPayload.set(nonce);
resultHash = hash(innerPayload);
trialValue = (new Uint8Array(bb.wrap(resultHash, 'hex').toArrayBuffer())).slice(0, NONCE_LEN);
trialValue = new Uint8Array(
bb.wrap(hash(nonce + initialHash), 'hex').toArrayBuffer()
).slice(0, 8);
}
return bufferToBase64(nonce);
}
// Start calculation in child process when main process sends message data
process.on('message', (msg) => {
process.send({nonce: calcPoW(msg.timestamp, msg.ttl, msg.pubKey, new Uint8Array(msg.data))});
process.on('message', msg => {
process.send({
nonce: calcPoW(
msg.timestamp,
msg.ttl,
msg.pubKey,
new Uint8Array(msg.data)
),
});
});

@ -5,8 +5,10 @@
WebSocketResource,
btoa,
getString,
libphonenumber,
Event
Event,
dcodeIO,
StringView,
log,
*/
/* eslint-disable more/no-then */
@ -55,59 +57,77 @@
generateKeypair = () => {
const seedHex = window.mnemonic.mn_decode(mnemonic, mnemonicLanguage);
const privKeyHex = window.mnemonic.sc_reduce32(seedHex);
const privKey = dcodeIO.ByteBuffer.wrap(privKeyHex, 'hex').toArrayBuffer();
const privKey = dcodeIO.ByteBuffer.wrap(
privKeyHex,
'hex'
).toArrayBuffer();
return libsignal.Curve.async.createKeyPair(privKey);
};
} else {
generateKeypair = libsignal.KeyHelper.generateIdentityKeyPair;
}
return this.queueTask(() =>
generateKeypair().then(identityKeyPair => {
return createAccount(
identityKeyPair,
)
return this.queueTask(() =>
generateKeypair().then(identityKeyPair =>
createAccount(identityKeyPair)
.then(clearSessionsAndPreKeys)
.then(generateKeys)
.then(keys => confirmKeys(keys))
.then(registrationDone);
})
.then(registrationDone)
)
);
},
async addMockContact(doSave) {
if (doSave === undefined) {
// eslint-disable-next-line no-param-reassign
doSave = true;
}
const keyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
const pubKey = StringView.arrayBufferToHex(keyPair.pubKey);
const privKey = StringView.arrayBufferToHex(keyPair.privKey);
log.info('contact pubkey ' + pubKey);
log.info('contact privkey ' + privKey);
const signedKeyId = Math.floor((Math.random() * 1000) + 1);
log.info(`contact pubkey ${pubKey}`);
log.info(`contact privkey ${privKey}`);
const signedKeyId = Math.floor(Math.random() * 1000 + 1);
const signedPreKey = await libsignal.KeyHelper.generateSignedPreKey(keyPair, signedKeyId);
const signedPreKey = await libsignal.KeyHelper.generateSignedPreKey(
keyPair,
signedKeyId
);
const contactSignedPreKey = {
publicKey: signedPreKey.keyPair.pubKey,
signature: signedPreKey.signature,
keyId: signedPreKey.keyId
keyId: signedPreKey.keyId,
};
if (doSave) {
await textsecure.storage.protocol.storeContactSignedPreKey(pubKey, contactSignedPreKey);
}
else {
log.info('signed prekey: ' + StringView.arrayBufferToHex(contactSignedPreKey.publicKey));
log.info('signature: ' + StringView.arrayBufferToHex(contactSignedPreKey.signature));
await textsecure.storage.protocol.storeContactSignedPreKey(
pubKey,
contactSignedPreKey
);
} else {
log.info(
`signed prekey:
${StringView.arrayBufferToHex(contactSignedPreKey.publicKey)}`
);
log.info(
`signature:
${StringView.arrayBufferToHex(contactSignedPreKey.signature)}`
);
}
for (let keyId = 0; keyId < 10; keyId += 1) {
const preKey = await libsignal.KeyHelper.generatePreKey(keyId);
if (doSave) {
await textsecure.storage.protocol.storeContactPreKey(pubKey, { publicKey: preKey.keyPair.pubKey, keyId: keyId });
}
else {
log.info('signed prekey: ' + StringView.arrayBufferToHex(preKey.keyPair.pubKey));
await textsecure.storage.protocol.storeContactPreKey(pubKey, {
publicKey: preKey.keyPair.pubKey,
keyId,
});
} else {
log.info(
`signed prekey:
${StringView.arrayBufferToHex(preKey.keyPair.pubKey)}`
);
}
}
log.info("Added mock contact")
log.info('Added mock contact');
},
registerSecondDevice(setProvisioningUrl, confirmNumber, progressCallback) {
const createAccount = this.createAccount.bind(this);
@ -225,7 +245,7 @@
}
const store = textsecure.storage.protocol;
const { server, cleanSignedPreKeys } = this;
const { cleanSignedPreKeys } = this;
// TODO: harden this against missing identity key? Otherwise, we get
// retries every five seconds.
@ -250,14 +270,24 @@
window.log.info('Saving new signed prekey', res.keyId);
return Promise.all([
textsecure.storage.put('signedKeyId', signedKeyId + 1),
store.storeSignedPreKey(res.keyId, res.keyPair, undefined, res.signature),
store.storeSignedPreKey(
res.keyId,
res.keyPair,
undefined,
res.signature
),
])
.then(() => {
const confirmed = true;
window.log.info('Confirming new signed prekey', res.keyId);
return Promise.all([
textsecure.storage.remove('signedKeyRotationRejected'),
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed, res.signature),
store.storeSignedPreKey(
res.keyId,
res.keyPair,
confirmed,
res.signature
),
]);
})
.then(() => cleanSignedPreKeys());
@ -359,47 +389,41 @@
});
});
},
createAccount(
identityKeyPair,
userAgent,
readReceipts
) {
return Promise.resolve()
.then(response => {
textsecure.storage.remove('identityKey');
textsecure.storage.remove('number_id');
textsecure.storage.remove('device_name');
textsecure.storage.remove('userAgent');
textsecure.storage.remove('read-receipts-setting');
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
const pubKeyString = StringView.arrayBufferToHex(identityKeyPair.pubKey);
textsecure.storage.protocol.saveIdentityWithAttributes(pubKeyString, {
id: pubKeyString,
publicKey: identityKeyPair.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED,
nonblockingApproval: true,
});
textsecure.storage.put('identityKey', identityKeyPair);
if (userAgent) {
textsecure.storage.put('userAgent', userAgent);
}
if (readReceipts) {
textsecure.storage.put('read-receipt-setting', true);
} else {
textsecure.storage.put('read-receipt-setting', false);
}
createAccount(identityKeyPair, userAgent, readReceipts) {
return Promise.resolve().then(() => {
textsecure.storage.remove('identityKey');
textsecure.storage.remove('number_id');
textsecure.storage.remove('device_name');
textsecure.storage.remove('userAgent');
textsecure.storage.remove('read-receipts-setting');
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
const pubKeyString = StringView.arrayBufferToHex(
identityKeyPair.pubKey
);
textsecure.storage.user.setNumberAndDeviceId(
pubKeyString,
1,
);
textsecure.storage.protocol.saveIdentityWithAttributes(pubKeyString, {
id: pubKeyString,
publicKey: identityKeyPair.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED,
nonblockingApproval: true,
});
textsecure.storage.put('identityKey', identityKeyPair);
if (userAgent) {
textsecure.storage.put('userAgent', userAgent);
}
if (readReceipts) {
textsecure.storage.put('read-receipt-setting', true);
} else {
textsecure.storage.put('read-receipt-setting', false);
}
textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1);
});
},
clearSessionsAndPreKeys() {
const store = textsecure.storage.protocol;
@ -418,7 +442,12 @@
const confirmed = true;
window.log.info('confirmKeys: confirming key', key.keyId);
return store.storeSignedPreKey(key.keyId, key.keyPair, confirmed, key.signature);
return store.storeSignedPreKey(
key.keyId,
key.keyPair,
confirmed,
key.signature
);
},
generateKeys(count, providedProgressCallback) {
const progressCallback =
@ -460,7 +489,12 @@
identityKey,
signedKeyId
).then(res => {
store.storeSignedPreKey(res.keyId, res.keyPair, undefined, res.signature);
store.storeSignedPreKey(
res.keyId,
res.keyPair,
undefined,
res.signature
);
result.signedPreKey = {
keyId: res.keyId,
publicKey: res.keyPair.pubKey,

@ -441,7 +441,7 @@ MessageReceiver.prototype.extend({
getEnvelopeId(envelope) {
return `${envelope.source}.${
envelope.sourceDevice
} ${envelope.timestamp.toNumber()}`;
} ${envelope.timestamp.toNumber()}`;
},
async getAllFromCache() {
window.log.info('getAllFromCache');
@ -652,13 +652,13 @@ MessageReceiver.prototype.extend({
switch (envelope.type) {
case textsecure.protobuf.Envelope.Type.CIPHERTEXT:
window.log.info('message from', this.getEnvelopeId(envelope));
promise = Promise.resolve(ciphertext.toArrayBuffer())//;sessionCipher
promise = Promise.resolve(ciphertext.toArrayBuffer()); //;sessionCipher
// TODO: restore decryption & unpadding (?)
//.decryptWhisperMessage(ciphertext)
//.then(this.unpad);
break;
case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST:
window.log.info('friend-request message from ', envelope.source)
window.log.info('friend-request message from ', envelope.source);
const fallBackSessionCipher = new libloki.FallBackSessionCipher(
address
);
@ -752,7 +752,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)) {
@ -793,7 +793,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)) {
@ -828,16 +828,16 @@ MessageReceiver.prototype.extend({
return this.handleDataMessage(envelope, message);
},
handleContentMessage(envelope) {
return this.decrypt(envelope, envelope.content).then(plaintext =>
this.innerHandleContentMessage(envelope, plaintext)
).catch(e => {
if (e instanceof libloki.FallBackDecryptionError) {
console.log(e.message + ' Ignoring message.');
}
});
return this.decrypt(envelope, envelope.content)
.then(plaintext => this.innerHandleContentMessage(envelope, plaintext))
.catch(e => {
if (e instanceof libloki.FallBackDecryptionError) {
console.log(e.message + ' Ignoring message.');
}
});
},
async promptUserToAcceptFriendRequest(pubKey, message) {
pubKey = pubKey.slice(0, 30) + "...";
pubKey = pubKey.slice(0, 30) + '...';
let p = new Promise(resolve => {
window.Whisper.events.trigger('showFriendRequest', {
pubKey,
@ -847,7 +847,7 @@ MessageReceiver.prototype.extend({
},
decline: () => {
resolve(false);
}
},
});
});
return await p;
@ -860,10 +860,12 @@ MessageReceiver.prototype.extend({
let conversation;
try {
conversation = ConversationController.get(envelope.source);
} catch (e) {
}
} catch (e) {}
if (!conversation) {
const accepted = await this.promptUserToAcceptFriendRequest(envelope.source, content.dataMessage.body);
const accepted = await this.promptUserToAcceptFriendRequest(
envelope.source,
content.dataMessage.body
);
if (accepted) {
// send our own prekeys as a response - no need to wait
libloki.sendEmptyMessageWithPreKeys(envelope.source);
@ -875,7 +877,10 @@ MessageReceiver.prototype.extend({
}
if (content.preKeyBundleMessage) {
await this.handlePreKeyBundleMessage(envelope, content.preKeyBundleMessage);
await this.handlePreKeyBundleMessage(
envelope,
content.preKeyBundleMessage
);
}
if (content.syncMessage) {
@ -1100,17 +1105,18 @@ MessageReceiver.prototype.extend({
return this.removeFromCache(envelope);
},
async handlePreKeyBundleMessage(envelope, preKeyBundleMessage) {
const { preKeyId, signedKeyId } = preKeyBundleMessage;
const [identityKey, preKey, signedKey, signature] = [
preKeyBundleMessage.identityKey,
preKeyBundleMessage.preKey,
preKeyBundleMessage.signedKey,
preKeyBundleMessage.signature
preKeyBundleMessage.signature,
].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer());
if (envelope.source != StringView.arrayBufferToHex(identityKey)) {
throw new Error("Error in handlePreKeyBundleMessage: envelope pubkey does not match pubkey in prekey bundle");
throw new Error(
'Error in handlePreKeyBundleMessage: envelope pubkey does not match pubkey in prekey bundle'
);
}
const pubKey = envelope.source;

@ -1,4 +1,13 @@
/* global textsecure, libsignal, window, btoa, libloki */
/* global
textsecure,
libsignal,
window,
ConversationController,
libloki,
StringView,
dcodeIO,
log,
*/
/* eslint-disable more/no-then */
@ -23,7 +32,7 @@ function OutgoingMessage(
this.callback = callback;
this.silent = silent;
this.lokiserver = window.LokiAPI.connect()
this.lokiserver = window.LokiAPI.connect();
this.numbersCompleted = 0;
this.errors = [];
@ -64,6 +73,7 @@ OutgoingMessage.prototype = {
return () =>
textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
if (deviceIds.length === 0) {
// eslint-disable-next-line no-param-reassign
deviceIds = [1];
// return this.registerError(
// number,
@ -121,17 +131,21 @@ OutgoingMessage.prototype = {
promise = promise.then(() =>
Promise.all([
textsecure.storage.protocol.loadContactPreKey(number),
textsecure.storage.protocol.loadContactSignedPreKey(number)
]).then((keys) => {
const [preKey, signedPreKey] = keys;
if (preKey == undefined || signedPreKey == undefined) {
return false;
}
else {
textsecure.storage.protocol.loadContactSignedPreKey(number),
])
.then(keys => {
const [preKey, signedPreKey] = keys;
if (preKey === undefined || signedPreKey === undefined) {
return false;
}
const identityKey = StringView.hexToArrayBuffer(number);
return handleResult({ identityKey, devices: [{ deviceId: device, preKey, signedPreKey, registrationId: 0 }] })
}
})
return handleResult({
identityKey,
devices: [
{ deviceId: device, preKey, signedPreKey, registrationId: 0 },
],
});
})
.catch(e => {
if (e.name === 'HTTPError' && e.code === 404) {
if (device !== 1) {
@ -152,10 +166,9 @@ OutgoingMessage.prototype = {
async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60) {
const pubKey = number;
try {
const [response, status] = await this.lokiserver.sendMessage(pubKey, data, ttl);
const [response] = await this.lokiserver.sendMessage(pubKey, data, ttl);
return response;
}
catch (e) {
} catch (e) {
if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) {
// 409 and 410 should bubble and be handled by doSendMessage
// 404 should throw UnregisteredUserError
@ -163,12 +176,7 @@ OutgoingMessage.prototype = {
if (e.code === 404) {
throw new textsecure.UnregisteredUserError(number, e);
}
throw new textsecure.SendMessageNetworkError(
number,
"",
e,
timestamp
);
throw new textsecure.SendMessageNetworkError(number, '', e, timestamp);
}
throw e;
}
@ -194,7 +202,7 @@ OutgoingMessage.prototype = {
// this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
// );
this.plaintext.set(new Uint8Array(messageBuffer));
//this.plaintext[messageBuffer.byteLength] = 0x80;
// this.plaintext[messageBuffer.byteLength] = 0x80;
}
return this.plaintext;
},
@ -207,17 +215,17 @@ OutgoingMessage.prototype = {
content: outgoingObject.content,
});
const requestMessage = new textsecure.protobuf.WebSocketRequestMessage({
id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], // random ID for now
verb: 'PUT',
path: '/api/v1/message',
body: messageEnvelope.encode().toArrayBuffer()
id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], // random ID for now
verb: 'PUT',
path: '/api/v1/message',
body: messageEnvelope.encode().toArrayBuffer(),
});
const websocketMessage = new textsecure.protobuf.WebSocketMessage({
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
request: requestMessage
request: requestMessage,
});
const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer())
console.log(bytes.toString()); // print bytes for debugging purposes: can be injected in mock socket server
const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer());
log.info(bytes.toString()); // print bytes for debugging purposes: can be injected in mock socket server
return bytes;
},
doSendMessage(number, deviceIds, recurse) {
@ -238,9 +246,7 @@ OutgoingMessage.prototype = {
let sessionCipher;
if (this.fallBackEncryption) {
sessionCipher = new libloki.FallBackSessionCipher(
address
);
sessionCipher = new libloki.FallBackSessionCipher(address);
} else {
sessionCipher = new libsignal.SessionCipher(
textsecure.storage.protocol,
@ -249,17 +255,26 @@ OutgoingMessage.prototype = {
);
}
ciphers[address.getDeviceId()] = sessionCipher;
return sessionCipher.encrypt(plaintext).then(ciphertext => {
if (! this.fallBackEncryption)
ciphertext.body = new Uint8Array(dcodeIO.ByteBuffer.wrap(ciphertext.body,'binary').toArrayBuffer());
return ciphertext;
}).then(ciphertext => ({
type: ciphertext.type,
address: address,
destinationDeviceId: address.getDeviceId(),
destinationRegistrationId: ciphertext.registrationId,
content: ciphertext.body,
}));
return sessionCipher
.encrypt(plaintext)
.then(ciphertext => {
if (!this.fallBackEncryption)
// eslint-disable-next-line no-param-reassign
ciphertext.body = new Uint8Array(
dcodeIO.ByteBuffer.wrap(
ciphertext.body,
'binary'
).toArrayBuffer()
);
return ciphertext;
})
.then(ciphertext => ({
type: ciphertext.type,
address,
destinationDeviceId: address.getDeviceId(),
destinationRegistrationId: ciphertext.registrationId,
content: ciphertext.body,
}));
})
)
.then(async outgoingObjects => {
@ -268,7 +283,10 @@ OutgoingMessage.prototype = {
const socketMessage = await this.wrapInWebsocketMessage(outgoingObject);
let ttl;
// TODO: Allow user to set ttl manually
if (outgoingObject.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST) {
if (
outgoingObject.type ===
textsecure.protobuf.Envelope.Type.FRIEND_REQUEST
) {
ttl = 4 * 24 * 60 * 60; // 4 days for friend request message
} else {
ttl = 24 * 60 * 60; // 1 day default for any other message
@ -276,8 +294,7 @@ OutgoingMessage.prototype = {
await this.transmitMessage(number, socketMessage, this.timestamp, ttl);
this.successfulNumbers[this.successfulNumbers.length] = number;
this.numberCompleted();
}
)
})
.catch(error => {
if (
error instanceof Error &&
@ -372,31 +389,34 @@ OutgoingMessage.prototype = {
let conversation;
try {
conversation = ConversationController.get(number);
} catch(e) {
} catch (e) {
// do nothing
}
return this.getStaleDeviceIdsForNumber(number).then(updateDevices =>
this.getKeysForNumber(number, updateDevices)
.then(async (keysFound) => {
.then(async keysFound => {
let attachPrekeys = false;
if (!keysFound)
{
log.info("Fallback encryption enabled");
if (!keysFound) {
log.info('Fallback encryption enabled');
this.fallBackEncryption = true;
attachPrekeys = true;
} else if (conversation) {
try {
attachPrekeys = !conversation.isKeyExchangeCompleted();
} catch(e) {
} catch (e) {
// do nothing
}
}
if (attachPrekeys) {
log.info('attaching prekeys to outgoing message');
this.message.preKeyBundleMessage = await libloki.getPreKeyBundleForNumber(number);
this.message.preKeyBundleMessage = await libloki.getPreKeyBundleForNumber(
number
);
}
}).then(this.reloadDevicesAndSend(number, true))
})
.then(this.reloadDevicesAndSend(number, true))
.then(() => {
if (this.fallBackEncryption && conversation) {
conversation.onFriendRequestSent();

@ -100,13 +100,19 @@
},
arrayBufferToHex(aArrayBuffer) {
return Array.prototype.map.call(new Uint8Array(aArrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('');
return Array.prototype.map
.call(new Uint8Array(aArrayBuffer), x =>
('00' + x.toString(16)).slice(-2)
)
.join('');
},
hexToArrayBuffer(aString) {
return new Uint8Array(aString.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})).buffer
return new Uint8Array(
aString.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16);
})
).buffer;
},
};
})();

@ -3,4 +3,4 @@ websocket server for mocking up opening a connection to receive messages from th
run either server with
`sudo python3 <script>`
(sudo is required for port 80) but both can't be run at the same time.
(sudo is required for port 80) but both can't be run at the same time.

@ -48,6 +48,7 @@
"blob-util": "^1.3.0",
"blueimp-canvas-to-blob": "^3.14.0",
"blueimp-load-image": "^2.18.0",
"buffer-crc32": "^0.2.1",
"bunyan": "^1.8.12",
"bytebuffer": "^5.0.1",
"classnames": "^2.2.5",

@ -200,7 +200,9 @@ window.WebAPI = initializeWebAPI({
proxyUrl: config.proxyUrl,
});
const { initialize: initializeLokiAPI } = require('./js/modules/loki_message_api');
const {
initialize: initializeLokiAPI,
} = require('./js/modules/loki_message_api');
window.LokiAPI = initializeLokiAPI({
url: config.serverUrl,

@ -85,7 +85,14 @@ export class ConversationHeader extends React.Component<Props> {
}
public renderTitle() {
const { name, phoneNumber, i18n, profileName, isVerified, isKeysPending } = this.props;
const {
name,
phoneNumber,
i18n,
profileName,
isVerified,
isKeysPending,
} = this.props;
return (
<div className="module-conversation-header__title">

Loading…
Cancel
Save