pubkeys-in-message-encryptor-init

pull/1234/head
Vincent 5 years ago
parent 646973e330
commit 68f2a90f7c

@ -2,6 +2,7 @@ import { EncryptionType } from '../types/EncryptionType';
import { SignalService } from '../../protobuf'; import { SignalService } from '../../protobuf';
import { UserUtil } from '../../util'; import { UserUtil } from '../../util';
import { CipherTextObject } from '../../../libtextsecure/libsignal-protocol'; import { CipherTextObject } from '../../../libtextsecure/libsignal-protocol';
import { PubKey } from '../types';
/** /**
* Add padding to a message buffer * Add padding to a message buffer
@ -31,13 +32,13 @@ function getPaddedMessageLength(originalLength: number): number {
/** /**
* Encrypt `plainTextBuffer` with given `encryptionType` for `device`. * Encrypt `plainTextBuffer` with given `encryptionType` for `device`.
* *
* @param device The device to encrypt for. * @param device The device `PubKey` to encrypt for.
* @param plainTextBuffer The unpadded plaintext buffer. * @param plainTextBuffer The unpadded plaintext buffer.
* @param encryptionType The type of encryption. * @param encryptionType The type of encryption.
* @returns The envelope type and the base64 encoded cipher text * @returns The envelope type and the base64 encoded cipher text
*/ */
export async function encrypt( export async function encrypt(
device: string, device: PubKey,
plainTextBuffer: Uint8Array, plainTextBuffer: Uint8Array,
encryptionType: EncryptionType encryptionType: EncryptionType
): Promise<{ ): Promise<{
@ -45,7 +46,7 @@ export async function encrypt(
cipherText: Uint8Array; cipherText: Uint8Array;
}> { }> {
const plainText = padPlainTextBuffer(plainTextBuffer); const plainText = padPlainTextBuffer(plainTextBuffer);
const address = new window.libsignal.SignalProtocolAddress(device, 1); const address = new window.libsignal.SignalProtocolAddress(device.key, 1);
if (encryptionType === EncryptionType.MediumGroup) { if (encryptionType === EncryptionType.MediumGroup) {
// TODO: Do medium group stuff here // TODO: Do medium group stuff here
@ -68,7 +69,7 @@ export async function encrypt(
} }
async function encryptUsingSealedSender( async function encryptUsingSealedSender(
device: string, device: PubKey,
innerCipherText: CipherTextObject innerCipherText: CipherTextObject
): Promise<{ ): Promise<{
envelopeType: SignalService.Envelope.Type; envelopeType: SignalService.Envelope.Type;
@ -88,7 +89,7 @@ async function encryptUsingSealedSender(
window.textsecure.storage.protocol window.textsecure.storage.protocol
); );
const cipherTextBuffer = await cipher.encrypt( const cipherTextBuffer = await cipher.encrypt(
device, device.key,
certificate, certificate,
innerCipherText innerCipherText
); );

@ -276,7 +276,7 @@ export class SessionProtocol {
} }
/** /**
* timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB * Timestamp undefined to remove the `key`/`value` pair, otherwise updates the processed timestamp and writes to database
*/ */
private static async updateProcessedSessionTimestamp( private static async updateProcessedSessionTimestamp(
device: string, device: string,

@ -6,6 +6,7 @@ import { SignalService } from '../../protobuf';
import { UserUtil } from '../../util'; import { UserUtil } from '../../util';
import { MessageEncrypter } from '../crypto'; import { MessageEncrypter } from '../crypto';
import pRetry from 'p-retry'; import pRetry from 'p-retry';
import { PubKey } from '../types';
// ================ Regular ================ // ================ Regular ================
@ -31,7 +32,8 @@ export async function send(
throw new Error('lokiMessageAPI is not initialized.'); throw new Error('lokiMessageAPI is not initialized.');
} }
const { device, plainTextBuffer, encryption, timestamp, ttl } = message; const device = PubKey.cast(message.device);
const { plainTextBuffer, encryption, timestamp, ttl } = message;
const { envelopeType, cipherText } = await MessageEncrypter.encrypt( const { envelopeType, cipherText } = await MessageEncrypter.encrypt(
device, device,
plainTextBuffer, plainTextBuffer,
@ -41,7 +43,7 @@ export async function send(
const data = wrapEnvelope(envelope); const data = wrapEnvelope(envelope);
return pRetry( return pRetry(
async () => window.lokiMessageAPI.sendMessage(device, data, timestamp, ttl), async () => window.lokiMessageAPI.sendMessage(device.key, data, timestamp, ttl),
{ {
retries: Math.max(attempts - 1, 0), retries: Math.max(attempts - 1, 0),
factor: 1, factor: 1,

@ -12,6 +12,9 @@ export class PubKey {
* @param pubkeyString The public key string. * @param pubkeyString The public key string.
*/ */
constructor(pubkeyString: string) { constructor(pubkeyString: string) {
console.log('[vince] pubkeyString:', pubkeyString);
if (!PubKey.validate(pubkeyString)) { if (!PubKey.validate(pubkeyString)) {
throw new Error(`Invalid pubkey string passed: ${pubkeyString}`); throw new Error(`Invalid pubkey string passed: ${pubkeyString}`);
} }

@ -49,7 +49,7 @@ describe('MessageEncrypter', () => {
it('should throw an error', async () => { it('should throw an error', async () => {
const data = crypto.randomBytes(10); const data = crypto.randomBytes(10);
const promise = MessageEncrypter.encrypt( const promise = MessageEncrypter.encrypt(
'1', TestUtils.generateFakePubKey(),
data, data,
EncryptionType.MediumGroup EncryptionType.MediumGroup
); );
@ -66,7 +66,7 @@ describe('MessageEncrypter', () => {
Stubs.FallBackSessionCipherStub.prototype, Stubs.FallBackSessionCipherStub.prototype,
'encrypt' 'encrypt'
); );
await MessageEncrypter.encrypt('1', data, EncryptionType.Fallback); await MessageEncrypter.encrypt(TestUtils.generateFakePubKey(), data, EncryptionType.Fallback);
expect(spy.called).to.equal( expect(spy.called).to.equal(
true, true,
'FallbackSessionCipher.encrypt should be called.' 'FallbackSessionCipher.encrypt should be called.'
@ -79,7 +79,7 @@ describe('MessageEncrypter', () => {
Stubs.FallBackSessionCipherStub.prototype, Stubs.FallBackSessionCipherStub.prototype,
'encrypt' 'encrypt'
); );
await MessageEncrypter.encrypt('1', data, EncryptionType.Fallback); await MessageEncrypter.encrypt(TestUtils.generateFakePubKey(), data, EncryptionType.Fallback);
const paddedData = MessageEncrypter.padPlainTextBuffer(data); const paddedData = MessageEncrypter.padPlainTextBuffer(data);
const firstArgument = new Uint8Array(spy.args[0][0]); const firstArgument = new Uint8Array(spy.args[0][0]);
@ -89,7 +89,7 @@ describe('MessageEncrypter', () => {
it('should return an UNIDENTIFIED SENDER envelope type', async () => { it('should return an UNIDENTIFIED SENDER envelope type', async () => {
const data = crypto.randomBytes(10); const data = crypto.randomBytes(10);
const result = await MessageEncrypter.encrypt( const result = await MessageEncrypter.encrypt(
'1', TestUtils.generateFakePubKey(),
data, data,
EncryptionType.Fallback EncryptionType.Fallback
); );
@ -103,7 +103,7 @@ describe('MessageEncrypter', () => {
it('should call SessionCipher encrypt', async () => { it('should call SessionCipher encrypt', async () => {
const data = crypto.randomBytes(10); const data = crypto.randomBytes(10);
const spy = sandbox.spy(Stubs.SessionCipherStub.prototype, 'encrypt'); const spy = sandbox.spy(Stubs.SessionCipherStub.prototype, 'encrypt');
await MessageEncrypter.encrypt('1', data, EncryptionType.Signal); await MessageEncrypter.encrypt(TestUtils.generateFakePubKey(), data, EncryptionType.Signal);
expect(spy.called).to.equal( expect(spy.called).to.equal(
true, true,
'SessionCipher.encrypt should be called.' 'SessionCipher.encrypt should be called.'
@ -113,7 +113,7 @@ describe('MessageEncrypter', () => {
it('should pass the padded message body to encrypt', async () => { it('should pass the padded message body to encrypt', async () => {
const data = crypto.randomBytes(10); const data = crypto.randomBytes(10);
const spy = sandbox.spy(Stubs.SessionCipherStub.prototype, 'encrypt'); const spy = sandbox.spy(Stubs.SessionCipherStub.prototype, 'encrypt');
await MessageEncrypter.encrypt('1', data, EncryptionType.Signal); await MessageEncrypter.encrypt(TestUtils.generateFakePubKey(), data, EncryptionType.Signal);
const paddedData = MessageEncrypter.padPlainTextBuffer(data); const paddedData = MessageEncrypter.padPlainTextBuffer(data);
const firstArgument = new Uint8Array(spy.args[0][0]); const firstArgument = new Uint8Array(spy.args[0][0]);
@ -123,7 +123,7 @@ describe('MessageEncrypter', () => {
it('should return an UNIDENTIFIED SENDER envelope type', async () => { it('should return an UNIDENTIFIED SENDER envelope type', async () => {
const data = crypto.randomBytes(10); const data = crypto.randomBytes(10);
const result = await MessageEncrypter.encrypt( const result = await MessageEncrypter.encrypt(
'1', TestUtils.generateFakePubKey(),
data, data,
EncryptionType.Signal EncryptionType.Signal
); );
@ -142,7 +142,9 @@ describe('MessageEncrypter', () => {
Stubs.SecretSessionCipherStub.prototype, Stubs.SecretSessionCipherStub.prototype,
'encrypt' 'encrypt'
); );
await MessageEncrypter.encrypt('user', crypto.randomBytes(10), type);
const user = TestUtils.generateFakePubKey();
await MessageEncrypter.encrypt(user, crypto.randomBytes(10), type);
const args = spy.args[0]; const args = spy.args[0];
const [device, certificate] = args; const [device, certificate] = args;
@ -152,7 +154,7 @@ describe('MessageEncrypter', () => {
senderDevice: 1, senderDevice: 1,
}); });
expect(device).to.equal('user'); expect(device).to.equal(user.key);
expect(certificate.toJSON()).to.deep.equal( expect(certificate.toJSON()).to.deep.equal(
expectedCertificate.toJSON() expectedCertificate.toJSON()
); );

@ -1,243 +0,0 @@
import { expect } from 'chai';
import * as crypto from 'crypto';
import * as sinon from 'sinon';
import { toNumber } from 'lodash';
import { MessageSender } from '../../../session/sending';
import LokiMessageAPI from '../../../../js/modules/loki_message_api';
import { TestUtils } from '../../test-utils';
import { UserUtil } from '../../../util';
import { MessageEncrypter } from '../../../session/crypto';
import { SignalService } from '../../../protobuf';
import { OpenGroupMessage } from '../../../session/messages/outgoing';
import { EncryptionType } from '../../../session/types/EncryptionType';
describe('MessageSender', () => {
const sandbox = sinon.createSandbox();
afterEach(() => {
sandbox.restore();
TestUtils.restoreStubs();
});
describe('canSendToSnode', () => {
it('should return the correct value', () => {
const stub = TestUtils.stubWindow('lokiMessageAPI', undefined);
expect(MessageSender.canSendToSnode()).to.equal(
false,
'We cannot send if lokiMessageAPI is not set'
);
stub.set(sandbox.createStubInstance(LokiMessageAPI));
expect(MessageSender.canSendToSnode()).to.equal(
true,
'We can send if lokiMessageAPI is set'
);
});
});
describe('send', () => {
const ourNumber = 'ourNumber';
let lokiMessageAPISendStub: sinon.SinonStub<
[string, Uint8Array, number, number],
Promise<void>
>;
let encryptStub: sinon.SinonStub<[string, Uint8Array, EncryptionType]>;
beforeEach(() => {
// We can do this because LokiMessageAPI has a module export in it
lokiMessageAPISendStub = sandbox.stub<
[string, Uint8Array, number, number],
Promise<void>
>();
TestUtils.stubWindow('lokiMessageAPI', {
sendMessage: lokiMessageAPISendStub,
});
encryptStub = sandbox.stub(MessageEncrypter, 'encrypt').resolves({
envelopeType: SignalService.Envelope.Type.CIPHERTEXT,
cipherText: crypto.randomBytes(10),
});
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);
});
describe('retry', () => {
const rawMessage = {
identifier: '1',
device: '0',
plainTextBuffer: crypto.randomBytes(10),
encryption: EncryptionType.Signal,
timestamp: Date.now(),
ttl: 100,
};
it('should not retry if an error occurred during encryption', async () => {
encryptStub.throws(new Error('Failed to encrypt.'));
const promise = MessageSender.send(rawMessage);
await expect(promise).is.rejectedWith('Failed to encrypt.');
expect(lokiMessageAPISendStub.callCount).to.equal(0);
});
it('should only call lokiMessageAPI once if no errors occured', async () => {
await MessageSender.send(rawMessage);
expect(lokiMessageAPISendStub.callCount).to.equal(1);
});
it('should only retry the specified amount of times before throwing', async () => {
lokiMessageAPISendStub.throws(new Error('API error'));
const attempts = 2;
const promise = MessageSender.send(rawMessage, attempts);
await expect(promise).is.rejectedWith('API error');
expect(lokiMessageAPISendStub.callCount).to.equal(attempts);
});
it('should not throw error if successful send occurs within the retry limit', async () => {
lokiMessageAPISendStub.onFirstCall().throws(new Error('API error'));
await MessageSender.send(rawMessage, 3);
expect(lokiMessageAPISendStub.callCount).to.equal(2);
});
});
describe('logic', () => {
let messageEncyrptReturnEnvelopeType =
SignalService.Envelope.Type.CIPHERTEXT;
beforeEach(() => {
encryptStub.callsFake(async (_device, plainTextBuffer, _type) => ({
envelopeType: messageEncyrptReturnEnvelopeType,
cipherText: plainTextBuffer,
}));
});
it('should pass the correct values to lokiMessageAPI', async () => {
const device = '0';
const timestamp = Date.now();
const ttl = 100;
await MessageSender.send({
identifier: '1',
device,
plainTextBuffer: crypto.randomBytes(10),
encryption: EncryptionType.Signal,
timestamp,
ttl,
});
const args = lokiMessageAPISendStub.getCall(0).args;
expect(args[0]).to.equal(device);
expect(args[2]).to.equal(timestamp);
expect(args[3]).to.equal(ttl);
});
it('should correctly build the envelope', async () => {
messageEncyrptReturnEnvelopeType =
SignalService.Envelope.Type.CIPHERTEXT;
// This test assumes the encryption stub returns the plainText passed into it.
const plainTextBuffer = crypto.randomBytes(10);
const timestamp = Date.now();
await MessageSender.send({
identifier: '1',
device: '0',
plainTextBuffer,
encryption: EncryptionType.Signal,
timestamp,
ttl: 1,
});
const data = lokiMessageAPISendStub.getCall(0).args[1];
const webSocketMessage = SignalService.WebSocketMessage.decode(data);
expect(webSocketMessage.request?.body).to.not.equal(
undefined,
'Request body should not be undefined'
);
expect(webSocketMessage.request?.body).to.not.equal(
null,
'Request body should not be null'
);
const envelope = SignalService.Envelope.decode(
webSocketMessage.request?.body as Uint8Array
);
expect(envelope.type).to.equal(SignalService.Envelope.Type.CIPHERTEXT);
expect(envelope.source).to.equal(ourNumber);
expect(envelope.sourceDevice).to.equal(1);
expect(toNumber(envelope.timestamp)).to.equal(timestamp);
expect(envelope.content).to.deep.equal(plainTextBuffer);
});
describe('UNIDENTIFIED_SENDER', () => {
it('should set the envelope source to be empty', async () => {
messageEncyrptReturnEnvelopeType =
SignalService.Envelope.Type.UNIDENTIFIED_SENDER;
// This test assumes the encryption stub returns the plainText passed into it.
const plainTextBuffer = crypto.randomBytes(10);
const timestamp = Date.now();
await MessageSender.send({
identifier: '1',
device: '0',
plainTextBuffer,
encryption: EncryptionType.Signal,
timestamp,
ttl: 1,
});
const data = lokiMessageAPISendStub.getCall(0).args[1];
const webSocketMessage = SignalService.WebSocketMessage.decode(data);
expect(webSocketMessage.request?.body).to.not.equal(
undefined,
'Request body should not be undefined'
);
expect(webSocketMessage.request?.body).to.not.equal(
null,
'Request body should not be null'
);
const envelope = SignalService.Envelope.decode(
webSocketMessage.request?.body as Uint8Array
);
expect(envelope.type).to.equal(
SignalService.Envelope.Type.UNIDENTIFIED_SENDER
);
expect(envelope.source).to.equal(
'',
'envelope source should be empty in UNIDENTIFIED_SENDER'
);
});
});
});
});
describe('sendToOpenGroup', () => {
it('should send the message to the correct server and channel', async () => {
// We can do this because LokiPublicChatFactoryAPI has a module export in it
const stub = sandbox.stub().resolves({
sendMessage: sandbox.stub(),
});
TestUtils.stubWindow('lokiPublicChatAPI', {
findOrCreateChannel: stub,
});
const group = {
server: 'server',
channel: 1,
conversationId: '0',
};
const message = new OpenGroupMessage({
timestamp: Date.now(),
group,
});
await MessageSender.sendToOpenGroup(message);
const [server, channel, conversationId] = stub.getCall(0).args;
expect(server).to.equal(group.server);
expect(channel).to.equal(group.channel);
expect(conversationId).to.equal(group.conversationId);
});
});
});
Loading…
Cancel
Save