/* global window, libsignal, textsecure */ // eslint-disable-next-line func-names (function() { window.libloki = window.libloki || {}; 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 removeContactPreKeyBundle(pubKey) { await Promise.all([ textsecure.storage.protocol.removeContactPreKey(pubKey), textsecure.storage.protocol.removeContactSignedPreKey(pubKey), ]); } async function verifyFriendRequestAcceptPreKey(pubKey, buffer) { const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContact( pubKey ); if (!storedPreKey) { throw new Error( 'Received a friend request from a pubkey for which no prekey bundle was created' ); } // need to pop the version // eslint-disable-next-line no-unused-vars const version = buffer.readUint8(); const preKeyProto = window.textsecure.protobuf.PreKeyWhisperMessage.decode( buffer ); if (!preKeyProto) { throw new Error( 'Could not decode PreKeyWhisperMessage while attempting to match the preKeyId' ); } const { preKeyId } = preKeyProto; if (storedPreKey.keyId !== preKeyId) { throw new Error( 'Received a preKeyWhisperMessage (friend request accept) from an unknown source' ); } } function getGuardNodes() { return window.Signal.Data.getGuardNodes(); } function updateGuardNodes(nodes) { return window.Signal.Data.updateGuardNodes(nodes); } window.libloki.storage = { getPreKeyBundleForContact, removeContactPreKeyBundle, verifyFriendRequestAcceptPreKey, getGuardNodes, updateGuardNodes, }; // Libloki protocol store const store = window.SignalProtocolStore.prototype; store.storeContactPreKey = async (pubKey, preKey) => { const key = { // id: (autoincrement) identityKeyString: pubKey, publicKey: preKey.publicKey, keyId: preKey.keyId, }; await window.Signal.Data.createOrUpdateContactPreKey(key); }; store.loadContactPreKey = async pubKey => { const preKey = await window.Signal.Data.getContactPreKeyByIdentityKey( pubKey ); if (preKey) { return { id: preKey.id, keyId: preKey.keyId, publicKey: preKey.publicKey, identityKeyString: preKey.identityKeyString, }; } window.log.warn('Failed to fetch contact prekey:', pubKey); return undefined; }; store.loadContactPreKeys = async filters => { const { keyId, identityKeyString } = filters; const keys = await window.Signal.Data.getContactPreKeys( keyId, identityKeyString ); if (keys) { return keys.map(preKey => ({ id: preKey.id, keyId: preKey.keyId, publicKey: preKey.publicKey, identityKeyString: preKey.identityKeyString, })); } window.log.warn('Failed to fetch signed prekey with filters', filters); return undefined; }; store.removeContactPreKey = async pubKey => { await window.Signal.Data.removeContactPreKeyByIdentityKey(pubKey); }; store.clearContactPreKeysStore = async () => { await window.Signal.Data.removeAllContactPreKeys(); }; store.storeContactSignedPreKey = async (pubKey, signedPreKey) => { const key = { // id: (autoincrement) identityKeyString: pubKey, keyId: signedPreKey.keyId, publicKey: signedPreKey.publicKey, signature: signedPreKey.signature, created_at: Date.now(), confirmed: false, }; await window.Signal.Data.createOrUpdateContactSignedPreKey(key); }; store.loadContactSignedPreKey = async pubKey => { const preKey = await window.Signal.Data.getContactSignedPreKeyByIdentityKey( pubKey ); if (preKey) { return { id: preKey.id, identityKeyString: preKey.identityKeyString, publicKey: preKey.publicKey, signature: preKey.signature, created_at: preKey.created_at, keyId: preKey.keyId, confirmed: preKey.confirmed, }; } window.log.warn('Failed to fetch contact signed prekey:', pubKey); return undefined; }; store.loadContactSignedPreKeys = async filters => { const { keyId, identityKeyString } = filters; const keys = await window.Signal.Data.getContactSignedPreKeys( keyId, identityKeyString ); if (keys) { return keys.map(preKey => ({ id: preKey.id, identityKeyString: preKey.identityKeyString, publicKey: preKey.publicKey, signature: preKey.signature, created_at: preKey.created_at, keyId: preKey.keyId, confirmed: preKey.confirmed, })); } window.log.warn( 'Failed to fetch contact signed prekey with filters', filters ); return undefined; }; store.removeContactSignedPreKey = async pubKey => { await window.Signal.Data.removeContactSignedPreKeyByIdentityKey(pubKey); }; store.clearContactSignedPreKeysStore = async () => { await window.Signal.Data.removeAllContactSignedPreKeys(); }; })();