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 = [];
 | 
			
		||||
 | 
			
		||||
function SecretSessionCipher(storage) {
 | 
			
		||||
function SecretSessionCipher(storage, options) {
 | 
			
		||||
  this.storage = storage;
 | 
			
		||||
 | 
			
		||||
  // We do this on construction because libsignal won't be available when this file loads
 | 
			
		||||
  const { SessionCipher } = libsignal;
 | 
			
		||||
  this.SessionCipher = SessionCipher;
 | 
			
		||||
 | 
			
		||||
  this.options = options || {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const CIPHERTEXT_VERSION = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -291,7 +293,8 @@ SecretSessionCipher.prototype = {
 | 
			
		|||
 | 
			
		||||
    const sessionCipher = new SessionCipher(
 | 
			
		||||
      signalProtocolStore,
 | 
			
		||||
      destinationAddress
 | 
			
		||||
      destinationAddress,
 | 
			
		||||
      this.options
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const message = await sessionCipher.encrypt(paddedPlaintext);
 | 
			
		||||
| 
						 | 
				
			
			@ -448,7 +451,11 @@ SecretSessionCipher.prototype = {
 | 
			
		|||
    const { SessionCipher } = this;
 | 
			
		||||
    const signalProtocolStore = this.storage;
 | 
			
		||||
 | 
			
		||||
    const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
 | 
			
		||||
    const cipher = new SessionCipher(
 | 
			
		||||
      signalProtocolStore,
 | 
			
		||||
      remoteAddress,
 | 
			
		||||
      this.options
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return cipher.getSessionVersion();
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -458,7 +465,11 @@ SecretSessionCipher.prototype = {
 | 
			
		|||
    const { SessionCipher } = this;
 | 
			
		||||
    const signalProtocolStore = this.storage;
 | 
			
		||||
 | 
			
		||||
    const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
 | 
			
		||||
    const cipher = new SessionCipher(
 | 
			
		||||
      signalProtocolStore,
 | 
			
		||||
      remoteAddress,
 | 
			
		||||
      this.options
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return cipher.getRemoteRegistrationId();
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -468,7 +479,11 @@ SecretSessionCipher.prototype = {
 | 
			
		|||
    const { SessionCipher } = this;
 | 
			
		||||
    const signalProtocolStore = this.storage;
 | 
			
		||||
 | 
			
		||||
    const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
 | 
			
		||||
    const cipher = new SessionCipher(
 | 
			
		||||
      signalProtocolStore,
 | 
			
		||||
      remoteAddress,
 | 
			
		||||
      this.options
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return cipher.closeOpenSessionForDevice();
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -528,12 +543,14 @@ SecretSessionCipher.prototype = {
 | 
			
		|||
      case CiphertextMessage.WHISPER_TYPE:
 | 
			
		||||
        return new SessionCipher(
 | 
			
		||||
          signalProtocolStore,
 | 
			
		||||
          sender
 | 
			
		||||
          sender,
 | 
			
		||||
          this.options
 | 
			
		||||
        ).decryptWhisperMessage(message.content);
 | 
			
		||||
      case CiphertextMessage.PREKEY_TYPE:
 | 
			
		||||
        return new SessionCipher(
 | 
			
		||||
          signalProtocolStore,
 | 
			
		||||
          sender
 | 
			
		||||
          sender,
 | 
			
		||||
          this.options
 | 
			
		||||
        ).decryptPreKeyWhisperMessage(message.content);
 | 
			
		||||
      default:
 | 
			
		||||
        throw new Error(`Unknown type: ${message.type}`);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -163,20 +163,7 @@
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  function SignalProtocolStore() {}
 | 
			
		||||
 | 
			
		||||
  async function _hydrateCache(object, field, itemsPromise, idField) {
 | 
			
		||||
    const items = await itemsPromise;
 | 
			
		||||
| 
						 | 
				
			
			@ -350,8 +337,11 @@
 | 
			
		|||
        if (session) {
 | 
			
		||||
          return session.record;
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        window.log.error(`could not load session ${encodedAddress}`);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        const errorString = error && error.stack ? error.stack : error;
 | 
			
		||||
        window.log.error(
 | 
			
		||||
          `could not load session ${encodedAddress}: ${errorString}`
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return undefined;
 | 
			
		||||
| 
						 | 
				
			
			@ -365,6 +355,7 @@
 | 
			
		|||
 | 
			
		||||
      try {
 | 
			
		||||
        const id = await normalizeEncodedAddress(encodedAddress);
 | 
			
		||||
        const previousData = this.sessions[id];
 | 
			
		||||
 | 
			
		||||
        const data = {
 | 
			
		||||
          id,
 | 
			
		||||
| 
						 | 
				
			
			@ -373,13 +364,22 @@
 | 
			
		|||
          record,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Optimistically update in-memory cache; will revert if save fails.
 | 
			
		||||
        this.sessions[id] = data;
 | 
			
		||||
 | 
			
		||||
        // Note: Because these are cached in memory, we batch and make these database
 | 
			
		||||
        //   updates out of band.
 | 
			
		||||
        this.sessionUpdateBatcher.add(data);
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        window.log.error(`could not store session for ${encodedAddress}`);
 | 
			
		||||
        try {
 | 
			
		||||
          await window.Signal.Data.createOrUpdateSession(data);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          if (previousData) {
 | 
			
		||||
            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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -604,8 +604,21 @@
 | 
			
		|||
    },
 | 
			
		||||
    async _saveIdentityKey(data) {
 | 
			
		||||
      const { id } = data;
 | 
			
		||||
 | 
			
		||||
      const previousData = this.identityKeys[id];
 | 
			
		||||
 | 
			
		||||
      // Optimistically update in-memory cache; will revert if save fails.
 | 
			
		||||
      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) {
 | 
			
		||||
      if (encodedAddress === null || encodedAddress === undefined) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24703,9 +24703,10 @@ libsignal.SessionBuilder = function (storage, remoteAddress) {
 | 
			
		|||
  this.processV3 = builder.processV3.bind(builder);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function SessionCipher(storage, remoteAddress) {
 | 
			
		||||
function SessionCipher(storage, remoteAddress, options) {
 | 
			
		||||
  this.remoteAddress = remoteAddress;
 | 
			
		||||
  this.storage = storage;
 | 
			
		||||
  this.options = options || {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SessionCipher.prototype = {
 | 
			
		||||
| 
						 | 
				
			
			@ -25045,10 +25046,20 @@ SessionCipher.prototype = {
 | 
			
		|||
          return Promise.resolve(); // Already calculated
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (counter - chain.chainKey.counter > 5000) {
 | 
			
		||||
          throw new Error('Over 5000 messages into the future! New: ' + counter + ', Existing: ' + chain.chainKey.counter);
 | 
			
		||||
      var limit = 5000;
 | 
			
		||||
      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) {
 | 
			
		||||
          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(
 | 
			
		||||
    storage: StorageType,
 | 
			
		||||
    remoteAddress: SignalProtocolAddressClass,
 | 
			
		||||
    options?: any
 | 
			
		||||
    options?: { messageKeysLimit?: number | boolean }
 | 
			
		||||
  );
 | 
			
		||||
  closeOpenSessionForDevice: () => Promise<void>;
 | 
			
		||||
  decryptPreKeyWhisperMessage: (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -937,7 +937,8 @@ class MessageReceiverInner extends EventTarget {
 | 
			
		|||
      options
 | 
			
		||||
    );
 | 
			
		||||
    const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
 | 
			
		||||
      window.textsecure.storage.protocol
 | 
			
		||||
      window.textsecure.storage.protocol,
 | 
			
		||||
      options
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const me = {
 | 
			
		||||
| 
						 | 
				
			
			@ -1096,6 +1097,12 @@ class MessageReceiverInner extends EventTarget {
 | 
			
		|||
            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');
 | 
			
		||||
        ev.error = errorToThrow;
 | 
			
		||||
        ev.proto = envelope;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -285,7 +285,7 @@
 | 
			
		|||
    "rule": "jQuery-load(",
 | 
			
		||||
    "path": "js/signal_protocol_store.js",
 | 
			
		||||
    "line": "      await ConversationController.load();",
 | 
			
		||||
    "lineNumber": 1022,
 | 
			
		||||
    "lineNumber": 1035,
 | 
			
		||||
    "reasonCategory": "falseMatch",
 | 
			
		||||
    "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 {
 | 
			
		||||
  constructor(storage: StorageType);
 | 
			
		||||
  constructor(
 | 
			
		||||
    storage: StorageType,
 | 
			
		||||
    options?: { messageKeysLimit?: number | boolean }
 | 
			
		||||
  );
 | 
			
		||||
  decrypt: (
 | 
			
		||||
    validator: CertificateValidatorType,
 | 
			
		||||
    ciphertext: ArrayBuffer,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue