Update libsignal-protocol v0.6.0
Adds session accessors on SessionCipher and an internal session lock to replace the same implemented in protocol_wrapper.js // FREEBIE
This commit is contained in:
parent
3d3cbb45b7
commit
284cf5be3a
2 changed files with 452 additions and 316 deletions
|
@ -34864,6 +34864,7 @@ function SessionBuilder(storage, remoteAddress) {
|
|||
|
||||
SessionBuilder.prototype = {
|
||||
processPreKey: function(device) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
return this.storage.isTrustedIdentity(
|
||||
this.remoteAddress.getName(), device.identityKey
|
||||
).then(function(trusted) {
|
||||
|
@ -34908,6 +34909,7 @@ SessionBuilder.prototype = {
|
|||
]);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
processV3: function(record, message) {
|
||||
var preKeyPair, signedPreKeyPair;
|
||||
|
@ -35070,12 +35072,21 @@ SessionCipher.prototype = {
|
|||
});
|
||||
},
|
||||
encrypt: function(plaintext) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
if (!(plaintext instanceof ArrayBuffer)) {
|
||||
throw new Error("Expected plaintext to be an ArrayBuffer");
|
||||
}
|
||||
|
||||
var address = this.remoteAddress.toString();
|
||||
var ourIdentityKey, myRegistrationId, record, session;
|
||||
var ourIdentityKey, myRegistrationId, record, session, chain;
|
||||
|
||||
var msg = new Internal.protobuf.WhisperMessage();
|
||||
var paddedPlaintext = new Uint8Array(
|
||||
this.getPaddedMessageLength(plaintext.byteLength + 1) - 1
|
||||
);
|
||||
paddedPlaintext.set(new Uint8Array(plaintext));
|
||||
paddedPlaintext[plaintext.byteLength] = 0x80;
|
||||
|
||||
return Promise.all([
|
||||
this.storage.getIdentityKeyPair(),
|
||||
this.storage.getLocalRegistrationId(),
|
||||
|
@ -35092,22 +35103,17 @@ SessionCipher.prototype = {
|
|||
throw new Error("No session to encrypt message for " + address);
|
||||
}
|
||||
|
||||
var msg = new Internal.protobuf.WhisperMessage();
|
||||
var paddedPlaintext = new Uint8Array(
|
||||
this.getPaddedMessageLength(plaintext.byteLength + 1) - 1
|
||||
);
|
||||
paddedPlaintext.set(new Uint8Array(plaintext));
|
||||
paddedPlaintext[plaintext.byteLength] = 0x80;
|
||||
|
||||
msg.ephemeralKey = util.toArrayBuffer(
|
||||
session.currentRatchet.ephemeralKeyPair.pubKey
|
||||
);
|
||||
var chain = session[util.toString(msg.ephemeralKey)];
|
||||
chain = session[util.toString(msg.ephemeralKey)];
|
||||
|
||||
return this.fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
|
||||
return Internal.HKDF(util.toArrayBuffer(chain.messageKeys[chain.chainKey.counter]),
|
||||
new ArrayBuffer(32), "WhisperMessageKeys"
|
||||
).then(function(keys) {
|
||||
return this.fillMessageKeys(chain, chain.chainKey.counter + 1);
|
||||
}.bind(this)).then(function() {
|
||||
return Internal.HKDF(
|
||||
util.toArrayBuffer(chain.messageKeys[chain.chainKey.counter]),
|
||||
new ArrayBuffer(32), "WhisperMessageKeys");
|
||||
}).then(function(keys) {
|
||||
delete chain.messageKeys[chain.chainKey.counter];
|
||||
msg.counter = chain.chainKey.counter;
|
||||
msg.previousCounter = session.currentRatchet.previousCounter;
|
||||
|
@ -35116,7 +35122,7 @@ SessionCipher.prototype = {
|
|||
keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)
|
||||
).then(function(ciphertext) {
|
||||
msg.ciphertext = ciphertext;
|
||||
var encodedMsg = util.toArrayBuffer(msg.encode());
|
||||
var encodedMsg = msg.toArrayBuffer();
|
||||
|
||||
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
|
||||
macInput.set(new Uint8Array(util.toArrayBuffer(ourIdentityKey.pubKey)));
|
||||
|
@ -35124,9 +35130,7 @@ SessionCipher.prototype = {
|
|||
macInput[33*2] = (3 << 4) | 3;
|
||||
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
|
||||
|
||||
return Internal.crypto.sign(
|
||||
keys[1], macInput.buffer
|
||||
).then(function(mac) {
|
||||
return Internal.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
|
||||
var result = new Uint8Array(encodedMsg.byteLength + 9);
|
||||
result[0] = (3 << 4) | 3;
|
||||
result.set(new Uint8Array(encodedMsg), 1);
|
||||
|
@ -35138,8 +35142,6 @@ SessionCipher.prototype = {
|
|||
});
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this)).then(function(message) {
|
||||
if (session.pendingPreKey !== undefined) {
|
||||
var preKeyMsg = new Internal.protobuf.PreKeyWhisperMessage();
|
||||
|
@ -35157,6 +35159,7 @@ SessionCipher.prototype = {
|
|||
return {type: 1, body: util.toString(message)};
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
getPaddedMessageLength: function(messageLength) {
|
||||
var messageLengthWithTerminator = messageLength + 1;
|
||||
|
@ -35169,6 +35172,7 @@ SessionCipher.prototype = {
|
|||
return messagePartCount * 160;
|
||||
},
|
||||
decryptWhisperMessage: function(messageBytes) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
var address = this.remoteAddress.toString();
|
||||
return this.getRecord(address).then(function(record) {
|
||||
if (!record) {
|
||||
|
@ -35185,8 +35189,10 @@ SessionCipher.prototype = {
|
|||
});
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
decryptPreKeyWhisperMessage: function(encodedMessage, encoding) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
var address = this.remoteAddress.toString();
|
||||
return this.getRecord(address).then(function(record) {
|
||||
var preKeyProto = Internal.protobuf.PreKeyWhisperMessage.decode(encodedMessage, encoding);
|
||||
|
@ -35216,6 +35222,7 @@ SessionCipher.prototype = {
|
|||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
doDecryptWhisperMessage: function(messageBytes, session) {
|
||||
if (!messageBytes instanceof ArrayBuffer) {
|
||||
|
@ -35377,13 +35384,46 @@ SessionCipher.prototype = {
|
|||
throw new Error("Bad MAC");
|
||||
}
|
||||
});
|
||||
},
|
||||
getRemoteRegistrationId: function() {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
return this.getRecord(this.remoteAddress.toString()).then(function(record) {
|
||||
if (record === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return record.registrationId;
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
hasOpenSession: function() {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
return this.getRecord(this.remoteAddress.toString()).then(function(record) {
|
||||
if (record === undefined) {
|
||||
return false;
|
||||
}
|
||||
return record.haveOpenSession();
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
closeOpenSessionForDevice: function() {
|
||||
var address = this.remoteAddress.toString();
|
||||
return Internal.SessionLock.queueJobForNumber(address, function() {
|
||||
return this.getRecord(address).then(function(record) {
|
||||
if (record === undefined || record.getOpenSession() === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
record.archiveCurrentState();
|
||||
return this.storage.storeSession(address, record.serialize());
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
libsignal.SessionCipher = function(storage, remoteAddress) {
|
||||
var cipher = new SessionCipher(storage, remoteAddress);
|
||||
|
||||
// return Promise that resolves
|
||||
// returns a Promise that resolves to a ciphertext array buffer
|
||||
this.encrypt = cipher.encrypt.bind(cipher);
|
||||
|
||||
// returns a Promise that inits a session if necessary and resolves
|
||||
|
@ -35392,8 +35432,36 @@ libsignal.SessionCipher = function(storage, remoteAddress) {
|
|||
|
||||
// returns a Promise that resolves to decrypted plaintext array buffer
|
||||
this.decryptWhisperMessage = cipher.decryptWhisperMessage.bind(cipher);
|
||||
|
||||
this.getRemoteRegistrationId = cipher.getRemoteRegistrationId.bind(cipher);
|
||||
this.hasOpenSession = cipher.hasOpenSession.bind(cipher);
|
||||
this.closeOpenSessionForDevice = cipher.closeOpenSessionForDevice.bind(cipher);
|
||||
};
|
||||
|
||||
/*
|
||||
* jobQueue manages multiple queues indexed by device to serialize
|
||||
* session io ops on the database.
|
||||
*/
|
||||
;(function() {
|
||||
'use strict';
|
||||
|
||||
Internal.SessionLock = {};
|
||||
|
||||
var jobQueue = {};
|
||||
|
||||
Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJob) {
|
||||
var runPrevious = jobQueue[number] || Promise.resolve();
|
||||
var runCurrent = jobQueue[number] = runPrevious.then(runJob, runJob);
|
||||
runCurrent.then(function() {
|
||||
if (jobQueue[number] === runCurrent) {
|
||||
delete jobQueue[number];
|
||||
}
|
||||
});
|
||||
return runCurrent;
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
})();
|
||||
/*
|
||||
* vim: ts=4:sw=4:expandtab
|
||||
|
|
|
@ -34750,6 +34750,7 @@ function SessionBuilder(storage, remoteAddress) {
|
|||
|
||||
SessionBuilder.prototype = {
|
||||
processPreKey: function(device) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
return this.storage.isTrustedIdentity(
|
||||
this.remoteAddress.getName(), device.identityKey
|
||||
).then(function(trusted) {
|
||||
|
@ -34794,6 +34795,7 @@ SessionBuilder.prototype = {
|
|||
]);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
processV3: function(record, message) {
|
||||
var preKeyPair, signedPreKeyPair;
|
||||
|
@ -34956,12 +34958,21 @@ SessionCipher.prototype = {
|
|||
});
|
||||
},
|
||||
encrypt: function(plaintext) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
if (!(plaintext instanceof ArrayBuffer)) {
|
||||
throw new Error("Expected plaintext to be an ArrayBuffer");
|
||||
}
|
||||
|
||||
var address = this.remoteAddress.toString();
|
||||
var ourIdentityKey, myRegistrationId, record, session;
|
||||
var ourIdentityKey, myRegistrationId, record, session, chain;
|
||||
|
||||
var msg = new Internal.protobuf.WhisperMessage();
|
||||
var paddedPlaintext = new Uint8Array(
|
||||
this.getPaddedMessageLength(plaintext.byteLength + 1) - 1
|
||||
);
|
||||
paddedPlaintext.set(new Uint8Array(plaintext));
|
||||
paddedPlaintext[plaintext.byteLength] = 0x80;
|
||||
|
||||
return Promise.all([
|
||||
this.storage.getIdentityKeyPair(),
|
||||
this.storage.getLocalRegistrationId(),
|
||||
|
@ -34978,22 +34989,17 @@ SessionCipher.prototype = {
|
|||
throw new Error("No session to encrypt message for " + address);
|
||||
}
|
||||
|
||||
var msg = new Internal.protobuf.WhisperMessage();
|
||||
var paddedPlaintext = new Uint8Array(
|
||||
this.getPaddedMessageLength(plaintext.byteLength + 1) - 1
|
||||
);
|
||||
paddedPlaintext.set(new Uint8Array(plaintext));
|
||||
paddedPlaintext[plaintext.byteLength] = 0x80;
|
||||
|
||||
msg.ephemeralKey = util.toArrayBuffer(
|
||||
session.currentRatchet.ephemeralKeyPair.pubKey
|
||||
);
|
||||
var chain = session[util.toString(msg.ephemeralKey)];
|
||||
chain = session[util.toString(msg.ephemeralKey)];
|
||||
|
||||
return this.fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
|
||||
return Internal.HKDF(util.toArrayBuffer(chain.messageKeys[chain.chainKey.counter]),
|
||||
new ArrayBuffer(32), "WhisperMessageKeys"
|
||||
).then(function(keys) {
|
||||
return this.fillMessageKeys(chain, chain.chainKey.counter + 1);
|
||||
}.bind(this)).then(function() {
|
||||
return Internal.HKDF(
|
||||
util.toArrayBuffer(chain.messageKeys[chain.chainKey.counter]),
|
||||
new ArrayBuffer(32), "WhisperMessageKeys");
|
||||
}).then(function(keys) {
|
||||
delete chain.messageKeys[chain.chainKey.counter];
|
||||
msg.counter = chain.chainKey.counter;
|
||||
msg.previousCounter = session.currentRatchet.previousCounter;
|
||||
|
@ -35002,7 +35008,7 @@ SessionCipher.prototype = {
|
|||
keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)
|
||||
).then(function(ciphertext) {
|
||||
msg.ciphertext = ciphertext;
|
||||
var encodedMsg = util.toArrayBuffer(msg.encode());
|
||||
var encodedMsg = msg.toArrayBuffer();
|
||||
|
||||
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
|
||||
macInput.set(new Uint8Array(util.toArrayBuffer(ourIdentityKey.pubKey)));
|
||||
|
@ -35010,9 +35016,7 @@ SessionCipher.prototype = {
|
|||
macInput[33*2] = (3 << 4) | 3;
|
||||
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
|
||||
|
||||
return Internal.crypto.sign(
|
||||
keys[1], macInput.buffer
|
||||
).then(function(mac) {
|
||||
return Internal.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
|
||||
var result = new Uint8Array(encodedMsg.byteLength + 9);
|
||||
result[0] = (3 << 4) | 3;
|
||||
result.set(new Uint8Array(encodedMsg), 1);
|
||||
|
@ -35024,8 +35028,6 @@ SessionCipher.prototype = {
|
|||
});
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this)).then(function(message) {
|
||||
if (session.pendingPreKey !== undefined) {
|
||||
var preKeyMsg = new Internal.protobuf.PreKeyWhisperMessage();
|
||||
|
@ -35043,6 +35045,7 @@ SessionCipher.prototype = {
|
|||
return {type: 1, body: util.toString(message)};
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
getPaddedMessageLength: function(messageLength) {
|
||||
var messageLengthWithTerminator = messageLength + 1;
|
||||
|
@ -35055,6 +35058,7 @@ SessionCipher.prototype = {
|
|||
return messagePartCount * 160;
|
||||
},
|
||||
decryptWhisperMessage: function(messageBytes) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
var address = this.remoteAddress.toString();
|
||||
return this.getRecord(address).then(function(record) {
|
||||
if (!record) {
|
||||
|
@ -35071,8 +35075,10 @@ SessionCipher.prototype = {
|
|||
});
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
decryptPreKeyWhisperMessage: function(encodedMessage, encoding) {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
var address = this.remoteAddress.toString();
|
||||
return this.getRecord(address).then(function(record) {
|
||||
var preKeyProto = Internal.protobuf.PreKeyWhisperMessage.decode(encodedMessage, encoding);
|
||||
|
@ -35102,6 +35108,7 @@ SessionCipher.prototype = {
|
|||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
doDecryptWhisperMessage: function(messageBytes, session) {
|
||||
if (!messageBytes instanceof ArrayBuffer) {
|
||||
|
@ -35263,13 +35270,46 @@ SessionCipher.prototype = {
|
|||
throw new Error("Bad MAC");
|
||||
}
|
||||
});
|
||||
},
|
||||
getRemoteRegistrationId: function() {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
return this.getRecord(this.remoteAddress.toString()).then(function(record) {
|
||||
if (record === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return record.registrationId;
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
hasOpenSession: function() {
|
||||
return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() {
|
||||
return this.getRecord(this.remoteAddress.toString()).then(function(record) {
|
||||
if (record === undefined) {
|
||||
return false;
|
||||
}
|
||||
return record.haveOpenSession();
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
closeOpenSessionForDevice: function() {
|
||||
var address = this.remoteAddress.toString();
|
||||
return Internal.SessionLock.queueJobForNumber(address, function() {
|
||||
return this.getRecord(address).then(function(record) {
|
||||
if (record === undefined || record.getOpenSession() === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
record.archiveCurrentState();
|
||||
return this.storage.storeSession(address, record.serialize());
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
libsignal.SessionCipher = function(storage, remoteAddress) {
|
||||
var cipher = new SessionCipher(storage, remoteAddress);
|
||||
|
||||
// return Promise that resolves
|
||||
// returns a Promise that resolves to a ciphertext array buffer
|
||||
this.encrypt = cipher.encrypt.bind(cipher);
|
||||
|
||||
// returns a Promise that inits a session if necessary and resolves
|
||||
|
@ -35278,6 +35318,34 @@ libsignal.SessionCipher = function(storage, remoteAddress) {
|
|||
|
||||
// returns a Promise that resolves to decrypted plaintext array buffer
|
||||
this.decryptWhisperMessage = cipher.decryptWhisperMessage.bind(cipher);
|
||||
|
||||
this.getRemoteRegistrationId = cipher.getRemoteRegistrationId.bind(cipher);
|
||||
this.hasOpenSession = cipher.hasOpenSession.bind(cipher);
|
||||
this.closeOpenSessionForDevice = cipher.closeOpenSessionForDevice.bind(cipher);
|
||||
};
|
||||
|
||||
/*
|
||||
* jobQueue manages multiple queues indexed by device to serialize
|
||||
* session io ops on the database.
|
||||
*/
|
||||
;(function() {
|
||||
'use strict';
|
||||
|
||||
Internal.SessionLock = {};
|
||||
|
||||
var jobQueue = {};
|
||||
|
||||
Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJob) {
|
||||
var runPrevious = jobQueue[number] || Promise.resolve();
|
||||
var runCurrent = jobQueue[number] = runPrevious.then(runJob, runJob);
|
||||
runCurrent.then(function() {
|
||||
if (jobQueue[number] === runCurrent) {
|
||||
delete jobQueue[number];
|
||||
}
|
||||
});
|
||||
return runCurrent;
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
})();
|
Loading…
Reference in a new issue