From 213b44935dfca09645de68299812ee8d6a99b2f7 Mon Sep 17 00:00:00 2001 From: lilia Date: Mon, 2 May 2016 16:55:02 -0700 Subject: [PATCH] Update libsignal-protocol v0.8.0 Reorder identity key check --- js/libtextsecure.js | 136 ++++++++++++++-------------- libtextsecure/libsignal-protocol.js | 136 ++++++++++++++-------------- 2 files changed, 134 insertions(+), 138 deletions(-) diff --git a/js/libtextsecure.js b/js/libtextsecure.js index fcf4c4b6..5f4873b6 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -34020,7 +34020,7 @@ Curve25519Worker.prototype = { */ var Internal = Internal || {}; -Internal.crypto = function() { +(function() { 'use strict'; var crypto = window.crypto; @@ -34041,7 +34041,7 @@ Internal.crypto = function() { } }; - return { + Internal.crypto = { getRandomBytes: function(size) { var array = new Uint8Array(size); crypto.getRandomValues(array); @@ -34138,7 +34138,16 @@ Internal.crypto = function() { return Internal.curve25519.verify(pubKey, msg, sig); } }; -}(); + + // HKDF for TextSecure has a bit of additional handling - salts always end up being 32 bytes + Internal.HKDF = function(input, salt, info) { + if (salt.byteLength != 32) + throw new Error("Got salt of incorrect length"); + + return Internal.crypto.HKDF(input, salt, util.toArrayBuffer(info)); + } + +})(); /* vim: ts=4:sw=4 * @@ -34270,17 +34279,6 @@ window.libsignal = window.libsignal || {}; libsignal.protocol = function() { var self = {}; - /***************************** - *** Internal Crypto stuff *** - *****************************/ - Internal.HKDF = function(input, salt, info) { - // HKDF for TextSecure has a bit of additional handling - salts always end up being 32 bytes - if (salt.byteLength != 32) - throw new Error("Got salt of incorrect length"); - - return Internal.crypto.HKDF(input, salt, util.toArrayBuffer(info)); - } - var verifyMAC = function(data, key, mac, length) { return Internal.crypto.sign(key, data).then(function(calculated_mac) { if (mac.byteLength != length || calculated_mac.byteLength < length) { @@ -34301,27 +34299,6 @@ libsignal.protocol = function() { /****************************** *** Ratchet implementation *** ******************************/ - Internal.calculateRatchet = function(session, remoteKey, sending) { - var ratchet = session.currentRatchet; - - return Internal.crypto.ECDHE(remoteKey, util.toArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) { - return Internal.HKDF(sharedSecret, util.toArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) { - var ephemeralPublicKey; - if (sending) { - ephemeralPublicKey = ratchet.ephemeralKeyPair.pubKey; - } - else { - ephemeralPublicKey = remoteKey; - } - session[util.toString(ephemeralPublicKey)] = { - messageKeys: {}, - chainKey: { counter: -1, key: masterKey[1] } - }; - ratchet.rootKey = masterKey[0]; - }); - }); - } - self.createIdentityKeyRecvSocket = function() { var socketInfo = {}; var keyPair; @@ -34613,10 +34590,12 @@ Internal.SessionRecord = function() { haveOpenSession: function() { return this.registrationId !== null; }, - getSessionOrIdentityKeyByBaseKey: function(baseKey) { - var sessions = this._sessions; - var preferredSession = this._sessions[util.toString(baseKey)]; + getSessionByBaseKey: function(baseKey) { + return this._sessions[util.toString(baseKey)]; + }, + getSessionOrIdentityKeyByBaseKey: function(baseKey) { + var preferredSession = this.getSessionByBaseKey(baseKey); if (preferredSession !== undefined) { return preferredSession; } @@ -34867,15 +34846,31 @@ SessionBuilder.prototype = { }.bind(this)); }, processV3: function(record, message) { - var preKeyPair, signedPreKeyPair; - var session = record.getSessionOrIdentityKeyByBaseKey(message.baseKey); - return Promise.all([ - this.storage.loadPreKey(message.preKeyId), - this.storage.loadSignedPreKey(message.signedPreKeyId), - ]).then(function(results) { - preKeyPair = results[0]; - signedPreKeyPair = results[1]; - }).then(function() { + var preKeyPair, signedPreKeyPair, session; + return this.storage.isTrustedIdentity( + this.remoteAddress.getName(), message.identityKey.toArrayBuffer() + ).then(function(trusted) { + if (!trusted) { + var e = new Error('Unknown identity key'); + e.identityKey = message.identityKey.toArrayBuffer(); + throw e; + } + return Promise.all([ + this.storage.loadPreKey(message.preKeyId), + this.storage.loadSignedPreKey(message.signedPreKeyId), + ]).then(function(results) { + preKeyPair = results[0]; + signedPreKeyPair = results[1]; + }); + }.bind(this)).then(function() { + session = record.getSessionByBaseKey(message.baseKey); + if (session) { + console.log("Duplicate PreKeyMessage for session"); + return; + } + + session = record.getOpenSession(); + if (signedPreKeyPair === undefined) { // Session may or may not be the right one, but if its not, we // can't do anything about it ...fall through and let @@ -34886,24 +34881,9 @@ SessionBuilder.prototype = { throw new Error("Missing Signed PreKey for PreKeyWhisperMessage"); } } - if (session !== undefined) { - // Duplicate PreKeyMessage for session: - if (util.isEqual(session.indexInfo.baseKey, message.baseKey)) { - return; - } - // We already had a session/known identity key: - if (util.isEqual(session.indexInfo.remoteIdentityKey, message.identityKey)) { - // If the identity key matches the previous one, close the - // previous one and use the new one - record.archiveCurrentState(); - } else { - // ...otherwise create an error that the UI will pick up - // and ask the user if they want to re-negotiate - var e = new Error('Unknown identity key'); - e.identityKey = message.identityKey.toArrayBuffer(); - throw e; - } + if (session !== undefined) { + record.archiveCurrentState(); } if (message.preKeyId && !preKeyPair) { console.log('Invalid prekey id'); @@ -34992,18 +34972,36 @@ SessionBuilder.prototype = { session.indexInfo.baseKey = ourEphemeralKey.pubKey; return Internal.crypto.createKeyPair().then(function(ourSendingEphemeralKey) { session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey; - return Internal.calculateRatchet(session, theirSignedPubKey, true).then(function() { + return this.calculateSendingRatchet(session, theirSignedPubKey).then(function() { return session; }); - }); + }.bind(this)); } else { session.indexInfo.baseKey = theirEphemeralPubKey; session.currentRatchet.ephemeralKeyPair = ourSignedKey; return session; } - }); - }); + }.bind(this)); + }.bind(this)); + }, + calculateSendingRatchet: function(session, remoteKey) { + var ratchet = session.currentRatchet; + + return Internal.crypto.ECDHE( + remoteKey, util.toArrayBuffer(ratchet.ephemeralKeyPair.privKey) + ).then(function(sharedSecret) { + return Internal.HKDF( + sharedSecret, util.toArrayBuffer(ratchet.rootKey), "WhisperRatchet" + ); + }).then(function(masterKey) { + session[util.toString(ratchet.ephemeralKeyPair.pubKey)] = { + messageKeys : {}, + chainKey : { counter : -1, key : masterKey[1] } + }; + ratchet.rootKey = masterKey[0]; + }); } + }; libsignal.SessionBuilder = function (storage, remoteAddress) { @@ -35162,7 +35160,7 @@ SessionCipher.prototype = { } var builder = new SessionBuilder(this.storage, this.remoteAddress); return builder.processV3(record, preKeyProto).then(function(preKeyId) { - var session = record.getSessionOrIdentityKeyByBaseKey(preKeyProto.baseKey); + var session = record.getSessionByBaseKey(preKeyProto.baseKey); return this.doDecryptWhisperMessage( preKeyProto.message.toArrayBuffer(), session ).then(function(plaintext) { diff --git a/libtextsecure/libsignal-protocol.js b/libtextsecure/libsignal-protocol.js index 1aa654e6..bdf63dde 100644 --- a/libtextsecure/libsignal-protocol.js +++ b/libtextsecure/libsignal-protocol.js @@ -33906,7 +33906,7 @@ Curve25519Worker.prototype = { */ var Internal = Internal || {}; -Internal.crypto = function() { +(function() { 'use strict'; var crypto = window.crypto; @@ -33927,7 +33927,7 @@ Internal.crypto = function() { } }; - return { + Internal.crypto = { getRandomBytes: function(size) { var array = new Uint8Array(size); crypto.getRandomValues(array); @@ -34024,7 +34024,16 @@ Internal.crypto = function() { return Internal.curve25519.verify(pubKey, msg, sig); } }; -}(); + + // HKDF for TextSecure has a bit of additional handling - salts always end up being 32 bytes + Internal.HKDF = function(input, salt, info) { + if (salt.byteLength != 32) + throw new Error("Got salt of incorrect length"); + + return Internal.crypto.HKDF(input, salt, util.toArrayBuffer(info)); + } + +})(); /* vim: ts=4:sw=4 * @@ -34156,17 +34165,6 @@ window.libsignal = window.libsignal || {}; libsignal.protocol = function() { var self = {}; - /***************************** - *** Internal Crypto stuff *** - *****************************/ - Internal.HKDF = function(input, salt, info) { - // HKDF for TextSecure has a bit of additional handling - salts always end up being 32 bytes - if (salt.byteLength != 32) - throw new Error("Got salt of incorrect length"); - - return Internal.crypto.HKDF(input, salt, util.toArrayBuffer(info)); - } - var verifyMAC = function(data, key, mac, length) { return Internal.crypto.sign(key, data).then(function(calculated_mac) { if (mac.byteLength != length || calculated_mac.byteLength < length) { @@ -34187,27 +34185,6 @@ libsignal.protocol = function() { /****************************** *** Ratchet implementation *** ******************************/ - Internal.calculateRatchet = function(session, remoteKey, sending) { - var ratchet = session.currentRatchet; - - return Internal.crypto.ECDHE(remoteKey, util.toArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) { - return Internal.HKDF(sharedSecret, util.toArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) { - var ephemeralPublicKey; - if (sending) { - ephemeralPublicKey = ratchet.ephemeralKeyPair.pubKey; - } - else { - ephemeralPublicKey = remoteKey; - } - session[util.toString(ephemeralPublicKey)] = { - messageKeys: {}, - chainKey: { counter: -1, key: masterKey[1] } - }; - ratchet.rootKey = masterKey[0]; - }); - }); - } - self.createIdentityKeyRecvSocket = function() { var socketInfo = {}; var keyPair; @@ -34499,10 +34476,12 @@ Internal.SessionRecord = function() { haveOpenSession: function() { return this.registrationId !== null; }, - getSessionOrIdentityKeyByBaseKey: function(baseKey) { - var sessions = this._sessions; - var preferredSession = this._sessions[util.toString(baseKey)]; + getSessionByBaseKey: function(baseKey) { + return this._sessions[util.toString(baseKey)]; + }, + getSessionOrIdentityKeyByBaseKey: function(baseKey) { + var preferredSession = this.getSessionByBaseKey(baseKey); if (preferredSession !== undefined) { return preferredSession; } @@ -34753,15 +34732,31 @@ SessionBuilder.prototype = { }.bind(this)); }, processV3: function(record, message) { - var preKeyPair, signedPreKeyPair; - var session = record.getSessionOrIdentityKeyByBaseKey(message.baseKey); - return Promise.all([ - this.storage.loadPreKey(message.preKeyId), - this.storage.loadSignedPreKey(message.signedPreKeyId), - ]).then(function(results) { - preKeyPair = results[0]; - signedPreKeyPair = results[1]; - }).then(function() { + var preKeyPair, signedPreKeyPair, session; + return this.storage.isTrustedIdentity( + this.remoteAddress.getName(), message.identityKey.toArrayBuffer() + ).then(function(trusted) { + if (!trusted) { + var e = new Error('Unknown identity key'); + e.identityKey = message.identityKey.toArrayBuffer(); + throw e; + } + return Promise.all([ + this.storage.loadPreKey(message.preKeyId), + this.storage.loadSignedPreKey(message.signedPreKeyId), + ]).then(function(results) { + preKeyPair = results[0]; + signedPreKeyPair = results[1]; + }); + }.bind(this)).then(function() { + session = record.getSessionByBaseKey(message.baseKey); + if (session) { + console.log("Duplicate PreKeyMessage for session"); + return; + } + + session = record.getOpenSession(); + if (signedPreKeyPair === undefined) { // Session may or may not be the right one, but if its not, we // can't do anything about it ...fall through and let @@ -34772,24 +34767,9 @@ SessionBuilder.prototype = { throw new Error("Missing Signed PreKey for PreKeyWhisperMessage"); } } - if (session !== undefined) { - // Duplicate PreKeyMessage for session: - if (util.isEqual(session.indexInfo.baseKey, message.baseKey)) { - return; - } - // We already had a session/known identity key: - if (util.isEqual(session.indexInfo.remoteIdentityKey, message.identityKey)) { - // If the identity key matches the previous one, close the - // previous one and use the new one - record.archiveCurrentState(); - } else { - // ...otherwise create an error that the UI will pick up - // and ask the user if they want to re-negotiate - var e = new Error('Unknown identity key'); - e.identityKey = message.identityKey.toArrayBuffer(); - throw e; - } + if (session !== undefined) { + record.archiveCurrentState(); } if (message.preKeyId && !preKeyPair) { console.log('Invalid prekey id'); @@ -34878,18 +34858,36 @@ SessionBuilder.prototype = { session.indexInfo.baseKey = ourEphemeralKey.pubKey; return Internal.crypto.createKeyPair().then(function(ourSendingEphemeralKey) { session.currentRatchet.ephemeralKeyPair = ourSendingEphemeralKey; - return Internal.calculateRatchet(session, theirSignedPubKey, true).then(function() { + return this.calculateSendingRatchet(session, theirSignedPubKey).then(function() { return session; }); - }); + }.bind(this)); } else { session.indexInfo.baseKey = theirEphemeralPubKey; session.currentRatchet.ephemeralKeyPair = ourSignedKey; return session; } - }); - }); + }.bind(this)); + }.bind(this)); + }, + calculateSendingRatchet: function(session, remoteKey) { + var ratchet = session.currentRatchet; + + return Internal.crypto.ECDHE( + remoteKey, util.toArrayBuffer(ratchet.ephemeralKeyPair.privKey) + ).then(function(sharedSecret) { + return Internal.HKDF( + sharedSecret, util.toArrayBuffer(ratchet.rootKey), "WhisperRatchet" + ); + }).then(function(masterKey) { + session[util.toString(ratchet.ephemeralKeyPair.pubKey)] = { + messageKeys : {}, + chainKey : { counter : -1, key : masterKey[1] } + }; + ratchet.rootKey = masterKey[0]; + }); } + }; libsignal.SessionBuilder = function (storage, remoteAddress) { @@ -35048,7 +35046,7 @@ SessionCipher.prototype = { } var builder = new SessionBuilder(this.storage, this.remoteAddress); return builder.processV3(record, preKeyProto).then(function(preKeyId) { - var session = record.getSessionOrIdentityKeyByBaseKey(preKeyProto.baseKey); + var session = record.getSessionByBaseKey(preKeyProto.baseKey); return this.doDecryptWhisperMessage( preKeyProto.message.toArrayBuffer(), session ).then(function(plaintext) {