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.
		
		
		
		
		
			
		
			
				
	
	
		
			251 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			251 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			TypeScript
		
	
| import { ConversationController } from '../session/conversations';
 | |
| import { getSodium } from '../session/crypto';
 | |
| import { UserUtils } from '../session/utils';
 | |
| import {
 | |
|   fromArrayBufferToBase64,
 | |
|   fromHex,
 | |
|   toHex,
 | |
| } from '../session/utils/String';
 | |
| import { getOurPubKeyStrFromCache } from '../session/utils/User';
 | |
| import { trigger } from '../shims/events';
 | |
| import {
 | |
|   removeAllContactPreKeys,
 | |
|   removeAllContactSignedPreKeys,
 | |
|   removeAllPreKeys,
 | |
|   removeAllSessions,
 | |
|   removeAllSignedPreKeys,
 | |
| } from '../data/data';
 | |
| import { forceSyncConfigurationNowIfNeeded } from '../session/utils/syncUtils';
 | |
| 
 | |
| /**
 | |
|  * Might throw
 | |
|  */
 | |
| export async function sessionGenerateKeyPair(
 | |
|   seed: ArrayBuffer
 | |
| ): Promise<{ pubKey: ArrayBufferLike; privKey: ArrayBufferLike }> {
 | |
|   const sodium = await getSodium();
 | |
|   const ed25519KeyPair = sodium.crypto_sign_seed_keypair(new Uint8Array(seed));
 | |
|   const x25519PublicKey = sodium.crypto_sign_ed25519_pk_to_curve25519(
 | |
|     ed25519KeyPair.publicKey
 | |
|   );
 | |
|   // prepend version byte (coming from `processKeys(raw_keys)`)
 | |
|   const origPub = new Uint8Array(x25519PublicKey);
 | |
|   const prependedX25519PublicKey = new Uint8Array(33);
 | |
|   prependedX25519PublicKey.set(origPub, 1);
 | |
|   prependedX25519PublicKey[0] = 5;
 | |
|   const x25519SecretKey = sodium.crypto_sign_ed25519_sk_to_curve25519(
 | |
|     ed25519KeyPair.privateKey
 | |
|   );
 | |
| 
 | |
|   // prepend with 05 the public key
 | |
|   const x25519KeyPair = {
 | |
|     pubKey: prependedX25519PublicKey.buffer,
 | |
|     privKey: x25519SecretKey.buffer,
 | |
|     ed25519KeyPair,
 | |
|   };
 | |
| 
 | |
|   return x25519KeyPair;
 | |
| }
 | |
| 
 | |
| const generateKeypair = async (mnemonic: string, mnemonicLanguage: string) => {
 | |
|   let seedHex = window.mnemonic.mn_decode(mnemonic, mnemonicLanguage);
 | |
|   // handle shorter than 32 bytes seeds
 | |
|   const privKeyHexLength = 32 * 2;
 | |
|   if (seedHex.length !== privKeyHexLength) {
 | |
|     seedHex = seedHex.concat('0'.repeat(32));
 | |
|     seedHex = seedHex.substring(0, privKeyHexLength);
 | |
|   }
 | |
|   const seed = fromHex(seedHex);
 | |
|   return sessionGenerateKeyPair(seed);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Sign in with a recovery phrase. We won't try to recover an existing profile name
 | |
|  * @param mnemonic the mnemonic the user duly saved in a safe place. We will restore his sessionID based on this.
 | |
|  * @param mnemonicLanguage 'english' only is supported
 | |
|  * @param profileName the displayName to use for this user
 | |
|  */
 | |
| export async function signInWithRecovery(
 | |
|   mnemonic: string,
 | |
|   mnemonicLanguage: string,
 | |
|   profileName: string
 | |
| ) {
 | |
|   return registerSingleDevice(mnemonic, mnemonicLanguage, profileName);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sign in with a recovery phrase but trying to recover display name and avatar from the first encountered configuration message.
 | |
|  * @param mnemonic the mnemonic the user duly saved in a safe place. We will restore his sessionID based on this.
 | |
|  * @param mnemonicLanguage 'english' only is supported
 | |
|  */
 | |
| export async function signInByLinkingDevice(
 | |
|   mnemonic: string,
 | |
|   mnemonicLanguage: string
 | |
| ) {
 | |
|   if (!mnemonic) {
 | |
|     throw new Error(
 | |
|       'Session always needs a mnemonic. Either generated or given by the user'
 | |
|     );
 | |
|   }
 | |
|   if (!mnemonicLanguage) {
 | |
|     throw new Error('We always needs a mnemonicLanguage');
 | |
|   }
 | |
| 
 | |
|   const identityKeyPair = await generateKeypair(mnemonic, mnemonicLanguage);
 | |
|   UserUtils.setSignInByLinking(true);
 | |
|   await createAccount(identityKeyPair);
 | |
|   UserUtils.saveRecoveryPhrase(mnemonic);
 | |
|   await clearSessionsAndPreKeys();
 | |
|   const pubKeyString = toHex(identityKeyPair.pubKey);
 | |
| 
 | |
|   // await for the first configuration message to come in.
 | |
|   await registrationDone(pubKeyString, '');
 | |
| }
 | |
| /**
 | |
|  * This is a signup. User has no recovery and does not try to link a device
 | |
|  * @param mnemonic The mnemonic generated on first app loading and to use for this brand new user
 | |
|  * @param mnemonicLanguage only 'english' is supported
 | |
|  * @param profileName the display name to register toi
 | |
|  */
 | |
| export async function registerSingleDevice(
 | |
|   generatedMnemonic: string,
 | |
|   mnemonicLanguage: string,
 | |
|   profileName: string
 | |
| ) {
 | |
|   if (!generatedMnemonic) {
 | |
|     throw new Error(
 | |
|       'Session always needs a mnemonic. Either generated or given by the user'
 | |
|     );
 | |
|   }
 | |
|   if (!profileName) {
 | |
|     throw new Error('We always needs a profileName');
 | |
|   }
 | |
|   if (!mnemonicLanguage) {
 | |
|     throw new Error('We always needs a mnemonicLanguage');
 | |
|   }
 | |
| 
 | |
|   const identityKeyPair = await generateKeypair(
 | |
|     generatedMnemonic,
 | |
|     mnemonicLanguage
 | |
|   );
 | |
| 
 | |
|   await createAccount(identityKeyPair);
 | |
|   UserUtils.saveRecoveryPhrase(generatedMnemonic);
 | |
|   await clearSessionsAndPreKeys();
 | |
|   await UserUtils.setLastProfileUpdateTimestamp(Date.now());
 | |
| 
 | |
|   const pubKeyString = toHex(identityKeyPair.pubKey);
 | |
|   await registrationDone(pubKeyString, profileName);
 | |
| }
 | |
| 
 | |
| export async function generateMnemonic(language = 'english') {
 | |
|   // Note: 4 bytes are converted into 3 seed words, so length 12 seed words
 | |
|   // (13 - 1 checksum) are generated using 12 * 4 / 3 = 16 bytes.
 | |
|   const seedSize = 16;
 | |
|   const seed = (await getSodium()).randombytes_buf(seedSize);
 | |
|   const hex = toHex(seed);
 | |
|   return window.mnemonic.mn_encode(hex, language);
 | |
| }
 | |
| 
 | |
| export async function clearSessionsAndPreKeys() {
 | |
|   window.log.info('clearing all sessions');
 | |
|   // During secondary device registration we need to keep our prekeys sent
 | |
|   // to other pubkeys
 | |
|   await Promise.all([
 | |
|     removeAllPreKeys(),
 | |
|     removeAllSignedPreKeys(),
 | |
|     removeAllContactPreKeys(),
 | |
|     removeAllContactSignedPreKeys(),
 | |
|     removeAllSessions(),
 | |
|   ]);
 | |
| }
 | |
| 
 | |
| export async function deleteAccount(reason?: string) {
 | |
|   const deleteEverything = async () => {
 | |
|     window.log.info(
 | |
|       'configuration message sent successfully. Deleting everything'
 | |
|     );
 | |
|     await window.Signal.Logs.deleteAll();
 | |
|     await window.Signal.Data.removeAll();
 | |
|     await window.Signal.Data.close();
 | |
|     await window.Signal.Data.removeDB();
 | |
|     await window.Signal.Data.removeOtherData();
 | |
|     // 'unlink' => toast will be shown on app restart
 | |
|     window.localStorage.setItem('restart-reason', reason || '');
 | |
|   };
 | |
|   try {
 | |
|     window.log.info('DeleteAccount => Sending a last SyncConfiguration');
 | |
|     // be sure to wait for the message being effectively sent. Otherwise we won't be able to encrypt it for our devices !
 | |
|     await forceSyncConfigurationNowIfNeeded(true);
 | |
|     window.log.info('Last configuration message sent!');
 | |
|     await deleteEverything();
 | |
|   } catch (error) {
 | |
|     window.log.error(
 | |
|       'Something went wrong deleting all data:',
 | |
|       error && error.stack ? error.stack : error
 | |
|     );
 | |
|     try {
 | |
|       await deleteEverything();
 | |
|     } catch (e) {
 | |
|       window.log.error(e);
 | |
|     }
 | |
|   }
 | |
|   window.restart();
 | |
| }
 | |
| 
 | |
| async function createAccount(identityKeyPair: any) {
 | |
|   const sodium = await getSodium();
 | |
|   let password = fromArrayBufferToBase64(sodium.randombytes_buf(16));
 | |
|   password = password.substring(0, password.length - 2);
 | |
| 
 | |
|   await Promise.all([
 | |
|     window.textsecure.storage.remove('identityKey'),
 | |
|     window.textsecure.storage.remove('signaling_key'),
 | |
|     window.textsecure.storage.remove('password'),
 | |
|     window.textsecure.storage.remove('registrationId'),
 | |
|     window.textsecure.storage.remove('number_id'),
 | |
|     window.textsecure.storage.remove('device_name'),
 | |
|     window.textsecure.storage.remove('userAgent'),
 | |
|     window.textsecure.storage.remove('read-receipt-setting'),
 | |
|     window.textsecure.storage.remove('typing-indicators-setting'),
 | |
|     window.textsecure.storage.remove('regionCode'),
 | |
|   ]);
 | |
| 
 | |
|   // update our own identity key, which may have changed
 | |
|   // if we're relinking after a reinstall on the master device
 | |
|   const pubKeyString = toHex(identityKeyPair.pubKey);
 | |
| 
 | |
|   await window.textsecure.storage.put('identityKey', identityKeyPair);
 | |
|   await window.textsecure.storage.put('password', password);
 | |
|   await window.textsecure.storage.put('read-receipt-setting', false);
 | |
| 
 | |
|   // Enable typing indicators by default
 | |
|   await window.textsecure.storage.put(
 | |
|     'typing-indicators-setting',
 | |
|     Boolean(true)
 | |
|   );
 | |
| 
 | |
|   await window.textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1);
 | |
| }
 | |
| 
 | |
| async function registrationDone(ourPubkey: string, displayName: string) {
 | |
|   window.log.info('registration done');
 | |
| 
 | |
|   window.textsecure.storage.put('primaryDevicePubKey', ourPubkey);
 | |
| 
 | |
|   // Ensure that we always have a conversation for ourself
 | |
|   const conversation = await ConversationController.getInstance().getOrCreateAndWait(
 | |
|     ourPubkey,
 | |
|     'private'
 | |
|   );
 | |
|   await conversation.setLokiProfile({ displayName });
 | |
|   const user = {
 | |
|     ourNumber: getOurPubKeyStrFromCache(),
 | |
|     ourPrimary: window.textsecure.storage.get('primaryDevicePubKey'),
 | |
|   };
 | |
|   trigger('userChanged', user);
 | |
|   window.Whisper.Registration.markDone();
 | |
|   window.log.info('dispatching registration event');
 | |
|   trigger('registration_done');
 | |
| }
 |