369 lines
12 KiB
JavaScript
369 lines
12 KiB
JavaScript
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
const bb = require('bytebuffer');
|
|
const libsignal = require('libsignal');
|
|
const assert = require('assert');
|
|
const libloki_crypt = require('../../../dialects/transport/lib.loki_crypt');
|
|
const util = require('util');
|
|
const textEncoder = new util.TextEncoder();
|
|
|
|
const IV_LENGTH = 16;
|
|
|
|
const OPEN_SERVER_PUB_KEY_FILE = 'proxy.pub'
|
|
|
|
// load local key
|
|
if (!fs.existsSync(OPEN_SERVER_PUB_KEY_FILE)) {
|
|
console.log(OPEN_SERVER_PUB_KEY_FILE, 'is missing');
|
|
process.exit(1);
|
|
}
|
|
|
|
// load into buffers
|
|
const OpenServerPubKey = fs.readFileSync(OPEN_SERVER_PUB_KEY_FILE);
|
|
|
|
// functions specific to test
|
|
|
|
// no proxy for open groups
|
|
/*
|
|
// PROXY
|
|
const testSecureRpc = async (payloadObj, testInfo) => {
|
|
const payloadData = Buffer.from(
|
|
bb.wrap(JSON.stringify(payloadObj)).toArrayBuffer()
|
|
);
|
|
// test token endpoints
|
|
const ephemeralKey = libsignal.curve.generateKeyPair();
|
|
|
|
// mix server pub key with our priv key
|
|
const symKey = libsignal.curve.calculateAgreement(
|
|
OpenServerPubKey, // server's pubkey
|
|
ephemeralKey.privKey // our privkey
|
|
);
|
|
|
|
// make sym key
|
|
const cipherText64 = await libloki_crypt.DHEncrypt64(symKey, payloadData);
|
|
const result = await testInfo.overlayApi.serverRequest('loki/v1/secure_rpc', {
|
|
method: 'POST',
|
|
objBody: {
|
|
cipherText64
|
|
},
|
|
// out headers
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-loki-file-server-ephemeral-key': bb.wrap(ephemeralKey.pubKey).toString('base64'),
|
|
},
|
|
});
|
|
assert.equal(200, result.statusCode);
|
|
assert.ok(result.response);
|
|
assert.ok(result.response.meta);
|
|
assert.equal(200, result.response.meta.code);
|
|
assert.ok(result.response.data);
|
|
|
|
const ivAndCiphertextResponse = bb.wrap(result.response.data,'base64').toArrayBuffer();
|
|
|
|
const riv = Buffer.from(ivAndCiphertextResponse.slice(0, IV_LENGTH));
|
|
const rciphertext = Buffer.from(ivAndCiphertextResponse.slice(IV_LENGTH));
|
|
|
|
const decrypted = await libsignal.crypto.decrypt(
|
|
symKey,
|
|
rciphertext,
|
|
riv,
|
|
);
|
|
// not all results are json (/time /)
|
|
const str = decrypted.toString();
|
|
return str;
|
|
}
|
|
*/
|
|
|
|
// ONION
|
|
const testLsrpc = async (payloadObj, testInfo) => {
|
|
const payloadData = textEncoder.encode(JSON.stringify(payloadObj));
|
|
const ephemeralKey = libsignal.curve.generateKeyPair();
|
|
const symKey = libloki_crypt.makeSymmetricKey(
|
|
ephemeralKey.privKey, // our privkey
|
|
OpenServerPubKey, // server's pubkey
|
|
);
|
|
const cipherTextBuf = libloki_crypt.encryptGCM(symKey, payloadData);
|
|
const result = await testInfo.overlayApi.serverRequest('loki/v1/lsrpc', {
|
|
method: 'POST',
|
|
objBody: {
|
|
ciphertext: bb.wrap(cipherTextBuf).toString('base64'),
|
|
ephemeral_key: bb.wrap(ephemeralKey.pubKey).toString('hex')
|
|
},
|
|
noJson: true,
|
|
// out headers
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
assert.equal(200, result.statusCode);
|
|
assert.ok(result.response);
|
|
const nonceCiphertextAndTag = Buffer.from(
|
|
bb.wrap(result.response, 'base64').toArrayBuffer()
|
|
);
|
|
const decryptedJSON = libloki_crypt.decryptGCM(symKey, nonceCiphertextAndTag);
|
|
const obj = JSON.parse(decryptedJSON)
|
|
assert.equal(200, obj.status);
|
|
return obj;
|
|
}
|
|
|
|
module.exports = (testInfo) => {
|
|
it('server public key', async function() {
|
|
// test token endpoints
|
|
const result = await testInfo.overlayApi.serverRequest('loki/v1/public_key');
|
|
assert.equal(200, result.statusCode);
|
|
assert.ok(result.response);
|
|
assert.ok(result.response.meta);
|
|
assert.equal(200, result.response.meta.code);
|
|
assert.ok(result.response.data); // 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc'
|
|
});
|
|
|
|
// no proxy for open groups
|
|
// these can be enabled and will pass
|
|
// but I'd rather reflect responsibility of each subsystem/repo
|
|
// then to tie unit tests to where they're located
|
|
/*
|
|
describe('proxy tests', function() {
|
|
// no reason to test through a snode...
|
|
it('secure rpc homepage', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: '',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
it('secure rpc time', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
it('secure rpc rss', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'loki/v1/rss/messenger',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
it('secure rpc get users by id', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'users?include_user_annotations=1&ids=@053b0ff9567a9ae0c2c62d5c37eb065b766e18d90e1c92c5a4a1ee1ba8d235b26e',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const json = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(json);
|
|
const obj = JSON.parse(json);
|
|
assert.ok(obj);
|
|
assert.ok(obj.meta);
|
|
assert.equal(200, obj.meta.code);
|
|
assert.ok(obj.data);
|
|
});
|
|
// TODO:
|
|
// patch users/me
|
|
// file upload
|
|
// token exchange...
|
|
it('secure rpc get/submit challenge', async function() {
|
|
const ephemeralKey = libsignal.curve.generateKeyPair();
|
|
const getChalPayloadObj = {
|
|
// I think this is a stream, we may need to collect it all?
|
|
body: null,
|
|
endpoint: "loki/v1/get_challenge?pubKey=" + ephemeralKey.pubKey.toString('hex'),
|
|
method: "GET",
|
|
headers: {},
|
|
};
|
|
const json = await testSecureRpc(getChalPayloadObj, testInfo);
|
|
const response = JSON.parse(json);
|
|
assert.ok(response.cipherText64);
|
|
assert.ok(response.serverPubKey64);
|
|
// test b64 decode?
|
|
// that's why this next line kind of does...
|
|
const symmetricKey = libsignal.curve.calculateAgreement(
|
|
Buffer.from(response.serverPubKey64, 'base64'),
|
|
ephemeralKey.privKey
|
|
);
|
|
const token = await libloki_crypt.DHDecrypt64(symmetricKey, response.cipherText64);
|
|
const submitChalPayloadObj = {
|
|
// I think this is a stream, we may need to collect it all?
|
|
body: '{"pubKey":"' + ephemeralKey.pubKey.toString('hex') + '","token":"' + token + '"}',
|
|
endpoint: "loki/v1/submit_challenge",
|
|
method: "POST",
|
|
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
};
|
|
// will auto test the response enough
|
|
await testSecureRpc(submitChalPayloadObj, testInfo);
|
|
});
|
|
it('secure rpc missing header', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
it('secure rpc missing body', async function() {
|
|
const payloadObj = {
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
it('secure rpc missing method', async function() {
|
|
const payloadObj = {
|
|
endpoint: 'loki/v1/time',
|
|
body: {}, // might need to b64 if binary...
|
|
headers: {},
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
it('secure rpc missing body & header', async function() {
|
|
const payloadObj = {
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
};
|
|
const str = await testSecureRpc(payloadObj, testInfo);
|
|
assert.ok(str);
|
|
});
|
|
});
|
|
*/
|
|
|
|
// FIXME: make one test change that we can swap out the transport...
|
|
describe('onion request tests', function() {
|
|
it('lsrpc homepage', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: '',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
it('lsrpc time', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
it('lsrpc rss', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'loki/v1/rss/messenger',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
it('lsrpc get users by id', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'users?include_user_annotations=1&ids=@' + testInfo.ourPubKeyHex,
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
const obj = JSON.parse(resp.body);
|
|
assert.ok(obj);
|
|
assert.ok(obj.meta);
|
|
assert.equal(200, obj.meta.code);
|
|
assert.ok(obj.data);
|
|
});
|
|
it('lsrpc get/submit challenge', async function() {
|
|
const ephemeralKey = libsignal.curve.generateKeyPair();
|
|
// whitelist support for this test
|
|
if (testInfo.config.inWhiteListMode()) {
|
|
// need to allow this
|
|
var oldToken = testInfo.platformApi.token // backup
|
|
const modToken = await testInfo.selectModToken(testInfo.channelId);
|
|
testInfo.platformApi.token = modToken // switch to mod
|
|
const result = await testInfo.platformApi.serverRequest('loki/v1/moderation/whitelist/@' + ephemeralKey.pubKey.toString('hex'), {
|
|
method: 'POST',
|
|
});
|
|
assert.equal(200, result.statusCode);
|
|
testInfo.platformApi.token = oldToken // restore
|
|
}
|
|
const getChalPayloadObj = {
|
|
// I think this is a stream, we may need to collect it all?
|
|
body: null,
|
|
endpoint: "loki/v1/get_challenge?pubKey=" + ephemeralKey.pubKey.toString('hex'),
|
|
method: "GET",
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(getChalPayloadObj, testInfo);
|
|
const response = JSON.parse(resp.body);
|
|
assert.ok(response.cipherText64);
|
|
assert.ok(response.serverPubKey64);
|
|
// test b64 decode?
|
|
// that's why this next line kind of does...
|
|
const symmetricKey = libsignal.curve.calculateAgreement(
|
|
Buffer.from(response.serverPubKey64, 'base64'),
|
|
ephemeralKey.privKey
|
|
);
|
|
const token = await libloki_crypt.DHDecrypt64(symmetricKey, response.cipherText64);
|
|
const submitChalPayloadObj = {
|
|
// I think this is a stream, we may need to collect it all?
|
|
body: '{"pubKey":"' + ephemeralKey.pubKey.toString('hex') + '","token":"' + token + '"}',
|
|
endpoint: "loki/v1/submit_challenge",
|
|
method: "POST",
|
|
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
};
|
|
// will auto test the response enough
|
|
await testLsrpc(submitChalPayloadObj, testInfo);
|
|
});
|
|
it('lsrpc missing header', async function() {
|
|
const payloadObj = {
|
|
body: {}, // might need to b64 if binary...
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
it('lsrpc missing body', async function() {
|
|
const payloadObj = {
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
it('lsrpc missing method', async function() {
|
|
const payloadObj = {
|
|
endpoint: 'loki/v1/time',
|
|
body: {}, // might need to b64 if binary...
|
|
headers: {},
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
it('lsrpc missing body & header', async function() {
|
|
const payloadObj = {
|
|
endpoint: 'loki/v1/time',
|
|
method: 'GET',
|
|
};
|
|
const resp = await testLsrpc(payloadObj, testInfo);
|
|
assert.ok(resp.body);
|
|
});
|
|
});
|
|
}
|