use channel encryption with storage server

pull/213/head
sachaaaaa 6 years ago
parent 0500bb6f4f
commit aa722590fa

@ -1,11 +1,13 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable no-loop-func */ /* eslint-disable no-loop-func */
/* global log, dcodeIO, window, callWorker, lokiP2pAPI, lokiSnodeAPI */ /* global log, dcodeIO, window, callWorker, lokiP2pAPI, lokiSnodeAPI, libloki */
const nodeFetch = require('node-fetch'); const nodeFetch = require('node-fetch');
const _ = require('lodash'); const _ = require('lodash');
const { parse } = require('url');
const endpointBase = '/v1/storage_rpc'; const endpointBase = '/v1/storage_rpc';
const LOKI_EPHEMKEY_HEADER = 'X-Loki-EphemKey';
class HTTPError extends Error { class HTTPError extends Error {
constructor(response) { constructor(response) {
@ -27,6 +29,25 @@ const fetch = async (url, options = {}) => {
const timeout = options.timeout || 10000; const timeout = options.timeout || 10000;
const method = options.method || 'GET'; const method = options.method || 'GET';
const address = parse(url).hostname;
const doEncryptChannel =
address.endsWith('.snode') &&
options.headers &&
LOKI_EPHEMKEY_HEADER in options.headers;
if (doEncryptChannel) {
try {
// eslint-disable-next-line no-param-reassign
options.body = await libloki.crypto.snodeCipher.encrypt(
address,
options.body
);
// eslint-disable-next-line no-param-reassign
options.headers['Content-Type'] = 'text/plain';
} catch (e) {
log.warn(`Could not encrypt channel for ${address}: `, e);
}
}
try { try {
const response = await nodeFetch(url, { const response = await nodeFetch(url, {
...options, ...options,
@ -45,6 +66,18 @@ const fetch = async (url, options = {}) => {
result = await response.buffer(); result = await response.buffer();
} else { } else {
result = await response.text(); result = await response.text();
if (doEncryptChannel) {
try {
result = await libloki.crypto.snodeCipher.decrypt(address, result);
} catch (e) {
log.warn(`Could not decrypt response from ${address}`, e);
}
try {
result = JSON.parse(result);
} catch(e) {
log.warn(`Could not parse string to json ${result}`, e);
}
}
} }
return result; return result;
@ -151,7 +184,7 @@ class LokiMessageAPI {
method: 'POST', method: 'POST',
body: JSON.stringify(body), body: JSON.stringify(body),
headers: { headers: {
'X-Loki-EphemKey': 'not implemented yet', [LOKI_EPHEMKEY_HEADER]: libloki.crypto.snodeCipher.getChannelPublicKeyHex(),
}, },
}; };
@ -247,7 +280,7 @@ class LokiMessageAPI {
}, },
}; };
const headers = { const headers = {
'X-Loki-EphemKey': 'not implemented yet', [LOKI_EPHEMKEY_HEADER]: libloki.crypto.snodeCipher.getChannelPublicKeyHex(),
}; };
const fetchOptions = { const fetchOptions = {
method: 'POST', method: 'POST',

@ -1,4 +1,13 @@
/* global window, libsignal, textsecure, StringView, Multibase */ /* global
window,
libsignal,
textsecure,
StringView,
Multibase,
TextEncoder,
TextDecoder,
dcodeIO
*/
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function() {
@ -81,7 +90,7 @@
return ab; return ab;
} }
function decodeSnodeAddressToBuffer(snodeAddress) { function decodeSnodeAddressToPubKey(snodeAddress) {
const snodeAddressClean = snodeAddress const snodeAddressClean = snodeAddress
.replace('.snode', '') .replace('.snode', '')
.replace('http://', ''); .replace('http://', '');
@ -99,12 +108,16 @@
this._cache = {}; this._cache = {};
} }
_getSymmetricKey(snodeAddress) { async _getSymmetricKey(snodeAddress) {
if (snodeAddress in this._cache) { if (snodeAddress in this._cache) {
return this._cache[snodeAddress]; return this._cache[snodeAddress];
} }
const buffer = decodeSnodeAddressToBuffer(snodeAddress); const ed25519PubKey = decodeSnodeAddressToPubKey(snodeAddress);
const snodePubKeyArrayBuffer = bufferToArrayBuffer(buffer); const sodium = await window.getSodium();
const curve25519PubKey = sodium.crypto_sign_ed25519_pk_to_curve25519(
ed25519PubKey
);
const snodePubKeyArrayBuffer = bufferToArrayBuffer(curve25519PubKey);
const symmetricKey = libsignal.Curve.calculateAgreement( const symmetricKey = libsignal.Curve.calculateAgreement(
snodePubKeyArrayBuffer, snodePubKeyArrayBuffer,
this._ephemeralKeyPair.privKey this._ephemeralKeyPair.privKey
@ -117,18 +130,30 @@
return this._ephemeralPubKeyHex; return this._ephemeralPubKeyHex;
} }
async decrypt(snodeAddress, ivAndCipherText) { async decrypt(snodeAddress, ivAndCipherTextBase64) {
const symmetricKey = this._getSymmetricKey(snodeAddress); const ivAndCipherText = dcodeIO.ByteBuffer.wrap(
ivAndCipherTextBase64,
'base64'
).toArrayBuffer();
const symmetricKey = await this._getSymmetricKey(snodeAddress);
try { try {
return await DHDecrypt(symmetricKey, ivAndCipherText); const decrypted = await DHDecrypt(symmetricKey, ivAndCipherText);
const decoder = new TextDecoder();
return decoder.decode(decrypted);
} catch (e) { } catch (e) {
return ivAndCipherText; return ivAndCipherText;
} }
} }
async encrypt(snodeAddress, plainText) { async encrypt(snodeAddress, plainText) {
const symmetricKey = this._getSymmetricKey(snodeAddress); if (typeof plainText === 'string') {
return DHEncrypt(symmetricKey, plainText); const textEncoder = new TextEncoder();
// eslint-disable-next-line no-param-reassign
plainText = textEncoder.encode(plainText);
}
const symmetricKey = await this._getSymmetricKey(snodeAddress);
const cipherText = await DHEncrypt(symmetricKey, plainText);
return dcodeIO.ByteBuffer.wrap(cipherText).toString('base64');
} }
} }
@ -142,6 +167,6 @@
snodeCipher, snodeCipher,
// for testing // for testing
_LokiSnodeChannel: LokiSnodeChannel, _LokiSnodeChannel: LokiSnodeChannel,
_decodeSnodeAddressToBuffer: decodeSnodeAddressToBuffer, _decodeSnodeAddressToPubKey: decodeSnodeAddressToPubKey,
}; };
})(); })();

@ -77,6 +77,7 @@
"jquery": "3.3.1", "jquery": "3.3.1",
"js-sha512": "0.8.0", "js-sha512": "0.8.0",
"jsbn": "1.1.0", "jsbn": "1.1.0",
"libsodium-wrappers": "^0.7.4",
"linkify-it": "2.0.3", "linkify-it": "2.0.3",
"lodash": "4.17.11", "lodash": "4.17.11",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",

@ -339,6 +339,13 @@ window.React = require('react');
window.ReactDOM = require('react-dom'); window.ReactDOM = require('react-dom');
window.moment = require('moment'); window.moment = require('moment');
const _sodium = require('libsodium-wrappers');
window.getSodium = async () => {
await _sodium.ready;
return _sodium;
};
window.clipboard = clipboard; window.clipboard = clipboard;
const Signal = require('./js/modules/signal'); const Signal = require('./js/modules/signal');

Loading…
Cancel
Save