Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify device with recovery key/passphrase #2506

Closed
Lopuch opened this issue Jul 8, 2022 · 13 comments
Closed

Verify device with recovery key/passphrase #2506

Lopuch opened this issue Jul 8, 2022 · 13 comments

Comments

@Lopuch
Copy link

Lopuch commented Jul 8, 2022

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?

@Lopuch Lopuch changed the title Verify device with secret key/passphrase Verify device with recovery key/passphrase Jul 8, 2022
@genjudev
Copy link

genjudev commented Jul 12, 2022

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
Copy link

@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
Copy link

genjudev commented Feb 7, 2023

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
Copy link

AbdullahQureshi1080 commented Feb 8, 2023

@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

  • The sender's device has not sent us the keys for this message.
  • Backup has an invalid signature from verified device. (OLM.BAD_MESSAGE_MAC)
  • The secure channel with the sender was corrupted. Trying to create a new secure channel and re-requesting the keys.

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
Copy link

genjudev commented Feb 9, 2023

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?
#2506 (comment)

@AbdullahQureshi1080
Copy link

AbdullahQureshi1080 commented Feb 10, 2023

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?
#2506 (comment)

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
Copy link

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
Copy link

AbdullahQureshi1080 commented Feb 10, 2023

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
Copy link

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

@genjudev
Copy link

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
Copy link

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
Copy link

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

@richvdh
Copy link
Member

richvdh commented Oct 18, 2024

Is it currently possible to verify a session with a recovery key or not?

Yes, provided your private cross-signing keys are stored in secret storage.

The documentation on crypto-api has been improved since this issue was opened, so I don't think it's relevant nowadays.

Contributions of better example code are always welcome (cf #430)

@richvdh richvdh closed this as completed Oct 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants