You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			168 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			168 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
/* global window, libsignal, textsecure, StringView, log */
 | 
						|
 | 
						|
// eslint-disable-next-line func-names
 | 
						|
(function () {
 | 
						|
  window.libloki = window.libloki || {};
 | 
						|
 | 
						|
  class FallBackDecryptionError extends Error { }
 | 
						|
 | 
						|
  const IV_LENGTH = 16;
 | 
						|
 | 
						|
  class FallBackSessionCipher {
 | 
						|
 | 
						|
    constructor(address) {
 | 
						|
      this.identityKeyString = address.getName();
 | 
						|
      this.pubKey = StringView.hexToArrayBuffer(address.getName());
 | 
						|
    }
 | 
						|
 | 
						|
    async encrypt(plaintext) {
 | 
						|
      const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
 | 
						|
      const myPrivateKey = myKeyPair.privKey;
 | 
						|
      const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
 | 
						|
      const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
 | 
						|
      const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv);
 | 
						|
      const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
 | 
						|
      ivAndCiphertext.set(new Uint8Array(iv));
 | 
						|
      ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
 | 
						|
      return {
 | 
						|
        type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST,
 | 
						|
        body: ivAndCiphertext,
 | 
						|
        registrationId: null,
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    async decrypt(ivAndCiphertext) {
 | 
						|
      const iv = ivAndCiphertext.slice(0, IV_LENGTH);
 | 
						|
      const cipherText = ivAndCiphertext.slice(IV_LENGTH);
 | 
						|
      const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
 | 
						|
      const myPrivateKey = myKeyPair.privKey;
 | 
						|
      const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
 | 
						|
      try {
 | 
						|
        return await libsignal.crypto.decrypt(symmetricKey, cipherText, iv);
 | 
						|
      }
 | 
						|
      catch (e) {
 | 
						|
        throw new FallBackDecryptionError(`Could not decrypt message from ${this.identityKeyString} using FallBack encryption.`);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async function getPreKeyBundleForContact(pubKey) {
 | 
						|
    const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
 | 
						|
    const identityKey = myKeyPair.pubKey;
 | 
						|
 | 
						|
    // Retrieve ids. The ids stored are always the latest generated + 1
 | 
						|
    const signedKeyId = textsecure.storage.get('signedKeyId', 2) - 1;
 | 
						|
 | 
						|
    const [signedKey, preKey] = await Promise.all([
 | 
						|
      textsecure.storage.protocol.loadSignedPreKey(signedKeyId),
 | 
						|
      new Promise(async resolve => {
 | 
						|
        // retrieve existing prekey if we already generated one for that recipient
 | 
						|
        const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContact(
 | 
						|
          pubKey
 | 
						|
        );
 | 
						|
        if (storedPreKey) {
 | 
						|
          resolve({ pubKey: storedPreKey.pubKey, keyId: storedPreKey.keyId });
 | 
						|
        } else {
 | 
						|
          // generate and store new prekey
 | 
						|
          const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
 | 
						|
          textsecure.storage.put('maxPreKeyId', preKeyId + 1);
 | 
						|
          const newPreKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
 | 
						|
          await textsecure.storage.protocol.storePreKey(
 | 
						|
            newPreKey.keyId,
 | 
						|
            newPreKey.keyPair,
 | 
						|
            pubKey
 | 
						|
          );
 | 
						|
          resolve({ pubKey: newPreKey.keyPair.pubKey, keyId: preKeyId });
 | 
						|
        }
 | 
						|
      }),
 | 
						|
    ]);
 | 
						|
 | 
						|
    return {
 | 
						|
      identityKey: new Uint8Array(identityKey),
 | 
						|
      deviceId: 1, // TODO: fetch from somewhere
 | 
						|
      preKeyId: preKey.keyId,
 | 
						|
      signedKeyId,
 | 
						|
      preKey: new Uint8Array(preKey.pubKey),
 | 
						|
      signedKey: new Uint8Array(signedKey.pubKey),
 | 
						|
      signature: new Uint8Array(signedKey.signature),
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  async function saveContactPreKeyBundle({
 | 
						|
    pubKey,
 | 
						|
    preKeyId,
 | 
						|
    preKey,
 | 
						|
    signedKeyId,
 | 
						|
    signedKey,
 | 
						|
    signature,
 | 
						|
  }) {
 | 
						|
    const signedPreKey = {
 | 
						|
      keyId: signedKeyId,
 | 
						|
      publicKey: signedKey,
 | 
						|
      signature,
 | 
						|
    };
 | 
						|
 | 
						|
    const signedKeyPromise = textsecure.storage.protocol.storeContactSignedPreKey(
 | 
						|
      pubKey,
 | 
						|
      signedPreKey
 | 
						|
    );
 | 
						|
 | 
						|
    const preKeyObject = {
 | 
						|
      publicKey: preKey,
 | 
						|
      keyId: preKeyId,
 | 
						|
    };
 | 
						|
 | 
						|
    const preKeyPromise = textsecure.storage.protocol.storeContactPreKey(
 | 
						|
      pubKey,
 | 
						|
      preKeyObject
 | 
						|
    );
 | 
						|
 | 
						|
    await Promise.all([signedKeyPromise, preKeyPromise]);
 | 
						|
  }
 | 
						|
 | 
						|
  async function removeContactPreKeyBundle(pubKey) {
 | 
						|
    await Promise.all([
 | 
						|
      textsecure.storage.protocol.removeContactPreKey(pubKey),
 | 
						|
      textsecure.storage.protocol.removeContactSignedPreKey(pubKey),
 | 
						|
    ]);
 | 
						|
  }
 | 
						|
 | 
						|
  async function sendFriendRequestAccepted(pubKey) {
 | 
						|
    return sendEmptyMessage(pubKey);
 | 
						|
  }
 | 
						|
 | 
						|
  async function sendEmptyMessage(pubKey) {
 | 
						|
    // empty content message
 | 
						|
    const content = new textsecure.protobuf.Content();
 | 
						|
 | 
						|
    // will be called once the transmission succeeded or failed
 | 
						|
    const callback = res => {
 | 
						|
      if (res.errors.length > 0) {
 | 
						|
        res.errors.forEach(error => log.error(error));
 | 
						|
      } else {
 | 
						|
        log.info('empty message sent successfully');
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const options = {};
 | 
						|
    // send an empty message. The logic in ougoing_message will attach the prekeys.
 | 
						|
    const outgoingMessage = new textsecure.OutgoingMessage(
 | 
						|
      null, // server
 | 
						|
      Date.now(), // timestamp,
 | 
						|
      [pubKey], // numbers
 | 
						|
      content, // message
 | 
						|
      true, // silent
 | 
						|
      callback, // callback
 | 
						|
      options
 | 
						|
    );
 | 
						|
    await outgoingMessage.sendToNumber(pubKey);
 | 
						|
  }
 | 
						|
 | 
						|
  window.libloki.FallBackSessionCipher = FallBackSessionCipher;
 | 
						|
  window.libloki.getPreKeyBundleForContact = getPreKeyBundleForContact;
 | 
						|
  window.libloki.FallBackDecryptionError = FallBackDecryptionError;
 | 
						|
  window.libloki.saveContactPreKeyBundle = saveContactPreKeyBundle;
 | 
						|
  window.libloki.removeContactPreKeyBundle = removeContactPreKeyBundle;
 | 
						|
  window.libloki.sendFriendRequestAccepted = sendFriendRequestAccepted;
 | 
						|
  window.libloki.sendEmptyMessage = sendEmptyMessage;
 | 
						|
})();
 |