1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-25 05:23:13 +03:00

Reincorporate crypto changes

https://github.com/matrix-org/matrix-js-sdk/pull/1697
This commit is contained in:
Travis Ralston
2021-06-03 18:59:01 -06:00
parent f53a32a6b4
commit 92ebd39391
2 changed files with 70 additions and 114 deletions

View File

@@ -58,12 +58,9 @@ import {
IKeyBackupPrepareOpts, IKeyBackupPrepareOpts,
IKeyBackupRestoreOpts, IKeyBackupRestoreOpts,
IKeyBackupRestoreResult, IKeyBackupRestoreResult,
IKeyBackupRoomSessions,
IKeyBackupSession,
IKeyBackupTrustInfo, IKeyBackupTrustInfo,
IKeyBackupVersion, IKeyBackupVersion,
} from "./crypto/keybackup"; } from "./crypto/keybackup";
import { PkDecryption } from "@matrix-org/olm";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider"; import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
import type Request from "request"; import type Request from "request";
import { MatrixScheduler } from "./scheduler"; import { MatrixScheduler } from "./scheduler";
@@ -109,6 +106,7 @@ import url from "url";
import { randomString } from "./randomstring"; import { randomString } from "./randomstring";
import { ReadStream } from "fs"; import { ReadStream } from "fs";
import { WebStorageSessionStore } from "./store/session/webstorage"; import { WebStorageSessionStore } from "./store/session/webstorage";
import { BackupManager } from "./crypto/backup";
export type Store = StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend; export type Store = StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend;
export type SessionStore = WebStorageSessionStore; export type SessionStore = WebStorageSessionStore;
@@ -122,29 +120,6 @@ export const CRYPTO_ENABLED: boolean = isCryptoAvailable();
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
const TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes const TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes
function keysFromRecoverySession(sessions: IKeyBackupRoomSessions, decryptionKey: PkDecryption, roomId: string) {
const keys = [];
for (const [sessionId, sessionData] of Object.entries(sessions)) {
try {
const decrypted = keyFromRecoverySession(sessionData, decryptionKey);
decrypted.session_id = sessionId;
decrypted.room_id = roomId;
keys.push(decrypted);
} catch (e) {
logger.log("Failed to decrypt megolm session from backup", e);
}
}
return keys;
}
function keyFromRecoverySession(session: IKeyBackupSession, decryptionKey: PkDecryption) {
return JSON.parse(decryptionKey.decrypt(
session.session_data.ephemeral,
session.session_data.mac,
session.session_data.ciphertext,
));
}
interface IOlmDevice { interface IOlmDevice {
pickledAccount: string; pickledAccount: string;
sessions: Array<Record<string, any>>; sessions: Array<Record<string, any>>;
@@ -1312,7 +1287,7 @@ export class MatrixClient extends EventEmitter {
// check the key backup status, since whether or not we use this depends on // check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device // whether it has a signature from a verified device
if (userId == this.credentials.userId) { if (userId == this.credentials.userId) {
this.crypto.checkKeyBackup(); this.checkKeyBackup();
} }
return prom; return prom;
} }
@@ -2072,7 +2047,7 @@ export class MatrixClient extends EventEmitter {
* in trustInfo. * in trustInfo.
*/ */
public checkKeyBackup(): IKeyBackupVersion { public checkKeyBackup(): IKeyBackupVersion {
return this.crypto.checkKeyBackup(); return this.crypto._backupManager.checkKeyBackup();
} }
/** /**
@@ -2114,7 +2089,7 @@ export class MatrixClient extends EventEmitter {
* } * }
*/ */
public isKeyBackupTrusted(info: IKeyBackupVersion): IKeyBackupTrustInfo { public isKeyBackupTrusted(info: IKeyBackupVersion): IKeyBackupTrustInfo {
return this.crypto.isKeyBackupTrusted(info); return this.crypto._backupManager.isKeyBackupTrusted(info);
} }
/** /**
@@ -2126,11 +2101,7 @@ export class MatrixClient extends EventEmitter {
if (!this.crypto) { if (!this.crypto) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
// XXX: Private member access return this.crypto._backupManager.getKeyBackupEnabled();
if (!this.crypto._checkedForBackup) {
return null;
}
return Boolean(this.crypto.backupKey);
} }
/** /**
@@ -2138,22 +2109,14 @@ export class MatrixClient extends EventEmitter {
* getKeyBackupVersion. * getKeyBackupVersion.
* *
* @param {object} info Backup information object as returned by getKeyBackupVersion * @param {object} info Backup information object as returned by getKeyBackupVersion
* @returns {Promise<void>} Resolves when complete.
*/ */
public enableKeyBackup(info: IKeyBackupVersion) { public enableKeyBackup(info: IKeyBackupVersion): Promise<void> {
if (!this.crypto) { if (!this.crypto) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
this.crypto.backupInfo = info; return this.crypto._backupManager.enableKeyBackup(info);
if (this.crypto.backupKey) this.crypto.backupKey.free();
this.crypto.backupKey = new global.Olm.PkEncryption();
this.crypto.backupKey.set_recipient_key(info.auth_data.public_key);
this.emit('crypto.keyBackupStatus', true);
// There may be keys left over from a partially completed backup, so
// schedule a send to check.
this.crypto.scheduleKeyBackupSend();
} }
/** /**
@@ -2164,11 +2127,7 @@ export class MatrixClient extends EventEmitter {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
this.crypto.backupInfo = null; this.crypto._backupManager.disableKeyBackup();
if (this.crypto.backupKey) this.crypto.backupKey.free();
this.crypto.backupKey = null;
this.emit('crypto.keyBackupStatus', false);
} }
/** /**
@@ -2194,27 +2153,20 @@ export class MatrixClient extends EventEmitter {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
const { keyInfo, encodedPrivateKey, privateKey } = // eslint-disable-next-line camelcase
await this.createRecoveryKeyFromPassphrase(password); const { algorithm, auth_data, recovery_key, privateKey } =
await this.crypto._backupManager.prepareKeyBackupVersion(password);
if (opts.secureSecretStorage) { if (opts.secureSecretStorage) {
await this.storeSecret("m.megolm_backup.v1", encodeBase64(privateKey)); await this.storeSecret("m.megolm_backup.v1", encodeBase64(privateKey));
logger.info("Key backup private key stored in secret storage"); logger.info("Key backup private key stored in secret storage");
} }
// Reshape objects into form expected for key backup
const authData: any = { // TODO
public_key: keyInfo.pubkey,
};
if (keyInfo.passphrase) {
authData.private_key_salt = keyInfo.passphrase.salt;
authData.private_key_iterations = keyInfo.passphrase.iterations;
}
return { return {
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM, algorithm,
auth_data: authData, auth_data,
recovery_key: encodedPrivateKey, recovery_key,
} as any; // TODO } as any; // TODO: Types
} }
/** /**
@@ -2240,6 +2192,8 @@ export class MatrixClient extends EventEmitter {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
await this.crypto._backupManager.createKeyBackupVersion(info);
const data = { const data = {
algorithm: info.algorithm, algorithm: info.algorithm,
auth_data: info.auth_data, auth_data: info.auth_data,
@@ -2288,8 +2242,8 @@ export class MatrixClient extends EventEmitter {
// If we're currently backing up to this backup... stop. // If we're currently backing up to this backup... stop.
// (We start using it automatically in createKeyBackupVersion // (We start using it automatically in createKeyBackupVersion
// so this is symmetrical). // so this is symmetrical).
if (this.crypto.backupInfo && this.crypto.backupInfo.version === version) { if (this.crypto._backupManager.version) {
this.disableKeyBackup(); this.crypto._backupManager.disableKeyBackup();
} }
const path = utils.encodeUri("/room_keys/version/$version", { const path = utils.encodeUri("/room_keys/version/$version", {
@@ -2354,7 +2308,7 @@ export class MatrixClient extends EventEmitter {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
await this.crypto.scheduleAllGroupSessionsForBackup(); await this.crypto._backupManager.scheduleAllGroupSessionsForBackup();
} }
/** /**
@@ -2367,7 +2321,7 @@ export class MatrixClient extends EventEmitter {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
return this.crypto.flagAllGroupSessionsForBackup(); return this.crypto._backupManager.flagAllGroupSessionsForBackup();
} }
public isValidRecoveryKey(recoveryKey: string): boolean { public isValidRecoveryKey(recoveryKey: string): boolean {
@@ -2513,7 +2467,7 @@ export class MatrixClient extends EventEmitter {
); );
} }
private restoreKeyBackup( private async restoreKeyBackup(
privKey: Uint8Array, privKey: Uint8Array,
targetRoomId: string, targetRoomId: string,
targetSessionId: string, targetSessionId: string,
@@ -2526,6 +2480,7 @@ export class MatrixClient extends EventEmitter {
if (!this.crypto) { if (!this.crypto) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
let totalKeyCount = 0; let totalKeyCount = 0;
let keys = []; let keys = [];
@@ -2533,20 +2488,14 @@ export class MatrixClient extends EventEmitter {
targetRoomId, targetSessionId, backupInfo.version, targetRoomId, targetSessionId, backupInfo.version,
); );
const decryption = new global.Olm.PkDecryption(); const algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => { return privKey; });
let backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privKey);
} catch (e) {
decryption.free();
throw e;
}
try {
// If the pubkey computed from the private data we've been given // 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 // doesn't match the one in the auth_data, the user has entered
// a different recovery key / the wrong passphrase. // a different recovery key / the wrong passphrase.
if (backupPubKey !== backupInfo.auth_data.public_key) { if (!await algorithm.keyMatches(privKey)) {
return Promise.reject(new MatrixError({ errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY })); return Promise.reject({ errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY });
} }
// Cache the key, if possible. // Cache the key, if possible.
@@ -2562,18 +2511,19 @@ export class MatrixClient extends EventEmitter {
}); });
} }
return this.http.authedRequest( const res = await this.http.authedRequest(
undefined, "GET", path.path, path.queryData, undefined, undefined, "GET", path.path, path.queryData, undefined,
{ prefix: PREFIX_UNSTABLE }, { prefix: PREFIX_UNSTABLE },
).then((res) => { );
if (res.rooms) { if (res.rooms) {
// TODO: Types? // TODO: Types
for (const [roomId, roomData] of Object.entries<any>(res.rooms)) { for (const [roomId, roomData] of Object.entries<any>(res.rooms)) {
if (!roomData.sessions) continue; if (!roomData.sessions) continue;
totalKeyCount += Object.keys(roomData.sessions).length; totalKeyCount += Object.keys(roomData.sessions).length;
const roomKeys = keysFromRecoverySession( const roomKeys = await algorithm.decryptSessions(
roomData.sessions, decryption, roomId, roomData.sessions,
); );
for (const k of roomKeys) { for (const k of roomKeys) {
k.room_id = roomId; k.room_id = roomId;
@@ -2582,13 +2532,18 @@ export class MatrixClient extends EventEmitter {
} }
} else if (res.sessions) { } else if (res.sessions) {
totalKeyCount = Object.keys(res.sessions).length; totalKeyCount = Object.keys(res.sessions).length;
keys = keysFromRecoverySession( keys = await algorithm.decryptSessions(
res.sessions, decryption, targetRoomId, res.sessions,
); );
for (const k of keys) {
k.room_id = targetRoomId;
}
} else { } else {
totalKeyCount = 1; totalKeyCount = 1;
try { try {
const key = keyFromRecoverySession(res, decryption); const [key] = await algorithm.decryptSessions({
[targetSessionId]: res,
});
key.room_id = targetRoomId; key.room_id = targetRoomId;
key.session_id = targetSessionId; key.session_id = targetSessionId;
keys.push(key); keys.push(key);
@@ -2596,19 +2551,19 @@ export class MatrixClient extends EventEmitter {
logger.log("Failed to decrypt megolm session from backup", e); logger.log("Failed to decrypt megolm session from backup", e);
} }
} }
} finally {
algorithm.free();
}
return this.importRoomKeys(keys, { await this.importRoomKeys(keys, {
progressCallback, progressCallback,
untrusted: true, untrusted: true,
source: "backup", source: "backup",
}); });
}).then(() => {
return this.crypto.setTrustedBackupPubKey(backupPubKey); await this.checkKeyBackup();
}).then(() => {
return { total: totalKeyCount, imported: keys.length }; return { total: totalKeyCount, imported: keys.length };
}).finally(() => {
decryption.free();
});
} }
public deleteKeysFromBackup(roomId: string, sessionId: string, version: string): Promise<void> { public deleteKeysFromBackup(roomId: string, sessionId: string, version: string): Promise<void> {

View File

@@ -118,6 +118,7 @@ export interface ICryptoCallbacks {
keyInfo: ISecretStorageKeyInfo, keyInfo: ISecretStorageKeyInfo,
checkFunc: (Uint8Array) => void, checkFunc: (Uint8Array) => void,
) => Promise<Uint8Array>; ) => Promise<Uint8Array>;
getBackupKey?: () => Promise<Uint8Array>;
} }
// TODO: Move this to `SecretStorage` once converted // TODO: Move this to `SecretStorage` once converted