From 538c7aecceaa4fb2c27f1a105759c8f7c4cd0ac8 Mon Sep 17 00:00:00 2001 From: lilia Date: Wed, 8 Feb 2017 22:00:53 -0800 Subject: [PATCH] Fix crashes when no preKey is present Adds a test identical to the 'basic prekey v3' test with the addition of `delete preKeyBundle.preKey` where appropriate. --- dist/libsignal-protocol.js | 15 ++++-- src/SessionBuilder.js | 9 +++- src/SessionCipher.js | 6 ++- test/SessionBuilderTest.js | 95 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/dist/libsignal-protocol.js b/dist/libsignal-protocol.js index 13353bc..d9762f5 100644 --- a/dist/libsignal-protocol.js +++ b/dist/libsignal-protocol.js @@ -35845,15 +35845,20 @@ SessionBuilder.prototype = { }).then(function() { return Internal.crypto.createKeyPair(); }).then(function(baseKey) { - var devicePreKey = (device.preKey.publicKey); + var devicePreKey; + if (device.preKey) { + devicePreKey = device.preKey.publicKey; + } return this.initSession(true, baseKey, undefined, device.identityKey, devicePreKey, device.signedPreKey.publicKey, device.registrationId ).then(function(session) { session.pendingPreKey = { - preKeyId : device.preKey.keyId, signedKeyId : device.signedPreKey.keyId, baseKey : baseKey.pubKey }; + if (device.preKey) { + session.pendingPreKey.preKeyId = device.preKey.keyId; + } return session; }); }.bind(this)).then(function(session) { @@ -36136,7 +36141,9 @@ SessionCipher.prototype = { preKeyMsg.registrationId = myRegistrationId; preKeyMsg.baseKey = util.toArrayBuffer(session.pendingPreKey.baseKey); - preKeyMsg.preKeyId = session.pendingPreKey.preKeyId; + if (session.pendingPreKey.preKeyId) { + preKeyMsg.preKeyId = session.pendingPreKey.preKeyId; + } preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId; preKeyMsg.message = message; @@ -36219,7 +36226,7 @@ SessionCipher.prototype = { ).then(function(plaintext) { record.updateSessionState(session); return this.storage.storeSession(address, record.serialize()).then(function() { - if (preKeyId !== undefined) { + if (preKeyId !== undefined && preKeyId !== null) { return this.storage.removePreKey(preKeyId); } }.bind(this)).then(function() { diff --git a/src/SessionBuilder.js b/src/SessionBuilder.js index 6b8ea5e..d244288 100644 --- a/src/SessionBuilder.js +++ b/src/SessionBuilder.js @@ -21,15 +21,20 @@ SessionBuilder.prototype = { }).then(function() { return Internal.crypto.createKeyPair(); }).then(function(baseKey) { - var devicePreKey = (device.preKey.publicKey); + var devicePreKey; + if (device.preKey) { + devicePreKey = device.preKey.publicKey; + } return this.initSession(true, baseKey, undefined, device.identityKey, devicePreKey, device.signedPreKey.publicKey, device.registrationId ).then(function(session) { session.pendingPreKey = { - preKeyId : device.preKey.keyId, signedKeyId : device.signedPreKey.keyId, baseKey : baseKey.pubKey }; + if (device.preKey) { + session.pendingPreKey.preKeyId = device.preKey.keyId; + } return session; }); }.bind(this)).then(function(session) { diff --git a/src/SessionCipher.js b/src/SessionCipher.js index b32aaa4..334dee8 100644 --- a/src/SessionCipher.js +++ b/src/SessionCipher.js @@ -89,7 +89,9 @@ SessionCipher.prototype = { preKeyMsg.registrationId = myRegistrationId; preKeyMsg.baseKey = util.toArrayBuffer(session.pendingPreKey.baseKey); - preKeyMsg.preKeyId = session.pendingPreKey.preKeyId; + if (session.pendingPreKey.preKeyId) { + preKeyMsg.preKeyId = session.pendingPreKey.preKeyId; + } preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId; preKeyMsg.message = message; @@ -172,7 +174,7 @@ SessionCipher.prototype = { ).then(function(plaintext) { record.updateSessionState(session); return this.storage.storeSession(address, record.serialize()).then(function() { - if (preKeyId !== undefined) { + if (preKeyId !== undefined && preKeyId !== null) { return this.storage.removePreKey(preKeyId); } }.bind(this)).then(function() { diff --git a/test/SessionBuilderTest.js b/test/SessionBuilderTest.js index 3f94ab8..8938aee 100644 --- a/test/SessionBuilderTest.js +++ b/test/SessionBuilderTest.js @@ -142,4 +142,99 @@ describe('SessionBuilder', function() { }); }); }); + + describe("basic v3 NO PREKEY", function() { + var aliceStore = new SignalProtocolStore(); + + var bobStore = new SignalProtocolStore(); + var bobPreKeyId = 1337; + var bobSignedKeyId = 1; + + var Curve = libsignal.Curve; + + before(function(done) { + Promise.all([ + generateIdentity(aliceStore), + generateIdentity(bobStore), + ]).then(function() { + return generatePreKeyBundle(bobStore, bobPreKeyId, bobSignedKeyId); + }).then(function(preKeyBundle) { + delete preKeyBundle.preKey; + var builder = new libsignal.SessionBuilder(aliceStore, BOB_ADDRESS); + return builder.processPreKey(preKeyBundle).then(function() { + done(); + }); + }).catch(done); + }); + + var originalMessage = util.toArrayBuffer("L'homme est condamné à être libre"); + var aliceSessionCipher = new libsignal.SessionCipher(aliceStore, BOB_ADDRESS); + var bobSessionCipher = new libsignal.SessionCipher(bobStore, ALICE_ADDRESS); + + it('creates a session', function(done) { + return aliceStore.loadSession(BOB_ADDRESS.toString()).then(function(record) { + assert.isDefined(record); + var sessionRecord = Internal.SessionRecord.deserialize(record); + assert.isTrue(sessionRecord.haveOpenSession()); + assert.isDefined(sessionRecord.getOpenSession()); + }).then(done, done); + }); + + it('the session can encrypt', function(done) { + aliceSessionCipher.encrypt(originalMessage).then(function(ciphertext) { + + assert.strictEqual(ciphertext.type, 3); // PREKEY_BUNDLE + + return bobSessionCipher.decryptPreKeyWhisperMessage(ciphertext.body, 'binary'); + + }).then(function(plaintext) { + + assertEqualArrayBuffers(plaintext, originalMessage); + + }).then(done, done); + }); + + it('the session can decrypt', function(done) { + bobSessionCipher.encrypt(originalMessage).then(function(ciphertext) { + + return aliceSessionCipher.decryptWhisperMessage(ciphertext.body, 'binary'); + + }).then(function(plaintext) { + + assertEqualArrayBuffers(plaintext, originalMessage); + + }).then(done, done); + }); + + it('accepts a new preKey with the same identity', function(done) { + generatePreKeyBundle(bobStore, bobPreKeyId + 1, bobSignedKeyId + 1).then(function(preKeyBundle) { + delete preKeyBundle.preKey; + var builder = new libsignal.SessionBuilder(aliceStore, BOB_ADDRESS); + return builder.processPreKey(preKeyBundle).then(function() { + return aliceStore.loadSession(BOB_ADDRESS.toString()).then(function(record) { + assert.isDefined(record); + var sessionRecord = Internal.SessionRecord.deserialize(record); + assert.isTrue(sessionRecord.haveOpenSession()); + assert.isDefined(sessionRecord.getOpenSession()); + done(); + }); + }); + }).catch(done); + }); + + it('rejects untrusted identity keys', function(done) { + KeyHelper.generateIdentityKeyPair().then(function(newIdentity) { + var builder = new libsignal.SessionBuilder(aliceStore, BOB_ADDRESS); + return builder.processPreKey({ + identityKey: newIdentity.pubKey, + registrationId : 12356 + }).then(function(e) { + assert.fail('should not be trusted'); + }).catch(function(e) { + assert.strictEqual(e.message, 'Identity key changed'); + done(); + }).catch(done); + }); + }); + }); });