You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Refactor key backup recovery to prepare for rust (#3708)
* Refactor key backup recovery to prepare for rust * code review * quick doc format * code review fix
This commit is contained in:
@ -229,6 +229,116 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
expect(event.getContent()).toEqual("testytest");
|
expect(event.getContent()).toEqual("testytest");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("recover from backup", () => {
|
||||||
|
oldBackendOnly("can restore from backup (Curve25519 version)", async function () {
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
await aliceClient.startClient();
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
const fullBackup = {
|
||||||
|
rooms: {
|
||||||
|
[ROOM_ID]: {
|
||||||
|
sessions: {
|
||||||
|
[testData.MEGOLM_SESSION_DATA.session_id]: testData.CURVE25519_KEY_BACKUP_DATA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMock.get("express:/_matrix/client/v3/room_keys/keys", fullBackup);
|
||||||
|
|
||||||
|
const check = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
|
||||||
|
let onKeyCached: () => void;
|
||||||
|
const awaitKeyCached = new Promise<void>((resolve) => {
|
||||||
|
onKeyCached = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await aliceClient.restoreKeyBackupWithRecoveryKey(
|
||||||
|
testData.BACKUP_DECRYPTION_KEY_BASE58,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
check!.backupInfo!,
|
||||||
|
{
|
||||||
|
cacheCompleteCallback: () => onKeyCached(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.imported).toStrictEqual(1);
|
||||||
|
|
||||||
|
await awaitKeyCached;
|
||||||
|
});
|
||||||
|
|
||||||
|
oldBackendOnly("recover specific session from backup", async function () {
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
await aliceClient.startClient();
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
fetchMock.get(
|
||||||
|
"express:/_matrix/client/v3/room_keys/keys/:room_id/:session_id",
|
||||||
|
testData.CURVE25519_KEY_BACKUP_DATA,
|
||||||
|
);
|
||||||
|
|
||||||
|
const check = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
|
||||||
|
const result = await aliceClient.restoreKeyBackupWithRecoveryKey(
|
||||||
|
testData.BACKUP_DECRYPTION_KEY_BASE58,
|
||||||
|
ROOM_ID,
|
||||||
|
testData.MEGOLM_SESSION_DATA.session_id,
|
||||||
|
check!.backupInfo!,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.imported).toStrictEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
oldBackendOnly("Fails on bad recovery key", async function () {
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
await aliceClient.startClient();
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
const fullBackup = {
|
||||||
|
rooms: {
|
||||||
|
[ROOM_ID]: {
|
||||||
|
sessions: {
|
||||||
|
[testData.MEGOLM_SESSION_DATA.session_id]: testData.CURVE25519_KEY_BACKUP_DATA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMock.get("express:/_matrix/client/v3/room_keys/keys", fullBackup);
|
||||||
|
|
||||||
|
const check = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
aliceClient.restoreKeyBackupWithRecoveryKey(
|
||||||
|
"EsTx A7Xn aNFF k3jH zpV3 MQoN LJEg mscC HecF 982L wC77 mYQD",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
check!.backupInfo!,
|
||||||
|
),
|
||||||
|
).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("backupLoop", () => {
|
describe("backupLoop", () => {
|
||||||
it("Alice should upload known keys when backup is enabled", async function () {
|
it("Alice should upload known keys when backup is enabled", async function () {
|
||||||
// 404 means that there is no active backup
|
// 404 means that there is no active backup
|
||||||
|
@ -26,10 +26,15 @@ python -m venv env
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import base58
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
from cryptography.hazmat.primitives.asymmetric import ed25519, x25519
|
from cryptography.hazmat.primitives.asymmetric import ed25519, x25519
|
||||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||||
|
from cryptography.hazmat.primitives import hashes, padding, hmac
|
||||||
|
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
|
||||||
from random import randbytes, seed
|
from random import randbytes, seed
|
||||||
|
|
||||||
ALICE_DATA = {
|
ALICE_DATA = {
|
||||||
@ -77,6 +82,7 @@ def main() -> None:
|
|||||||
import {{ IDeviceKeys, IMegolmSessionData }} from "../../../src/@types/crypto";
|
import {{ IDeviceKeys, IMegolmSessionData }} from "../../../src/@types/crypto";
|
||||||
import {{ IDownloadKeyResult }} from "../../../src";
|
import {{ IDownloadKeyResult }} from "../../../src";
|
||||||
import {{ KeyBackupInfo }} from "../../../src/crypto-api";
|
import {{ KeyBackupInfo }} from "../../../src/crypto-api";
|
||||||
|
import {{ IKeyBackupSession }} from "../../../src/crypto/keybackup";
|
||||||
|
|
||||||
/* eslint-disable comma-dangle */
|
/* eslint-disable comma-dangle */
|
||||||
|
|
||||||
@ -186,6 +192,10 @@ def build_test_data(user_data, prefix = "") -> str:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backed_up_room_key = encrypt_megolm_key_for_backup(additional_exported_room_key, backup_decryption_key.public_key())
|
||||||
|
|
||||||
|
backup_recovery_key = export_recovery_key(user_data["B64_BACKUP_DECRYPTION_KEY"])
|
||||||
|
|
||||||
return f"""\
|
return f"""\
|
||||||
export const {prefix}TEST_USER_ID = "{user_data['TEST_USER_ID']}";
|
export const {prefix}TEST_USER_ID = "{user_data['TEST_USER_ID']}";
|
||||||
export const {prefix}TEST_DEVICE_ID = "{user_data['TEST_DEVICE_ID']}";
|
export const {prefix}TEST_DEVICE_ID = "{user_data['TEST_DEVICE_ID']}";
|
||||||
@ -220,9 +230,15 @@ export const {prefix}SIGNED_CROSS_SIGNING_KEYS_DATA: Partial<IDownloadKeyResult>
|
|||||||
json.dumps(build_cross_signing_keys_data(user_data), indent=4)
|
json.dumps(build_cross_signing_keys_data(user_data), indent=4)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Signed OTKs, returned by `POST /keys/claim` */
|
||||||
|
export const {prefix}ONE_TIME_KEYS = { json.dumps(otks, indent=4) };
|
||||||
|
|
||||||
/** base64-encoded backup decryption (private) key */
|
/** base64-encoded backup decryption (private) key */
|
||||||
export const {prefix}BACKUP_DECRYPTION_KEY_BASE64 = "{ user_data['B64_BACKUP_DECRYPTION_KEY'] }";
|
export const {prefix}BACKUP_DECRYPTION_KEY_BASE64 = "{ user_data['B64_BACKUP_DECRYPTION_KEY'] }";
|
||||||
|
|
||||||
|
/** Backup decryption key in export format */
|
||||||
|
export const {prefix}BACKUP_DECRYPTION_KEY_BASE58 = "{ backup_recovery_key }";
|
||||||
|
|
||||||
/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{{roomId}}/{{sessionId}}` */
|
/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{{roomId}}/{{sessionId}}` */
|
||||||
export const {prefix}SIGNED_BACKUP_DATA: KeyBackupInfo = { json.dumps(backup_data, indent=4) };
|
export const {prefix}SIGNED_BACKUP_DATA: KeyBackupInfo = { json.dumps(backup_data, indent=4) };
|
||||||
|
|
||||||
@ -236,8 +252,8 @@ export const {prefix}MEGOLM_SESSION_DATA: IMegolmSessionData = {
|
|||||||
json.dumps(additional_exported_room_key, indent=4)
|
json.dumps(additional_exported_room_key, indent=4)
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Signed OTKs, returned by `POST /keys/claim` */
|
/** The key from {prefix}MEGOLM_SESSION_DATA, encrypted for backup using `m.megolm_backup.v1.curve25519-aes-sha2` algorithm*/
|
||||||
export const {prefix}ONE_TIME_KEYS = { json.dumps(otks, indent=4) };
|
export const {prefix}CURVE25519_KEY_BACKUP_DATA: IKeyBackupSession = {json.dumps(backed_up_room_key, indent=4)};
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -367,6 +383,97 @@ def build_exported_megolm_key() -> dict:
|
|||||||
|
|
||||||
return megolm_export
|
return megolm_export
|
||||||
|
|
||||||
|
def encrypt_megolm_key_for_backup(session_data: dict, backup_public_key: x25519.X25519PublicKey) -> dict:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Encrypts an exported megolm key for key backup, using the m.megolm_backup.v1.curve25519-aes-sha2 algorithm.
|
||||||
|
"""
|
||||||
|
data = encode_canonical_json(session_data)
|
||||||
|
|
||||||
|
# Generate an ephemeral curve25519 key, and perform an ECDH with the ephemeral key
|
||||||
|
# and the backup’s public key to generate a shared secret.
|
||||||
|
# The public half of the ephemeral key, encoded using unpadded base64,
|
||||||
|
# becomes the ephemeral property of the session_data.
|
||||||
|
ephemeral_keypair = x25519.X25519PrivateKey.from_private_bytes(randbytes(32))
|
||||||
|
shared_secret = ephemeral_keypair.exchange(backup_public_key)
|
||||||
|
ephemeral = encode_base64(ephemeral_keypair.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw))
|
||||||
|
|
||||||
|
# Using the shared secret, generate 80 bytes by performing an HKDF using SHA-256 as the hash,
|
||||||
|
# with a salt of 32 bytes of 0, and with the empty string as the info.
|
||||||
|
# The first 32 bytes are used as the AES key, the next 32 bytes are used as the MAC key,
|
||||||
|
# and the last 16 bytes are used as the AES initialization vector.
|
||||||
|
salt = bytes(32)
|
||||||
|
info = b""
|
||||||
|
|
||||||
|
hkdf = HKDF(
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
length=80,
|
||||||
|
salt=salt,
|
||||||
|
info=info,
|
||||||
|
)
|
||||||
|
|
||||||
|
raw_key = hkdf.derive(shared_secret)
|
||||||
|
aes_key = raw_key[:32]
|
||||||
|
mac = raw_key[32:64]
|
||||||
|
iv = raw_key[64:80]
|
||||||
|
|
||||||
|
# Stringify the JSON object, and encrypt it using AES-CBC-256 with PKCS#7 padding.
|
||||||
|
# This encrypted data, encoded using unpadded base64, becomes the ciphertext property of the session_data.
|
||||||
|
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
padder = padding.PKCS7(128).padder()
|
||||||
|
padded_data = padder.update(data) + padder.finalize()
|
||||||
|
ct = encryptor.update(padded_data) + encryptor.finalize()
|
||||||
|
cipher_text = encode_base64(ct)
|
||||||
|
|
||||||
|
# Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256 using the MAC key generated above.
|
||||||
|
# The first 8 bytes of the resulting MAC are base64-encoded, and become the mac property of the session_data.
|
||||||
|
h = hmac.HMAC(mac, hashes.SHA256())
|
||||||
|
# h.update(ct)
|
||||||
|
signature = h.finalize()
|
||||||
|
mac = encode_base64(signature[:8])
|
||||||
|
|
||||||
|
encrypted_key = {
|
||||||
|
"first_message_index": 1,
|
||||||
|
"forwarded_count": 0,
|
||||||
|
"is_verified": False,
|
||||||
|
"session_data": {
|
||||||
|
"ciphertext": cipher_text,
|
||||||
|
"ephemeral": ephemeral,
|
||||||
|
"mac": mac
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return encrypted_key
|
||||||
|
|
||||||
|
def export_recovery_key(key_b64: str) -> str:
|
||||||
|
"""
|
||||||
|
Export a private recovery key as a recovery key that can be presented to users.
|
||||||
|
As per spec https://spec.matrix.org/v1.8/client-server-api/#recovery-key
|
||||||
|
"""
|
||||||
|
private_key_bytes = base64.b64decode(key_b64)
|
||||||
|
|
||||||
|
# The 256-bit curve25519 private key is prepended by the bytes 0x8B and 0x01
|
||||||
|
export_bytes = bytearray()
|
||||||
|
export_bytes += b'\x8b'
|
||||||
|
export_bytes += b'\x01'
|
||||||
|
|
||||||
|
export_bytes += private_key_bytes
|
||||||
|
|
||||||
|
# All the bytes in the string above, including the two header bytes,
|
||||||
|
# are XORed together to form a parity byte. This parity byte is appended to the byte string.
|
||||||
|
parity_byte = 0 #b'\x8b' ^ b'\x01'
|
||||||
|
[parity_byte := parity_byte ^ x for x in export_bytes]
|
||||||
|
|
||||||
|
export_bytes += parity_byte.to_bytes(1, 'big')
|
||||||
|
|
||||||
|
# The byte string is encoded using base58
|
||||||
|
recovery_key = base58.b58encode(export_bytes).decode('utf-8')
|
||||||
|
|
||||||
|
split = [recovery_key[i:i + 4] for i in range(0, len(recovery_key), 4)]
|
||||||
|
return ' '.join(split)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import { IDeviceKeys, IMegolmSessionData } from "../../../src/@types/crypto";
|
import { IDeviceKeys, IMegolmSessionData } from "../../../src/@types/crypto";
|
||||||
import { IDownloadKeyResult } from "../../../src";
|
import { IDownloadKeyResult } from "../../../src";
|
||||||
import { KeyBackupInfo } from "../../../src/crypto-api";
|
import { KeyBackupInfo } from "../../../src/crypto-api";
|
||||||
|
import { IKeyBackupSession } from "../../../src/crypto/keybackup";
|
||||||
|
|
||||||
/* eslint-disable comma-dangle */
|
/* eslint-disable comma-dangle */
|
||||||
|
|
||||||
@ -102,9 +103,28 @@ export const SIGNED_CROSS_SIGNING_KEYS_DATA: Partial<IDownloadKeyResult> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Signed OTKs, returned by `POST /keys/claim` */
|
||||||
|
export const ONE_TIME_KEYS = {
|
||||||
|
"@alice:localhost": {
|
||||||
|
"test_device": {
|
||||||
|
"signed_curve25519:AAAAHQ": {
|
||||||
|
"key": "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw",
|
||||||
|
"signatures": {
|
||||||
|
"@alice:localhost": {
|
||||||
|
"ed25519:test_device": "25djC6Rk6gIgFBMVawY9X9LnY8XMMziey6lKqL8Q5Bbp7T1vw9uk0RE7eKO2a/jNLcYroO2xRztGhBrKz5sOCQ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** base64-encoded backup decryption (private) key */
|
/** base64-encoded backup decryption (private) key */
|
||||||
export const BACKUP_DECRYPTION_KEY_BASE64 = "dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo=";
|
export const BACKUP_DECRYPTION_KEY_BASE64 = "dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo=";
|
||||||
|
|
||||||
|
/** Backup decryption key in export format */
|
||||||
|
export const BACKUP_DECRYPTION_KEY_BASE58 = "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d";
|
||||||
|
|
||||||
/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}` */
|
/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}` */
|
||||||
export const SIGNED_BACKUP_DATA: KeyBackupInfo = {
|
export const SIGNED_BACKUP_DATA: KeyBackupInfo = {
|
||||||
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||||
@ -158,19 +178,15 @@ export const MEGOLM_SESSION_DATA: IMegolmSessionData = {
|
|||||||
"forwarding_curve25519_key_chain": []
|
"forwarding_curve25519_key_chain": []
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Signed OTKs, returned by `POST /keys/claim` */
|
/** The key from MEGOLM_SESSION_DATA, encrypted for backup using `m.megolm_backup.v1.curve25519-aes-sha2` algorithm*/
|
||||||
export const ONE_TIME_KEYS = {
|
export const CURVE25519_KEY_BACKUP_DATA: IKeyBackupSession = {
|
||||||
"@alice:localhost": {
|
"first_message_index": 1,
|
||||||
"test_device": {
|
"forwarded_count": 0,
|
||||||
"signed_curve25519:AAAAHQ": {
|
"is_verified": false,
|
||||||
"key": "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw",
|
"session_data": {
|
||||||
"signatures": {
|
"ciphertext": "r6HRk2/Im2yJe5cLP8R81aVjFWjYWPHpw7TVxphiSK1cdIDZTTK57r6MfU+0i/mTPn+/PosT74OvYwCnehy2d1BPGxhDl8AhPcBu3//KzlqinE6iAfrQYHmoOTes2uOsHgvbE7M9bIyAE1/FxzIgZuzmbJ1Fp/qjgKeTeonuMujLpnezUOYzDYqbF8hyLdm+SkrVWISde/wmVfpl1TI56ItCKmz0uB4fjUMWUV0sgp+3JLAUIqIqmwb7VVZooox94ze2VuspJMu9mF6MoQs5BEk3bFi9nbdgGwGh0pFYjEwE62+xLjo9x/gdVkIy8xlBmCFjBdvWjBjtvc8NfboWKxZ1tmpPCt6IiCTwOY4kyQdmK6U+sjh3tvs7qLOX5ETThOAqwKsY9VeXbeCtWF2h/m6ZXFQpxsf/vLgpTe0djDsGpWgLY+dywAelfz6O69EiHAxti9XxEGO0WLBDs0rmyhPN03IeLa+b8Gp2RMRdJ4prjKr0m7CVTUXEHbw6LtN0SWvEnc6iBI0vCEs1EocJAFfXsiwzAAgPDmaaDrCEk1dOoYj2eXoy4tbJx3+/FXFKe+RVdmiuv1nsVAQxPq78Tx6UtIz7ihHI0tzaZV+/7PUY065b6kevDBST+SZWERwqFZdz28TWsHu6Kxjr5k2N4sahs8a4frobip6C7PhXJFiq3n7lMx1IjfRrN0HxzpMqBzhkmiat9JR6nWtGjvOvvw",
|
||||||
"@alice:localhost": {
|
"ephemeral": "q+P1WdRtEiPIEtNuuGrRcueZxUbLnSKdsuTAkxewXgU",
|
||||||
"ed25519:test_device": "25djC6Rk6gIgFBMVawY9X9LnY8XMMziey6lKqL8Q5Bbp7T1vw9uk0RE7eKO2a/jNLcYroO2xRztGhBrKz5sOCQ"
|
"mac": "OibmACbORhI"
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -267,9 +283,28 @@ export const BOB_SIGNED_CROSS_SIGNING_KEYS_DATA: Partial<IDownloadKeyResult> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Signed OTKs, returned by `POST /keys/claim` */
|
||||||
|
export const BOB_ONE_TIME_KEYS = {
|
||||||
|
"@bob:xyz": {
|
||||||
|
"bob_device": {
|
||||||
|
"signed_curve25519:AAAAHQ": {
|
||||||
|
"key": "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw",
|
||||||
|
"signatures": {
|
||||||
|
"@bob:xyz": {
|
||||||
|
"ed25519:bob_device": "dlZc9VA/hP980Mxvu9qwi0qJx8VK7sADGOM48CE01YM7K/Mbty9lis/QjtQAWqDg371QyynVRjEzt9qj7eSFCg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** base64-encoded backup decryption (private) key */
|
/** base64-encoded backup decryption (private) key */
|
||||||
export const BOB_BACKUP_DECRYPTION_KEY_BASE64 = "DwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo=";
|
export const BOB_BACKUP_DECRYPTION_KEY_BASE64 = "DwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo=";
|
||||||
|
|
||||||
|
/** Backup decryption key in export format */
|
||||||
|
export const BOB_BACKUP_DECRYPTION_KEY_BASE58 = "EsT5 Sd5m mEXs NQYE ibRe 3q9E 4aXW rHih 5f9J 6rU6 AfwY mASR";
|
||||||
|
|
||||||
/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}` */
|
/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}` */
|
||||||
export const BOB_SIGNED_BACKUP_DATA: KeyBackupInfo = {
|
export const BOB_SIGNED_BACKUP_DATA: KeyBackupInfo = {
|
||||||
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||||
@ -290,10 +325,10 @@ export const BOB_MEGOLM_SESSION_DATA_ARRAY: IMegolmSessionData[] = [
|
|||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
"room_id": "!roomA:example.org",
|
"room_id": "!roomA:example.org",
|
||||||
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
|
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
|
||||||
"session_id": "X9SK5JceUdvUr9gZkmdfS78qmKztoL60oORJ9JA5XD8",
|
"session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
|
||||||
"session_key": "AQAAAADbfOdUj/ec5bK4xVEhhRw+jfd1FD4uA0MLy7NyVHugZxyXNZUx5YEof4H9YyjBretviZreMSXqflbgYKz257rkKu7MPeKFf7zmln2GxX0F/p++GOnvpY1FqOglhfRQi3tqiyOa7SL4f7TuERDTOpMqlWhIfTKQnqy0AyF2vpDi5V/UiuSXHlHb1K/YGZJnX0u/Kpis7aC+tKDkSfSQOVw/",
|
"session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
|
||||||
"sender_claimed_keys": {
|
"sender_claimed_keys": {
|
||||||
"ed25519": "ZG6lrfATe+958wN1xaGf3dKG/CThEfkmNdp1jcu4zok"
|
"ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
|
||||||
},
|
},
|
||||||
"forwarding_curve25519_key_chain": []
|
"forwarding_curve25519_key_chain": []
|
||||||
},
|
},
|
||||||
@ -301,10 +336,10 @@ export const BOB_MEGOLM_SESSION_DATA_ARRAY: IMegolmSessionData[] = [
|
|||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
"room_id": "!roomA:example.org",
|
"room_id": "!roomA:example.org",
|
||||||
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
|
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
|
||||||
"session_id": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA",
|
"session_id": "+07YOpSgdZ1X9le3n3NMByw0V1B0H0Djnbm76jgmWoo",
|
||||||
"session_key": "AQAAAACv0khqPrQ91MmWCgm0RTzfpn65AGCrRnAKLxGJdfSfECNZ8gyj34FZLwi+F+xC6ibFddcbLXW0mzR6PnTnHF3VHM4g/h+2rcxtlix8fySpIwFzaXViba7cOSy/b+dHTMZB40iA7F4y7AdTdHLv4N1XUj3puU/KVUIKf9/lEDLqyReD+39WdEY24mTIB5NcQQhtyguPzYPT5sSyeIUNd7Bw",
|
"session_key": "AQAAAAAjWfIMo9+BWS8IvhfsQuomxXXXGy11tJs0ej505xxd1RzOIP4ftq3MbZYsfH8kqSMBc2l1Ym2u3Dksv2/nR0zGQeNIgOxeMuwHU3Ry7+DdV1I96blPylVCCn/f5RAy6smKoaeylptPdXgVXmw3YBBUVYpHpm+xCIUUp9foAdb8hftO2DqUoHWdV/ZXt59zTAcsNFdQdB9A4525u+o4JlqK",
|
||||||
"sender_claimed_keys": {
|
"sender_claimed_keys": {
|
||||||
"ed25519": "HxUKnGfeUu0fF3cLyCFSDXYtVCQHy/+33q9RkzKfsiU"
|
"ed25519": "OsZMdC1gQ5nPr+L9tuT6xXsaFJkVPkgxP2FexHF1/QM"
|
||||||
},
|
},
|
||||||
"forwarding_curve25519_key_chain": []
|
"forwarding_curve25519_key_chain": []
|
||||||
}
|
}
|
||||||
@ -315,27 +350,23 @@ export const BOB_MEGOLM_SESSION_DATA: IMegolmSessionData = {
|
|||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
"room_id": "!roomA:example.org",
|
"room_id": "!roomA:example.org",
|
||||||
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
|
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
|
||||||
"session_id": "OsZMdC1gQ5nPr+L9tuT6xXsaFJkVPkgxP2FexHF1/QM",
|
"session_id": "gywydBrIJcJWktC/ic3tunKZM1XZm1MpYiYtdbj8Rpc",
|
||||||
"session_key": "AQAAAACvcoGk7mOY59fOqZaxFUiTCBRV1Ia94KBjAZx6kgdgBtkkvs50z8od8/Nc9ncK2UsEiXNvCTTp2dlN3du+Rx0/m7vet2ZOEEp2oYDjHMLLFmwd1gtlGuWYPdXA6Y1+9Yyph0/EDVfS+zd3XvbL0QgbyL43+yQnFNHKlxVJX1eiKTrGTHQtYEOZz6/i/bbk+sV7GhSZFT5IMT9hXsRxdf0D",
|
"session_key": "AQAAAADZJL7OdM/KHfPzXPZ3CtlLBIlzbwk06dnZTd3bvkcdP5u73rdmThBKdqGA4xzCyxZsHdYLZRrlmD3VwOmNfvWMqYdPxA1X0vs3d172y9EIG8i+N/skJxTRypcVSV9XoinBNIWr/gkyepuAKiQqemlc8J5amD9OkmbVkmnrxP1uyYMsMnQayCXCVpLQv4nN7bpymTNV2ZtTKWImLXW4/EaX",
|
||||||
"sender_claimed_keys": {
|
"sender_claimed_keys": {
|
||||||
"ed25519": "dV0TIhhkToXpL+gZLo+zXDHJfw7MWYxpg80cynIQDv0"
|
"ed25519": "zBdpQwWYyz1MkZuEUhXqcdMfUNN/B9psLFDDDTJOg64"
|
||||||
},
|
},
|
||||||
"forwarding_curve25519_key_chain": []
|
"forwarding_curve25519_key_chain": []
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Signed OTKs, returned by `POST /keys/claim` */
|
/** The key from BOB_MEGOLM_SESSION_DATA, encrypted for backup using `m.megolm_backup.v1.curve25519-aes-sha2` algorithm*/
|
||||||
export const BOB_ONE_TIME_KEYS = {
|
export const BOB_CURVE25519_KEY_BACKUP_DATA: IKeyBackupSession = {
|
||||||
"@bob:xyz": {
|
"first_message_index": 1,
|
||||||
"bob_device": {
|
"forwarded_count": 0,
|
||||||
"signed_curve25519:AAAAHQ": {
|
"is_verified": false,
|
||||||
"key": "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw",
|
"session_data": {
|
||||||
"signatures": {
|
"ciphertext": "d7UVOK17WEVky/8hK0h3HsTQrFMEbKbfqMcl2KtyTWcI9S5gGFWK9Git5BzVRxRggvxQ0c8PDfqL+dr3zHytAMW+71BJqIPQW910vV7SX3JkR91kuuiDqlFT1VHLMP9/MNsvCfeZz1Q+0/LqkJo1KTbrrjKxqOq5/e7RQequOEXHu0a658fKaK/tk0eLvO2xUTxxBCgtuuLpeysOykf4jqp3yV89+h+h3o4zsWGyA2cLIBC/9ofqiqsoF+l1smzA+sQ3ArLBM5B2wc7b4Ir7jZUzCpnOK6O6nQA3vwOpwy3AqpeCKbsuXBPLkb9QXnVH9UzNbuL1Go7M6S3zH2BlAQrUhKSj4yI9tQC3XL3Y3IuAJRUX2bIXzYn9DryL+zwz9aULGN1ECZ4pMHm9jlPUYgMDYmSA2rRCZPWXcSeqmV8aB2qR3k+gcCFXNGbKy73bdN87GsPsYPI7RCkgtHGyW2tpz0JpP1CQmtP3wXNBKIJKIgcxGQ3YDehGV7XlFDDUYBZyYGazXqU/fdt21gc4Jvg1xVKsj83woVmGDo+2WzPrpIJ79NJUovC8jJ+UA3QamY28Df6UaRuS3+vvgCCW9e3T5+h5hR7BQayZ9r9Ykv7h7NTxQYjsYwxfZK+OULJPFNYbcB+vc6msLAsqocPhNQTazOe4TfsSAAuaHa/2FfGGMyaWCQY5DI3tk3Pagr87L8lBhwXll9usXKslvc+sJw",
|
||||||
"@bob:xyz": {
|
"ephemeral": "oO0VX84OUIzm2i/12zAhTWOZT5IFRH5mXaKZ8fXkCgU",
|
||||||
"ed25519:bob_device": "dlZc9VA/hP980Mxvu9qwi0qJx8VK7sADGOM48CE01YM7K/Mbty9lis/QjtQAWqDg371QyynVRjEzt9qj7eSFCg"
|
"mac": "lEfHlqfJQwU"
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3471,9 +3471,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
await this.cryptoBackend.deleteKeyBackupVersion(version);
|
await this.cryptoBackend.deleteKeyBackupVersion(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeKeyBackupPath(roomId: undefined, sessionId: undefined, version?: string): IKeyBackupPath;
|
|
||||||
private makeKeyBackupPath(roomId: string, sessionId: undefined, version?: string): IKeyBackupPath;
|
|
||||||
private makeKeyBackupPath(roomId: string, sessionId: string, version?: string): IKeyBackupPath;
|
|
||||||
private makeKeyBackupPath(roomId?: string, sessionId?: string, version?: string): IKeyBackupPath {
|
private makeKeyBackupPath(roomId?: string, sessionId?: string, version?: string): IKeyBackupPath {
|
||||||
let path: string;
|
let path: string;
|
||||||
if (sessionId !== undefined) {
|
if (sessionId !== undefined) {
|
||||||
@ -3793,22 +3790,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
let totalKeyCount = 0;
|
let totalKeyCount = 0;
|
||||||
let keys: IMegolmSessionData[] = [];
|
let keys: IMegolmSessionData[] = [];
|
||||||
|
|
||||||
const path = this.makeKeyBackupPath(targetRoomId!, targetSessionId!, backupInfo.version);
|
const path = this.makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version);
|
||||||
|
|
||||||
const algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => {
|
const backupDecryptor = await this.cryptoBackend.getBackupDecryptor(backupInfo, privKey);
|
||||||
return privKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
const untrusted = algorithm.untrusted;
|
const untrusted = !backupDecryptor.sourceTrusted;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If the pubkey computed from the private data we've been given
|
|
||||||
// doesn't match the one in the auth_data, the user has entered
|
|
||||||
// a different recovery key / the wrong passphrase.
|
|
||||||
if (!(await algorithm.keyMatches(privKey))) {
|
|
||||||
return Promise.reject(new MatrixError({ errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(privKey instanceof Uint8Array)) {
|
if (!(privKey instanceof Uint8Array)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||||
throw new Error(`restoreKeyBackup expects Uint8Array, got ${privKey}`);
|
throw new Error(`restoreKeyBackup expects Uint8Array, got ${privKey}`);
|
||||||
@ -3842,7 +3830,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
if (!roomData.sessions) continue;
|
if (!roomData.sessions) continue;
|
||||||
|
|
||||||
totalKeyCount += Object.keys(roomData.sessions).length;
|
totalKeyCount += Object.keys(roomData.sessions).length;
|
||||||
const roomKeys = await algorithm.decryptSessions(roomData.sessions);
|
const roomKeys = await backupDecryptor.decryptSessions(roomData.sessions);
|
||||||
for (const k of roomKeys) {
|
for (const k of roomKeys) {
|
||||||
k.room_id = roomId;
|
k.room_id = roomId;
|
||||||
keys.push(k);
|
keys.push(k);
|
||||||
@ -3851,14 +3839,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
} else if ((res as IRoomKeysResponse).sessions) {
|
} else if ((res as IRoomKeysResponse).sessions) {
|
||||||
const sessions = (res as IRoomKeysResponse).sessions;
|
const sessions = (res as IRoomKeysResponse).sessions;
|
||||||
totalKeyCount = Object.keys(sessions).length;
|
totalKeyCount = Object.keys(sessions).length;
|
||||||
keys = await algorithm.decryptSessions(sessions);
|
keys = await backupDecryptor.decryptSessions(sessions);
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
k.room_id = targetRoomId!;
|
k.room_id = targetRoomId!;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
totalKeyCount = 1;
|
totalKeyCount = 1;
|
||||||
try {
|
try {
|
||||||
const [key] = await algorithm.decryptSessions({
|
const [key] = await backupDecryptor.decryptSessions({
|
||||||
[targetSessionId!]: res as IKeyBackupSession,
|
[targetSessionId!]: res as IKeyBackupSession,
|
||||||
});
|
});
|
||||||
key.room_id = targetRoomId!;
|
key.room_id = targetRoomId!;
|
||||||
@ -3869,7 +3857,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
algorithm.free();
|
backupDecryptor.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.cryptoBackend.importRoomKeys(keys, {
|
await this.cryptoBackend.importRoomKeys(keys, {
|
||||||
@ -3878,7 +3866,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
source: "backup",
|
source: "backup",
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.checkKeyBackup();
|
/// in case entering the passphrase would add a new signature?
|
||||||
|
await this.cryptoBackend.checkKeyBackupAndEnable();
|
||||||
|
|
||||||
return { total: totalKeyCount, imported: keys.length };
|
return { total: totalKeyCount, imported: keys.length };
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import { Room } from "../models/room";
|
|||||||
import { CryptoApi } from "../crypto-api";
|
import { CryptoApi } from "../crypto-api";
|
||||||
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning";
|
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning";
|
||||||
import { IEncryptedEventInfo } from "../crypto/api";
|
import { IEncryptedEventInfo } from "../crypto/api";
|
||||||
|
import { IKeyBackupInfo, IKeyBackupSession } from "../crypto/keybackup";
|
||||||
|
import { IMegolmSessionData } from "../@types/crypto";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common interface for the crypto implementations
|
* Common interface for the crypto implementations
|
||||||
@ -99,6 +101,13 @@ export interface CryptoBackend extends SyncCryptoCallbacks, CryptoApi {
|
|||||||
* @deprecated Unneeded for the new crypto
|
* @deprecated Unneeded for the new crypto
|
||||||
*/
|
*/
|
||||||
checkOwnCrossSigningTrust(opts?: CheckOwnCrossSigningTrustOpts): Promise<void>;
|
checkOwnCrossSigningTrust(opts?: CheckOwnCrossSigningTrustOpts): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a backup decryptor capable of decrypting megolm session data encrypted with the given backup information.
|
||||||
|
* @param backupInfo - The backup information
|
||||||
|
* @param privKey - The private decryption key.
|
||||||
|
*/
|
||||||
|
getBackupDecryptor(backupInfo: IKeyBackupInfo, privKey: ArrayLike<number>): Promise<BackupDecryptor>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The methods which crypto implementations should expose to the Sync api
|
/** The methods which crypto implementations should expose to the Sync api
|
||||||
@ -213,3 +222,35 @@ export interface EventDecryptionResult {
|
|||||||
*/
|
*/
|
||||||
encryptedDisabledForUnverifiedDevices?: boolean;
|
encryptedDisabledForUnverifiedDevices?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for decrypting megolm session data retrieved from a remote backup.
|
||||||
|
* The result of {@link CryptoBackend#getBackupDecryptor}.
|
||||||
|
*/
|
||||||
|
export interface BackupDecryptor {
|
||||||
|
/**
|
||||||
|
* Whether keys retrieved from this backup can be trusted.
|
||||||
|
*
|
||||||
|
* Depending on the backup algorithm, keys retrieved from the backup can be trusted or not.
|
||||||
|
* If false, keys retrieved from the backup must be considered unsafe (authenticity cannot be guaranteed).
|
||||||
|
* It could be by design (deniability) or for some technical reason (eg asymmetric encryption).
|
||||||
|
*/
|
||||||
|
readonly sourceTrusted: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Decrypt megolm session data retrieved from backup.
|
||||||
|
*
|
||||||
|
* @param ciphertexts - a Record of sessionId to session data.
|
||||||
|
*
|
||||||
|
* @returns An array of decrypted `IMegolmSessionData`
|
||||||
|
*/
|
||||||
|
decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free any resources held by this decryptor.
|
||||||
|
*
|
||||||
|
* Should be called once the decryptor is no longer needed.
|
||||||
|
*/
|
||||||
|
free(): void;
|
||||||
|
}
|
||||||
|
@ -41,6 +41,7 @@ import { CryptoEvent } from "./index";
|
|||||||
import { crypto } from "./crypto";
|
import { crypto } from "./crypto";
|
||||||
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api";
|
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api";
|
||||||
import { BackupTrustInfo } from "../crypto-api/keybackup";
|
import { BackupTrustInfo } from "../crypto-api/keybackup";
|
||||||
|
import { BackupDecryptor } from "../common-crypto/CryptoBackend";
|
||||||
|
|
||||||
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
||||||
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
|
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
|
||||||
@ -878,3 +879,32 @@ export function backupTrustInfoFromLegacyTrustInfo(trustInfo: TrustInfo): Backup
|
|||||||
matchesDecryptionKey: trustInfo.trusted_locally ?? false,
|
matchesDecryptionKey: trustInfo.trusted_locally ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link BackupDecryptor} for the libolm crypto backend.
|
||||||
|
*/
|
||||||
|
export class LibOlmBackupDecryptor implements BackupDecryptor {
|
||||||
|
private algorithm: BackupAlgorithm;
|
||||||
|
public readonly sourceTrusted: boolean;
|
||||||
|
|
||||||
|
public constructor(algorithm: BackupAlgorithm) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
this.sourceTrusted = !algorithm.untrusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link BackupDecryptor#free}
|
||||||
|
*/
|
||||||
|
public free(): void {
|
||||||
|
this.algorithm.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link BackupDecryptor#decryptSessions}
|
||||||
|
*/
|
||||||
|
public async decryptSessions(
|
||||||
|
sessions: Record<string, IKeyBackupSession<Curve25519SessionData>>,
|
||||||
|
): Promise<IMegolmSessionData[]> {
|
||||||
|
return await this.algorithm.decryptSessions(sessions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,7 +50,7 @@ import { IllegalMethod } from "./verification/IllegalMethod";
|
|||||||
import { KeySignatureUploadError } from "../errors";
|
import { KeySignatureUploadError } from "../errors";
|
||||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes";
|
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes";
|
||||||
import { DehydrationManager } from "./dehydration";
|
import { DehydrationManager } from "./dehydration";
|
||||||
import { BackupManager, backupTrustInfoFromLegacyTrustInfo } from "./backup";
|
import { BackupManager, LibOlmBackupDecryptor, backupTrustInfoFromLegacyTrustInfo } from "./backup";
|
||||||
import { IStore } from "../store";
|
import { IStore } from "../store";
|
||||||
import { Room, RoomEvent } from "../models/room";
|
import { Room, RoomEvent } from "../models/room";
|
||||||
import { RoomMember, RoomMemberEvent } from "../models/room-member";
|
import { RoomMember, RoomMemberEvent } from "../models/room-member";
|
||||||
@ -73,7 +73,7 @@ import { TypedEventEmitter } from "../models/typed-event-emitter";
|
|||||||
import { IDeviceLists, ISyncResponse, IToDeviceEvent } from "../sync-accumulator";
|
import { IDeviceLists, ISyncResponse, IToDeviceEvent } from "../sync-accumulator";
|
||||||
import { ISignatures } from "../@types/signed";
|
import { ISignatures } from "../@types/signed";
|
||||||
import { IMessage } from "./algorithms/olm";
|
import { IMessage } from "./algorithms/olm";
|
||||||
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
import { BackupDecryptor, CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
||||||
import { RoomState, RoomStateEvent } from "../models/room-state";
|
import { RoomState, RoomStateEvent } from "../models/room-state";
|
||||||
import { MapWithDefault, recursiveMapToObject } from "../utils";
|
import { MapWithDefault, recursiveMapToObject } from "../utils";
|
||||||
import {
|
import {
|
||||||
@ -101,7 +101,7 @@ import {
|
|||||||
} from "../crypto-api";
|
} from "../crypto-api";
|
||||||
import { Device, DeviceMap } from "../models/device";
|
import { Device, DeviceMap } from "../models/device";
|
||||||
import { deviceInfoToDevice } from "./device-converter";
|
import { deviceInfoToDevice } from "./device-converter";
|
||||||
import { ClientPrefix, Method } from "../http-api";
|
import { ClientPrefix, MatrixError, Method } from "../http-api";
|
||||||
|
|
||||||
/* re-exports for backwards compatibility */
|
/* re-exports for backwards compatibility */
|
||||||
export type {
|
export type {
|
||||||
@ -1845,6 +1845,28 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
// the backup with the new key (if not already signed)?
|
// the backup with the new key (if not already signed)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link CryptoBackend#getBackupDecryptor}.
|
||||||
|
*/
|
||||||
|
public async getBackupDecryptor(backupInfo: IKeyBackupInfo, privKey: ArrayLike<number>): Promise<BackupDecryptor> {
|
||||||
|
if (!(privKey instanceof Uint8Array)) {
|
||||||
|
throw new Error(`getBackupDecryptor expects Uint8Array`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => {
|
||||||
|
return privKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the pubkey computed from the private data we've been given
|
||||||
|
// doesn't match the one in the auth_data, the user has entered
|
||||||
|
// a different recovery key / the wrong passphrase.
|
||||||
|
if (!(await algorithm.keyMatches(privKey))) {
|
||||||
|
return Promise.reject(new MatrixError({ errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LibOlmBackupDecryptor(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a set of keys as our own, trusted, cross-signing keys.
|
* Store a set of keys as our own, trusted, cross-signing keys.
|
||||||
*
|
*
|
||||||
|
@ -23,7 +23,7 @@ import type { IEncryptedEventInfo } from "../crypto/api";
|
|||||||
import { IContent, MatrixEvent, MatrixEventEvent } from "../models/event";
|
import { IContent, MatrixEvent, MatrixEventEvent } from "../models/event";
|
||||||
import { Room } from "../models/room";
|
import { Room } from "../models/room";
|
||||||
import { RoomMember } from "../models/room-member";
|
import { RoomMember } from "../models/room-member";
|
||||||
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
import { BackupDecryptor, CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { IHttpOpts, MatrixHttpApi, Method } from "../http-api";
|
import { IHttpOpts, MatrixHttpApi, Method } from "../http-api";
|
||||||
import { RoomEncryptor } from "./RoomEncryptor";
|
import { RoomEncryptor } from "./RoomEncryptor";
|
||||||
@ -1023,6 +1023,13 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
obj.signatures = Object.fromEntries(sigs.entries());
|
obj.signatures = Object.fromEntries(sigs.entries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link CryptoBackend#getBackupDecryptor}.
|
||||||
|
*/
|
||||||
|
public async getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: ArrayLike<number>): Promise<BackupDecryptor> {
|
||||||
|
throw new Error("Stub not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// SyncCryptoCallbacks implementation
|
// SyncCryptoCallbacks implementation
|
||||||
|
Reference in New Issue
Block a user