Open Lopuch opened 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
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
const client = MatrixSDK.createClient({
// ... your config
cryptoCallbacks: {getSecretStorageKey: custom_getSecretStorageKey}
});
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)
}
}
@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.
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:
@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.
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
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.
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.
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. 😁
@larsonnn Hey, how's it going? did you get a chance to look at the repo?
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.
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.
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?
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?