Honor messageKeysLimit, remove batching for session saves
This commit is contained in:
parent
80871270c6
commit
9858ae0642
7 changed files with 87 additions and 36 deletions
|
@ -24,12 +24,14 @@ const {
|
||||||
|
|
||||||
const REVOKED_CERTIFICATES = [];
|
const REVOKED_CERTIFICATES = [];
|
||||||
|
|
||||||
function SecretSessionCipher(storage) {
|
function SecretSessionCipher(storage, options) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
// We do this on construction because libsignal won't be available when this file loads
|
// We do this on construction because libsignal won't be available when this file loads
|
||||||
const { SessionCipher } = libsignal;
|
const { SessionCipher } = libsignal;
|
||||||
this.SessionCipher = SessionCipher;
|
this.SessionCipher = SessionCipher;
|
||||||
|
|
||||||
|
this.options = options || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CIPHERTEXT_VERSION = 1;
|
const CIPHERTEXT_VERSION = 1;
|
||||||
|
@ -291,7 +293,8 @@ SecretSessionCipher.prototype = {
|
||||||
|
|
||||||
const sessionCipher = new SessionCipher(
|
const sessionCipher = new SessionCipher(
|
||||||
signalProtocolStore,
|
signalProtocolStore,
|
||||||
destinationAddress
|
destinationAddress,
|
||||||
|
this.options
|
||||||
);
|
);
|
||||||
|
|
||||||
const message = await sessionCipher.encrypt(paddedPlaintext);
|
const message = await sessionCipher.encrypt(paddedPlaintext);
|
||||||
|
@ -448,7 +451,11 @@ SecretSessionCipher.prototype = {
|
||||||
const { SessionCipher } = this;
|
const { SessionCipher } = this;
|
||||||
const signalProtocolStore = this.storage;
|
const signalProtocolStore = this.storage;
|
||||||
|
|
||||||
const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
|
const cipher = new SessionCipher(
|
||||||
|
signalProtocolStore,
|
||||||
|
remoteAddress,
|
||||||
|
this.options
|
||||||
|
);
|
||||||
|
|
||||||
return cipher.getSessionVersion();
|
return cipher.getSessionVersion();
|
||||||
},
|
},
|
||||||
|
@ -458,7 +465,11 @@ SecretSessionCipher.prototype = {
|
||||||
const { SessionCipher } = this;
|
const { SessionCipher } = this;
|
||||||
const signalProtocolStore = this.storage;
|
const signalProtocolStore = this.storage;
|
||||||
|
|
||||||
const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
|
const cipher = new SessionCipher(
|
||||||
|
signalProtocolStore,
|
||||||
|
remoteAddress,
|
||||||
|
this.options
|
||||||
|
);
|
||||||
|
|
||||||
return cipher.getRemoteRegistrationId();
|
return cipher.getRemoteRegistrationId();
|
||||||
},
|
},
|
||||||
|
@ -468,7 +479,11 @@ SecretSessionCipher.prototype = {
|
||||||
const { SessionCipher } = this;
|
const { SessionCipher } = this;
|
||||||
const signalProtocolStore = this.storage;
|
const signalProtocolStore = this.storage;
|
||||||
|
|
||||||
const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
|
const cipher = new SessionCipher(
|
||||||
|
signalProtocolStore,
|
||||||
|
remoteAddress,
|
||||||
|
this.options
|
||||||
|
);
|
||||||
|
|
||||||
return cipher.closeOpenSessionForDevice();
|
return cipher.closeOpenSessionForDevice();
|
||||||
},
|
},
|
||||||
|
@ -528,12 +543,14 @@ SecretSessionCipher.prototype = {
|
||||||
case CiphertextMessage.WHISPER_TYPE:
|
case CiphertextMessage.WHISPER_TYPE:
|
||||||
return new SessionCipher(
|
return new SessionCipher(
|
||||||
signalProtocolStore,
|
signalProtocolStore,
|
||||||
sender
|
sender,
|
||||||
|
this.options
|
||||||
).decryptWhisperMessage(message.content);
|
).decryptWhisperMessage(message.content);
|
||||||
case CiphertextMessage.PREKEY_TYPE:
|
case CiphertextMessage.PREKEY_TYPE:
|
||||||
return new SessionCipher(
|
return new SessionCipher(
|
||||||
signalProtocolStore,
|
signalProtocolStore,
|
||||||
sender
|
sender,
|
||||||
|
this.options
|
||||||
).decryptPreKeyWhisperMessage(message.content);
|
).decryptPreKeyWhisperMessage(message.content);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown type: ${message.type}`);
|
throw new Error(`Unknown type: ${message.type}`);
|
||||||
|
|
|
@ -163,20 +163,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function SignalProtocolStore() {
|
function SignalProtocolStore() {}
|
||||||
this.sessionUpdateBatcher = window.Signal.Util.createBatcher({
|
|
||||||
wait: 500,
|
|
||||||
maxSize: 20,
|
|
||||||
processBatch: async items => {
|
|
||||||
// We only care about the most recent update for each session
|
|
||||||
const byId = _.groupBy(items, item => item.id);
|
|
||||||
const ids = Object.keys(byId);
|
|
||||||
const mostRecent = ids.map(id => _.last(byId[id]));
|
|
||||||
|
|
||||||
await window.Signal.Data.createOrUpdateSessions(mostRecent);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _hydrateCache(object, field, itemsPromise, idField) {
|
async function _hydrateCache(object, field, itemsPromise, idField) {
|
||||||
const items = await itemsPromise;
|
const items = await itemsPromise;
|
||||||
|
@ -350,8 +337,11 @@
|
||||||
if (session) {
|
if (session) {
|
||||||
return session.record;
|
return session.record;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
window.log.error(`could not load session ${encodedAddress}`);
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
window.log.error(
|
||||||
|
`could not load session ${encodedAddress}: ${errorString}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -365,6 +355,7 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
const previousData = this.sessions[id];
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
id,
|
id,
|
||||||
|
@ -373,13 +364,22 @@
|
||||||
record,
|
record,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Optimistically update in-memory cache; will revert if save fails.
|
||||||
this.sessions[id] = data;
|
this.sessions[id] = data;
|
||||||
|
|
||||||
// Note: Because these are cached in memory, we batch and make these database
|
try {
|
||||||
// updates out of band.
|
await window.Signal.Data.createOrUpdateSession(data);
|
||||||
this.sessionUpdateBatcher.add(data);
|
} catch (e) {
|
||||||
} catch (e) {
|
if (previousData) {
|
||||||
window.log.error(`could not store session for ${encodedAddress}`);
|
this.sessions[id] = previousData;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
window.log.error(
|
||||||
|
`could not store session for ${encodedAddress}: ${errorString}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getDeviceIds(identifier) {
|
async getDeviceIds(identifier) {
|
||||||
|
@ -604,8 +604,21 @@
|
||||||
},
|
},
|
||||||
async _saveIdentityKey(data) {
|
async _saveIdentityKey(data) {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
|
|
||||||
|
const previousData = this.identityKeys[id];
|
||||||
|
|
||||||
|
// Optimistically update in-memory cache; will revert if save fails.
|
||||||
this.identityKeys[id] = data;
|
this.identityKeys[id] = data;
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey(data);
|
|
||||||
|
try {
|
||||||
|
await window.Signal.Data.createOrUpdateIdentityKey(data);
|
||||||
|
} catch (error) {
|
||||||
|
if (previousData) {
|
||||||
|
this.identityKeys[id] = previousData;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async saveIdentity(encodedAddress, publicKey, nonblockingApproval) {
|
async saveIdentity(encodedAddress, publicKey, nonblockingApproval) {
|
||||||
if (encodedAddress === null || encodedAddress === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
|
|
|
@ -24703,9 +24703,10 @@ libsignal.SessionBuilder = function (storage, remoteAddress) {
|
||||||
this.processV3 = builder.processV3.bind(builder);
|
this.processV3 = builder.processV3.bind(builder);
|
||||||
};
|
};
|
||||||
|
|
||||||
function SessionCipher(storage, remoteAddress) {
|
function SessionCipher(storage, remoteAddress, options) {
|
||||||
this.remoteAddress = remoteAddress;
|
this.remoteAddress = remoteAddress;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
this.options = options || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionCipher.prototype = {
|
SessionCipher.prototype = {
|
||||||
|
@ -25045,10 +25046,20 @@ SessionCipher.prototype = {
|
||||||
return Promise.resolve(); // Already calculated
|
return Promise.resolve(); // Already calculated
|
||||||
}
|
}
|
||||||
|
|
||||||
if (counter - chain.chainKey.counter > 5000) {
|
var limit = 5000;
|
||||||
throw new Error('Over 5000 messages into the future! New: ' + counter + ', Existing: ' + chain.chainKey.counter);
|
if (this.options.messageKeysLimit === false) {
|
||||||
|
// noop
|
||||||
|
} else {
|
||||||
|
if (this.options.messageKeysLimit > 0) {
|
||||||
|
limit = this.options.messageKeysLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counter - chain.chainKey.counter > limit) {
|
||||||
|
throw new Error('Over ' + limit + ' messages into the future! New: ' + counter + ', Existing: ' + chain.chainKey.counter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (chain.chainKey.key === undefined) {
|
if (chain.chainKey.key === undefined) {
|
||||||
throw new Error("Got invalid request to extend chain after it was already closed");
|
throw new Error("Got invalid request to extend chain after it was already closed");
|
||||||
}
|
}
|
||||||
|
|
2
ts/libsignal.d.ts
vendored
2
ts/libsignal.d.ts
vendored
|
@ -202,7 +202,7 @@ export declare class SessionCipherClass {
|
||||||
constructor(
|
constructor(
|
||||||
storage: StorageType,
|
storage: StorageType,
|
||||||
remoteAddress: SignalProtocolAddressClass,
|
remoteAddress: SignalProtocolAddressClass,
|
||||||
options?: any
|
options?: { messageKeysLimit?: number | boolean }
|
||||||
);
|
);
|
||||||
closeOpenSessionForDevice: () => Promise<void>;
|
closeOpenSessionForDevice: () => Promise<void>;
|
||||||
decryptPreKeyWhisperMessage: (
|
decryptPreKeyWhisperMessage: (
|
||||||
|
|
|
@ -937,7 +937,8 @@ class MessageReceiverInner extends EventTarget {
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
|
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
|
||||||
window.textsecure.storage.protocol
|
window.textsecure.storage.protocol,
|
||||||
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
const me = {
|
const me = {
|
||||||
|
@ -1096,6 +1097,12 @@ class MessageReceiverInner extends EventTarget {
|
||||||
error.identityKey
|
error.identityKey
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (envelope.timestamp && envelope.timestamp.toNumber) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
envelope.timestamp = envelope.timestamp.toNumber();
|
||||||
|
}
|
||||||
|
|
||||||
const ev = new Event('error');
|
const ev = new Event('error');
|
||||||
ev.error = errorToThrow;
|
ev.error = errorToThrow;
|
||||||
ev.proto = envelope;
|
ev.proto = envelope;
|
||||||
|
|
|
@ -285,7 +285,7 @@
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/signal_protocol_store.js",
|
"path": "js/signal_protocol_store.js",
|
||||||
"line": " await ConversationController.load();",
|
"line": " await ConversationController.load();",
|
||||||
"lineNumber": 1022,
|
"lineNumber": 1035,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-06-12T14:20:09.936Z"
|
"updated": "2020-06-12T14:20:09.936Z"
|
||||||
},
|
},
|
||||||
|
|
5
ts/window.d.ts
vendored
5
ts/window.d.ts
vendored
|
@ -557,7 +557,10 @@ export class CertificateValidatorType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SecretSessionCipherClass {
|
export class SecretSessionCipherClass {
|
||||||
constructor(storage: StorageType);
|
constructor(
|
||||||
|
storage: StorageType,
|
||||||
|
options?: { messageKeysLimit?: number | boolean }
|
||||||
|
);
|
||||||
decrypt: (
|
decrypt: (
|
||||||
validator: CertificateValidatorType,
|
validator: CertificateValidatorType,
|
||||||
ciphertext: ArrayBuffer,
|
ciphertext: ArrayBuffer,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue