|  |  |  | /* global | 
					
						
							|  |  |  |   window, | 
					
						
							|  |  |  |   libsignal, | 
					
						
							|  |  |  |   textsecure, | 
					
						
							|  |  |  |   StringView, | 
					
						
							|  |  |  |   Multibase, | 
					
						
							|  |  |  |   TextEncoder, | 
					
						
							|  |  |  |   TextDecoder, | 
					
						
							|  |  |  |   dcodeIO | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // eslint-disable-next-line func-names
 | 
					
						
							|  |  |  | (function() { | 
					
						
							|  |  |  |   window.libloki = window.libloki || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   class FallBackDecryptionError extends Error {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const IV_LENGTH = 16; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function DHEncrypt(symmetricKey, plainText) { | 
					
						
							|  |  |  |     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 ivAndCiphertext; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function DHDecrypt(symmetricKey, ivAndCiphertext) { | 
					
						
							|  |  |  |     const iv = ivAndCiphertext.slice(0, IV_LENGTH); | 
					
						
							|  |  |  |     const ciphertext = ivAndCiphertext.slice(IV_LENGTH); | 
					
						
							|  |  |  |     return libsignal.crypto.decrypt(symmetricKey, ciphertext, iv); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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 ivAndCiphertext = await DHEncrypt(symmetricKey, plaintext); | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST, | 
					
						
							|  |  |  |         body: ivAndCiphertext, | 
					
						
							|  |  |  |         registrationId: null, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async decrypt(ivAndCiphertext) { | 
					
						
							|  |  |  |       const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); | 
					
						
							|  |  |  |       const myPrivateKey = myKeyPair.privKey; | 
					
						
							|  |  |  |       const symmetricKey = libsignal.Curve.calculateAgreement( | 
					
						
							|  |  |  |         this.pubKey, | 
					
						
							|  |  |  |         myPrivateKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         return await DHDecrypt(symmetricKey, ivAndCiphertext); | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         throw new FallBackDecryptionError( | 
					
						
							|  |  |  |           `Could not decrypt message from ${ | 
					
						
							|  |  |  |             this.identityKeyString | 
					
						
							|  |  |  |           } using FallBack encryption.`
 | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const base32zIndex = Multibase.names.indexOf('base32z'); | 
					
						
							|  |  |  |   const base32zCode = Multibase.codes[base32zIndex]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function bufferToArrayBuffer(buf) { | 
					
						
							|  |  |  |     const ab = new ArrayBuffer(buf.length); | 
					
						
							|  |  |  |     const view = new Uint8Array(ab); | 
					
						
							|  |  |  |     for (let i = 0; i < buf.length; i += 1) { | 
					
						
							|  |  |  |       view[i] = buf[i]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return ab; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function decodeSnodeAddressToPubKey(snodeAddress) { | 
					
						
							|  |  |  |     const snodeAddressClean = snodeAddress | 
					
						
							|  |  |  |       .replace('.snode', '') | 
					
						
							|  |  |  |       .replace('https://', '') | 
					
						
							|  |  |  |       .replace('http://', ''); | 
					
						
							|  |  |  |     return Multibase.decode(`${base32zCode}${snodeAddressClean}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   class LokiSnodeChannel { | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |       this._ephemeralKeyPair = libsignal.Curve.generateKeyPair(); | 
					
						
							|  |  |  |       // Signal protocol prepends with "0x05"
 | 
					
						
							|  |  |  |       this._ephemeralKeyPair.pubKey = this._ephemeralKeyPair.pubKey.slice(1); | 
					
						
							|  |  |  |       this._ephemeralPubKeyHex = StringView.arrayBufferToHex( | 
					
						
							|  |  |  |         this._ephemeralKeyPair.pubKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       this._cache = {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async _getSymmetricKey(snodeAddress) { | 
					
						
							|  |  |  |       if (snodeAddress in this._cache) { | 
					
						
							|  |  |  |         return this._cache[snodeAddress]; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const ed25519PubKey = decodeSnodeAddressToPubKey(snodeAddress); | 
					
						
							|  |  |  |       const sodium = await window.getSodium(); | 
					
						
							|  |  |  |       const curve25519PubKey = sodium.crypto_sign_ed25519_pk_to_curve25519( | 
					
						
							|  |  |  |         ed25519PubKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const snodePubKeyArrayBuffer = bufferToArrayBuffer(curve25519PubKey); | 
					
						
							|  |  |  |       const symmetricKey = libsignal.Curve.calculateAgreement( | 
					
						
							|  |  |  |         snodePubKeyArrayBuffer, | 
					
						
							|  |  |  |         this._ephemeralKeyPair.privKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       this._cache[snodeAddress] = symmetricKey; | 
					
						
							|  |  |  |       return symmetricKey; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getChannelPublicKeyHex() { | 
					
						
							|  |  |  |       return this._ephemeralPubKeyHex; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async decrypt(snodeAddress, ivAndCiphertextBase64) { | 
					
						
							|  |  |  |       const ivAndCiphertext = dcodeIO.ByteBuffer.wrap( | 
					
						
							|  |  |  |         ivAndCiphertextBase64, | 
					
						
							|  |  |  |         'base64' | 
					
						
							|  |  |  |       ).toArrayBuffer(); | 
					
						
							|  |  |  |       const symmetricKey = await this._getSymmetricKey(snodeAddress); | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         const decrypted = await DHDecrypt(symmetricKey, ivAndCiphertext); | 
					
						
							|  |  |  |         const decoder = new TextDecoder(); | 
					
						
							|  |  |  |         return decoder.decode(decrypted); | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         return ivAndCiphertext; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async encrypt(snodeAddress, plainText) { | 
					
						
							|  |  |  |       if (typeof plainText === 'string') { | 
					
						
							|  |  |  |         const textEncoder = new TextEncoder(); | 
					
						
							|  |  |  |         // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |         plainText = textEncoder.encode(plainText); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const symmetricKey = await this._getSymmetricKey(snodeAddress); | 
					
						
							|  |  |  |       const ciphertext = await DHEncrypt(symmetricKey, plainText); | 
					
						
							|  |  |  |       return dcodeIO.ByteBuffer.wrap(ciphertext).toString('base64'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function decryptToken({ cipherText64, serverPubKey64 }) { | 
					
						
							|  |  |  |     const ivAndCiphertext = new Uint8Array( | 
					
						
							|  |  |  |       dcodeIO.ByteBuffer.fromBase64(cipherText64).toArrayBuffer() | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const serverPubKey = new Uint8Array( | 
					
						
							|  |  |  |       dcodeIO.ByteBuffer.fromBase64(serverPubKey64).toArrayBuffer() | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const { privKey } = await textsecure.storage.protocol.getIdentityKeyPair(); | 
					
						
							|  |  |  |     const symmetricKey = libsignal.Curve.calculateAgreement( | 
					
						
							|  |  |  |       serverPubKey, | 
					
						
							|  |  |  |       privKey | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const token = await DHDecrypt(symmetricKey, ivAndCiphertext); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const tokenString = dcodeIO.ByteBuffer.wrap(token).toString('utf8'); | 
					
						
							|  |  |  |     return tokenString; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const snodeCipher = new LokiSnodeChannel(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   window.libloki.crypto = { | 
					
						
							|  |  |  |     DHEncrypt, | 
					
						
							|  |  |  |     DHDecrypt, | 
					
						
							|  |  |  |     FallBackSessionCipher, | 
					
						
							|  |  |  |     FallBackDecryptionError, | 
					
						
							|  |  |  |     snodeCipher, | 
					
						
							|  |  |  |     decryptToken, | 
					
						
							|  |  |  |     // for testing
 | 
					
						
							|  |  |  |     _LokiSnodeChannel: LokiSnodeChannel, | 
					
						
							|  |  |  |     _decodeSnodeAddressToPubKey: decodeSnodeAddressToPubKey, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | })(); |