matrix-org / matrix-js-sdk

Matrix Client-Server SDK for JavaScript
Apache License 2.0
1.53k stars 580 forks source link

Verify device with recovery key/passphrase #2506

Open Lopuch opened 2 years ago

Lopuch commented 2 years ago

I try to verify a device with passphrase I created earlier. Unfortunately I cannot find any example or documentation on this topic. I only found this issue but it did not provide enought information.

Can anyone please provide additional info to the issue i linked above or post any sample code?

genjudev commented 2 years ago

Hey,

verify and restoring the key backup are different things. Here some info for key backup restore.

https://spec.matrix.org/v1.2/client-server-api/#server-side-key-backups

The doc does not sum this up, we did this by writing the getSecretStorageKey and set it as cryptCallback

/!\ WARNING NOT SURE IF THIS IS THE CORRECT WAY BUT IT WORKS

also because I needed to change somecode here because of privacy it may not work right away.

private custom_getSecretStorageKey = async ({
        keys: keyInfos,
    }: {
        keys: Record<string, ISecretStorageKeyInfo>;
    }): Promise<[string, Uint8Array] | null> => {
        if (!INPUT_PASSPHRASE) return null;

        let keyId: string | null =
            await client.getDefaultSecretStorageKeyId(); //MatrixClient

        let keyInfo;
        if (keyId) {
            keyInfo = keyInfos[keyId];
            if (!keyInfo) {
                keyId = null;
            }
        }

        if (!keyId) {
            const keyInfoEntries = Object.entries(keyInfos);
            if (keyInfoEntries.length > 1) {
                throw new Error(
                    'Multiple storage key requests not implemented',
                );
            }
            [keyId, keyInfo] = keyInfoEntries[0];
        }

        if (!keyInfo) return null;

        const derivedKey = await deriveKey(
            INPUT_PASSPHRASE
            keyInfo.passphrase.salt,
            keyInfo.passphrase.iterations,
        );

        return [keyId, derivedKey];
    };

create client with cryptoCallback

// create client
 const client = MatrixSDK.createClient({
  // ... your config
  cryptoCallbacks: {getSecretStorageKey: custom_getSecretStorageKey}
});

Restoring

We check if auth_data is in the keyBackup: With auth_data use restoreKeyBackupWithPassword method without use restoreKeyBackupWithSecretStorage

backup = await client.checkKeyBackup();
        if (!backup.backupInfo) {
            throw new Error('Backup broken or not there');
        }
        return backup;
if (
                backup.backupInfo.auth_data?.private_key_salt &&
                backup.backupInfo.auth_data?.private_key_iterations
            ) {
                await client.restoreKeyBackupWithPassword(
                    passphrase,
                    undefined,
                    undefined,
                    backup.backupInfo,
                    {},
                );
            } else {
                try {

            await client.enableKeyBackup(backup.backupInfo);

            if (!backup.trustInfo.usable) {
                // this will also set trust usable and local trust true.
                const recoverInfo =
                    await client.restoreKeyBackupWithSecretStorage(
                        backup.backupInfo,
                        undefined,
                        undefined,
                    );
            }
        } catch (e) {
            console.error(e)
        }
            }
AbdullahQureshi1080 commented 1 year ago

@larsonnn I am working on e2e solution with matrix-js-sdk as well, I need a little help with creating the key backup and then using the backup for my encrypted chats. Any pointers in the right direction would be helpful.

So what I have done so far is have properly initialized the crypto layer, verify the devices needed verification. Now the issue is if I logout and then log back or delete the app I don't have access to my previous encrypted chats. I am using a self hosted synapse server with my own two clients using the matrix-js-sdk in react native.

@Lopuch You can also give me some hints as well.

genjudev commented 1 year ago

Im not sure where you need help exactly.

First you need to create a keybackup.

https://matrix-org.github.io/matrix-js-sdk/23.2.0/classes/MatrixClient.html#createKeyBackupVersion

After that you can restore it.

For debugging I have a tool up where you exactly see whats happening:

https://matrix-debug.vercel.app/

AbdullahQureshi1080 commented 1 year ago

@larsonnn Sorry yes. Been working on that so far I have created the backup, check backup status and then restore backup based on the above snippets. I think it all works fine until I have switch devices or delete the app data and then re-install the app and then try to restore the backup then it seems to not work properly. What happens is that the already sent messages are being decrypted but new messages being sent are not decrypted.

Setup Backup

  async setAndEnableBackup() {
    const client = await this.getClient();
    const keypharse = 'secure';
    if (!client) {
      return alert('Client does not exist');
    }
    const backup = await this.getBackup();
    if (backup && backup.backupInfo) {
      this.restoreBackup(backup, client, keypharse);
    }
  }
}

Check & Create Backup

async getBackup() {
    const client = await this.getClient();
    let backup = await client.checkKeyBackup();
    console.log('KEY BACKUP - checkKeyBackup', backup);

    if (!backup.backupInfo) {
      console.log('KEY BACKUP - createSecureBackup');
      return this.createSecureBackup(client);
      // throw new Error('Backup broken or not there');
    }
    console.log('KEY BACKUP - retrivedBackup');
    return backup;
  }

Creating Backup

  async createSecureBackup() {
    const client = await this.getClient();
    const keypharse = 'secure';
    // const client = await this.getClient();
    console.log('KEY BACKUP - keypharse', keypharse);
    const prepareKeybackup = await client.prepareKeyBackupVersion(keypharse);
    console.log('KEY BACKUP - prepareKeyBackupVersion', prepareKeybackup);
    const newKeybackup = await client.createKeyBackupVersion(prepareKeybackup);
    console.log('KEY BACKUP - createKeyBackupVersion', newKeybackup);
    return newKeybackup;
  }

Restoring Backup

async restoreBackup(backup, client, passphrase) {
    if (
      backup.backupInfo.auth_data?.private_key_salt &&
      backup.backupInfo.auth_data?.private_key_iterations
    ) {
      const backupWithPassword = await client.restoreKeyBackupWithPassword(
        passphrase,
        undefined,
        undefined,
        backup.backupInfo,
        {},
      );
      console.log(
        'KEY BACKUP - restoreKeyBackupWithPassword',
        backupWithPassword,
      );
    } else {
      console.log('KEY BACKUP - enableKeybackup');
      try {
        await client.enableKeyBackup(backup.backupInfo);
        if (!backup.trustInfo.usable) {
          // this will also set trust usable and local trust true.
          const recoverInfo = await client.restoreKeyBackupWithSecretStorage(
            backup.backupInfo,
            undefined,
            undefined,
          );
          console.log('RECOVER INFO', recoverInfo);
        }
      } catch (e) {
        console.error('Error in restore backup', e);
      }
    }
  }

Common errors after deleting and reinstalling the app

What I am trying at the moment

Manually request keys and then see if that works or not but I think it's not that because the client is already trying this and logs the keys were received, downloaded and the store has all the necessary keys so on re-sync it should decrypt the messages but it's not.

genjudev commented 1 year ago

What happens is that the already sent messages are being decrypted but new messages being sent are not decrypted.

Are this messages sent by yourself or incoming messages from other?

When its by yourself, this would be a weird thing. Your Keybackup should be correctly configured because you can decrypt old messages.

Manually request keys and then see if that works or not but I think it's not that because the client is already trying this and logs the keys were received, downloaded and the store has all the necessary keys so on re-sync it should decrypt the messages but it's not.

In your state your backup is not working properly.

Did you configure the custom_getSecretStorageKey callback? https://github.com/matrix-org/matrix-js-sdk/issues/2506#issuecomment-1181549021

AbdullahQureshi1080 commented 1 year ago

What happens is that the already sent messages are being decrypted but new messages being sent are not decrypted.

Are this messages sent by yourself or incoming messages from other?

When its by yourself, this would be a weird thing. Your Keybackup should be correctly configured because you can decrypt old messages.

  • The sender's device has not sent us the keys for this message.
    • this is what it is. The keys are not being sent
  • Backup has an invalid signature from verified device. (OLM.BAD_MESSAGE_MAC)
    • could also be a bad passphrase?!
  • The secure channel with the sender was corrupted. Trying to create a new secure channel and re-requesting the keys.
    • you are trying to decrypt mutlipletimes and have a race condition. I assume thats the case.
      • try to have one pipeline to decrypt messages to avoid race conditions

Manually request keys and then see if that works or not but I think it's not that because the client is already trying this and logs the keys were received, downloaded and the store has all the necessary keys so on re-sync it should decrypt the messages but it's not.

In your state your backup is not working properly.

Did you configure the custom_getSecretStorageKey callback? https://github.com/matrix-org/matrix-js-sdk/issues/2506#issuecomment-1181549021

Yes, messages sent by myself are being decrypted but the ones being received are not.

Well for the keys request, I have add a to-device listener on m.room_key_request and then I manually send an m.forwarded_room_key in reply and that sends the keys but on re-sync it does not decrypt the messages.

client.on('toDeviceEvent', async function (event, room) {
      // console.log('Device Listener ->', event);

      if (event.getType() === 'm.room_key_request') {
        console.log('Device Listener ->  m.room_key_request', event);
        client.sendToDevice('m.forwarded_room_key', {
          messages: {
            [userId]: {
              [deviceId]: {...event.event.content},
            },
          },
        });
      }
})

Well I don't believe that that keyphrase can be bad because I have static value since I'm testing at the moment.

Yes, I believe so that that backups can be the thing causing the issues.

I don't get race condition part, is it that the message are being decrypted again and again if that's so then I doubt that, because either I'm decrypting messages on receiving them or when I make the initial room sync. Not sure if that causes the condition.

No I have tried configured the crypto callback will try that as well. Well I'm not sure what's the purpose for that if you can explain a bit.

I've also created a new issue explaining some tests that or scenario in the following issue that makes things work but I think it's not ideal.

Update I tried to configure the crypto callback but it did not help. The backup still gets invalid if app gets deleted. But this time a new error occurred while it is not being visible on my client, bit element shows that the message being sent and received is from a deleted encrypted session.

genjudev commented 1 year ago

hm its hard to support without the actual code. But Im also no matrix developer. :X

When using the matrix-js-sdk normally you dont need to send the keys manually.

AbdullahQureshi1080 commented 1 year ago

hm its hard to support without the actual code. But Im also no matrix developer. :X

When using the matrix-js-sdk normally you dont need to send the keys manually.

Yes I know. Just as a forced mechanism sending them myself.

Sorry, I understand. But thanks for the support up till now, really appreciated.

Nobody is kindof a matrix developer and if someone trys to go at it then gets lost in a sea of documation.😬

This to a basic project that I am building it's sort of test project to make the whole chat solution work.

It's quite late at my timezone, but will try to push all the latest code by tomorrow.

It would be a drag for you setup the whole mobile dev environment so hoping that the code can help, but if you have the setup just connecting a basic synapses server will get things up and running or you can always use the matrix org one. Let me know if you find issues in my ignorance. 😁

AbdullahQureshi1080 commented 1 year ago

@larsonnn Hey, how's it going? did you get a chance to look at the repo?

genjudev commented 1 year ago

yes, you use the library differently than I do. I'll build a react-native example app. The way I would build it. And see if I get any errors there.

remember, the matrix-js-sdk always uses objects like the MatrixEvent as arguments. It works with side effects everywhere. So if you give the MatrixEvent to a variable, remember that in javascript it is not a copy of an object, but a reference.

So long could not see through the whole code until now.

AbdullahQureshi1080 commented 1 year ago

yes, you use the library differently than I do. I'll build a react-native example app. The way I would build it. And see if I get any errors there.

remember, the matrix-js-sdk always uses objects like the MatrixEvent as arguments. It works with side effects everywhere. So if you give the MatrixEvent to a variable, remember that in javascript it is not a copy of an object, but a reference.

So long could not see through the whole code until now.

Yeah. If you could, that'd be great help.

Understood, I get your point.

No issues, I was also having trouble with bootstrapping cross-signing but managed to figure it out, not sure if that's the right way to do it but I did. Pushed the latest code to the same repo.

Neyl-123 commented 1 year ago

Is it currently possible to verify a session with a recovery key or not? I have a recovery key starting with a capital E that I saved some years ago, but currently all my sessions are not verified. If I click on verify it only lets me verify it with another verified device with I don't have. I seems there was this option (see picture below) but I can't find anything similar. Was this removed or placed somewhere else? Or is it not possible with a GUI and you need to run some code to verify with a recovery key? image