1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

Moar typescriptification

This commit is contained in:
Michael Telatynski
2021-06-24 17:41:52 +01:00
parent 3effeb7dc4
commit b4dc1e1555
8 changed files with 74 additions and 78 deletions

View File

@@ -114,7 +114,7 @@ import url from "url";
import { randomString } from "./randomstring";
import { ReadStream } from "fs";
import { WebStorageSessionStore } from "./store/session/webstorage";
import { BackupManager, IKeyBackupCheck, TrustInfo } from "./crypto/backup";
import { BackupManager, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
export type Store = StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend;
@@ -2180,7 +2180,7 @@ export class MatrixClient extends EventEmitter {
public async prepareKeyBackupVersion(
password: string,
opts: IKeyBackupPrepareOpts = { secureSecretStorage: false },
): Promise<IKeyBackupVersion> {
): Promise<Pick<IPreparedKeyBackupVersion, "algorithm" | "auth_data" | "recovery_key">> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@@ -2198,7 +2198,7 @@ export class MatrixClient extends EventEmitter {
algorithm,
auth_data,
recovery_key,
} as any; // TODO: Types
};
}
/**
@@ -2313,7 +2313,7 @@ export class MatrixClient extends EventEmitter {
* Back up session keys to the homeserver.
* @param {string} roomId ID of the room that the keys are for Optional.
* @param {string} sessionId ID of the session that the keys are for Optional.
* @param {integer} version backup version Optional.
* @param {number} version backup version Optional.
* @param {object} data Object keys to send
* @return {Promise} a promise that will resolve when the keys
* are uploaded
@@ -2388,7 +2388,7 @@ export class MatrixClient extends EventEmitter {
* @param {string} recoveryKey The recovery key
* @return {Uint8Array} key backup key
*/
public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array {
public keyBackupKeyFromRecoveryKey(recoveryKey: string): ArrayLike<number> {
return decodeRecoveryKey(recoveryKey);
}
@@ -2478,9 +2478,7 @@ export class MatrixClient extends EventEmitter {
opts: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> {
const privKey = decodeRecoveryKey(recoveryKey);
return this.restoreKeyBackup(
privKey, targetRoomId, targetSessionId, backupInfo, opts,
);
return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts);
}
// TODO: Types
@@ -2498,7 +2496,7 @@ export class MatrixClient extends EventEmitter {
}
private async restoreKeyBackup(
privKey: Uint8Array,
privKey: ArrayLike<number>,
targetRoomId: string,
targetSessionId: string,
backupInfo: IKeyBackupVersion,

View File

@@ -296,7 +296,7 @@ export class CrossSigningInfo extends EventEmitter {
}
const privateKeys: Record<string, Uint8Array> = {};
const keys: Record<string, object> = {};
const keys: Record<string, any> = {}; // TODO types
let masterSigning;
let masterPub;
@@ -719,12 +719,12 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
);
}
const pickleKey = Buffer.from(olmDevice._pickleKey);
key = await encryptAES(encodeBase64(key), pickleKey, type);
const encryptedKey = await encryptAES(encodeBase64(key), pickleKey, type);
return store.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
store.storeSecretStorePrivateKey(txn, type, key);
store.storeSecretStorePrivateKey(txn, type, encryptedKey);
},
);
},

View File

@@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { BinaryLike } from "crypto";
import { getCrypto } from '../utils';
import { decodeBase64, encodeBase64 } from './olmlib';
@@ -23,6 +25,12 @@ const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
// salt for HKDF, with 8 bytes of zeros
const zeroSalt = new Uint8Array(8);
interface IEncryptedPayload {
iv: string;
ciphertext: string;
mac: string;
}
/**
* encrypt a string in Node.js
*
@@ -31,7 +39,7 @@ const zeroSalt = new Uint8Array(8);
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string) {
async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
@@ -77,7 +85,7 @@ async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?:
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptNode(data: IData, key: Uint8Array, name: string) {
async function decryptNode(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
@@ -100,10 +108,9 @@ async function decryptNode(data: IData, key: Uint8Array, name: string) {
+ decipher.final("utf8");
}
function deriveKeysNode(key, name) {
function deriveKeysNode(key: BinaryLike, name: string): [Buffer, Buffer] {
const crypto = getCrypto();
const prk = crypto.createHmac("sha256", zeroSalt)
.update(key).digest();
const prk = crypto.createHmac("sha256", zeroSalt).update(key).digest();
const b = Buffer.alloc(1, 1);
const aesKey = crypto.createHmac("sha256", prk)
@@ -123,7 +130,7 @@ function deriveKeysNode(key, name) {
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr?: string) {
async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
let iv;
if (ivStr) {
iv = decodeBase64(ivStr);
@@ -163,12 +170,6 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
};
}
interface IData {
ciphertext: string;
iv: string;
mac: string;
}
/**
* decrypt a string in the browser
*
@@ -179,7 +180,7 @@ interface IData {
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptBrowser(data: IData, key: Uint8Array, name: string) {
async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const ciphertext = decodeBase64(data.ciphertext);
@@ -206,7 +207,7 @@ async function decryptBrowser(data: IData, key: Uint8Array, name: string) {
return new TextDecoder().decode(new Uint8Array(plaintext));
}
async function deriveKeysBrowser(key, name) {
async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
const hkdfkey = await subtleCrypto.importKey(
'raw',
key,
@@ -252,11 +253,11 @@ async function deriveKeysBrowser(key, name) {
return await Promise.all([aesProm, hmacProm]);
}
export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string) {
export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
return subtleCrypto ? encryptBrowser(data, key, name, ivStr) : encryptNode(data, key, name, ivStr);
}
export function decryptAES(data: IData, key: Uint8Array, name: string) {
export function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
return subtleCrypto ? decryptBrowser(data, key, name) : decryptNode(data, key, name);
}

View File

@@ -29,16 +29,11 @@ import { keyFromPassphrase } from './key_passphrase';
import { sleep } from "../utils";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { encodeRecoveryKey } from './recoverykey';
import { IKeyBackupVersion } from "./keybackup";
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
type AuthData = Record<string, any>;
type BackupInfo = {
algorithm: string,
auth_data: AuthData, // eslint-disable-line camelcase
[properties: string]: any,
};
type AuthData = IKeyBackupVersion["auth_data"];
type SigInfo = {
deviceId: string,
@@ -54,13 +49,22 @@ export type TrustInfo = {
};
export interface IKeyBackupCheck {
backupInfo: BackupInfo;
backupInfo: IKeyBackupVersion;
trustInfo: TrustInfo;
}
/* eslint-disable camelcase */
export interface IPreparedKeyBackupVersion {
algorithm: string;
auth_data: AuthData;
recovery_key: string;
privateKey: Uint8Array;
}
/* eslint-enable camelcase */
/** A function used to get the secret key for a backup.
*/
type GetKey = () => Promise<Uint8Array>;
type GetKey = () => Promise<ArrayLike<number>>;
interface BackupAlgorithmClass {
algorithmName: string;
@@ -77,7 +81,7 @@ interface BackupAlgorithm {
encryptSession(data: Record<string, any>): Promise<any>;
decryptSessions(ciphertexts: Record<string, any>): Promise<Record<string, any>[]>;
authData: AuthData;
keyMatches(key: Uint8Array): Promise<boolean>;
keyMatches(key: ArrayLike<number>): Promise<boolean>;
free(): void;
}
@@ -86,7 +90,7 @@ interface BackupAlgorithm {
*/
export class BackupManager {
private algorithm: BackupAlgorithm | undefined;
public backupInfo: BackupInfo | undefined; // The info dict from /room_keys/version
public backupInfo: IKeyBackupVersion | undefined; // The info dict from /room_keys/version
public checkedForBackup: boolean; // Have we checked the server for a backup we can use?
private sendingBackups: boolean; // Are we currently sending backups?
constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) {
@@ -98,7 +102,7 @@ export class BackupManager {
return this.backupInfo && this.backupInfo.version;
}
public static async makeAlgorithm(info: BackupInfo, getKey: GetKey): Promise<BackupAlgorithm> {
public static async makeAlgorithm(info: IKeyBackupVersion, getKey: GetKey): Promise<BackupAlgorithm> {
const Algorithm = algorithmsByName[info.algorithm];
if (!Algorithm) {
throw new Error("Unknown backup algorithm");
@@ -106,7 +110,7 @@ export class BackupManager {
return await Algorithm.init(info.auth_data, getKey);
}
public async enableKeyBackup(info: BackupInfo): Promise<void> {
public async enableKeyBackup(info: IKeyBackupVersion): Promise<void> {
this.backupInfo = info;
if (this.algorithm) {
this.algorithm.free();
@@ -145,7 +149,8 @@ export class BackupManager {
public async prepareKeyBackupVersion(
key?: string | Uint8Array | null,
algorithm?: string | undefined,
): Promise<BackupInfo> {
// eslint-disable-next-line camelcase
): Promise<IPreparedKeyBackupVersion> {
const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm;
if (!Algorithm) {
throw new Error("Unknown backup algorithm");
@@ -161,7 +166,7 @@ export class BackupManager {
};
}
public async createKeyBackupVersion(info: BackupInfo): Promise<void> {
public async createKeyBackupVersion(info: IKeyBackupVersion): Promise<void> {
this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey);
}
@@ -171,14 +176,14 @@ export class BackupManager {
* one of the user's verified devices, start backing up
* to it.
*/
public async checkAndStart(): Promise<{backupInfo: BackupInfo, trustInfo: TrustInfo}> {
public async checkAndStart(): Promise<IKeyBackupCheck> {
logger.log("Checking key backup status...");
if (this.baseApis.isGuest()) {
logger.log("Skipping key backup check since user is guest");
this.checkedForBackup = true;
return null;
}
let backupInfo: BackupInfo;
let backupInfo: IKeyBackupVersion;
try {
backupInfo = await this.baseApis.getKeyBackupVersion();
} catch (e) {
@@ -255,7 +260,7 @@ export class BackupManager {
* ]
* }
*/
public async isKeyBackupTrusted(backupInfo: BackupInfo): Promise<TrustInfo> {
public async isKeyBackupTrusted(backupInfo: IKeyBackupVersion): Promise<TrustInfo> {
const ret = {
usable: false,
trusted_locally: false,
@@ -569,7 +574,7 @@ export class Curve25519 implements BackupAlgorithm {
): Promise<[Uint8Array, AuthData]> {
const decryption = new global.Olm.PkDecryption();
try {
const authData: AuthData = {};
const authData: Partial<AuthData> = {};
if (!key) {
authData.public_key = decryption.generate_key();
} else if (key instanceof Uint8Array) {
@@ -585,7 +590,7 @@ export class Curve25519 implements BackupAlgorithm {
return [
decryption.get_private_key(),
authData,
authData as AuthData,
];
} finally {
decryption.free();

View File

@@ -138,11 +138,6 @@ interface IDeviceVerificationUpgrade {
* could be established
*/
interface IOlmSessionResult {
device: DeviceInfo;
sessionId?: string;
}
interface IUserOlmSession {
deviceIdKey: string;
sessions: {
@@ -1079,17 +1074,17 @@ export class Crypto extends EventEmitter {
* @param {Uint8Array} key the private key
* @returns {Promise} so you can catch failures
*/
public async storeSessionBackupPrivateKey(key: Uint8Array): Promise<void> {
public async storeSessionBackupPrivateKey(key: ArrayLike<number>): Promise<void> {
if (!(key instanceof Uint8Array)) {
throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`);
}
const pickleKey = Buffer.from(this.olmDevice._pickleKey);
key = await encryptAES(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1");
const encryptedKey = await encryptAES(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1");
return this.cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", key);
this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", encryptedKey);
},
);
}
@@ -2573,7 +2568,7 @@ export class Crypto extends EventEmitter {
* an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult}
*/
ensureOlmSessionsForUsers(users: string[]): Promise<IOlmSessionResult> {
ensureOlmSessionsForUsers(users: string[]): Promise<Record<string, Record<string, olmlib.IOlmSessionResult>>> {
const devicesByUser = {};
for (let i = 0; i < users.length; ++i) {
@@ -2598,9 +2593,7 @@ export class Crypto extends EventEmitter {
}
}
return olmlib.ensureOlmSessionsForDevices(
this.olmDevice, this.baseApis, devicesByUser,
);
return olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser);
}
/**
@@ -3259,9 +3252,7 @@ export class Crypto extends EventEmitter {
}
const devicesByUser = {};
devicesByUser[sender] = [device];
await olmlib.ensureOlmSessionsForDevices(
this.olmDevice, this.baseApis, devicesByUser, true,
);
await olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, true);
this.lastNewSessionForced[sender][deviceKey] = Date.now();

View File

@@ -44,7 +44,6 @@ export interface IKeyBackupVersion {
count: number;
etag: string;
version: string; // number contained within
recovery_key: string;
}
/* eslint-enable camelcase */

View File

@@ -52,6 +52,11 @@ export const MEGOLM_ALGORITHM = Algorithm.Megolm;
*/
export const MEGOLM_BACKUP_ALGORITHM = Algorithm.MegolmBackup;
export interface IOlmSessionResult {
device: DeviceInfo;
sessionId?: string;
}
/**
* Encrypt an event payload for an Olm device
*
@@ -209,11 +214,11 @@ export async function ensureOlmSessionsForDevices(
olmDevice: OlmDevice,
baseApis: MatrixClient,
devicesByUser: Record<string, DeviceInfo[]>,
force: boolean,
otkTimeout: number,
failedServers: string[],
log: Logger,
) {
force = false,
otkTimeout?: number,
failedServers?: string[],
log: Logger = logger,
): Promise<Record<string, Record<string, IOlmSessionResult>>> {
if (typeof force === "number") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - backwards compatibility
@@ -224,9 +229,6 @@ export async function ensureOlmSessionsForDevices(
otkTimeout = force;
force = false;
}
if (!log) {
log = logger;
}
const devicesWithoutSession = [
// [userId, deviceId], ...
@@ -439,9 +441,9 @@ async function _verifyKeyAndStartSession(
return sid;
}
interface IObject {
unsigned: object;
signatures: object;
export interface IObject {
unsigned?: object;
signatures?: object;
}
/**
@@ -555,7 +557,7 @@ export function pkVerify(obj: IObject, pubKey: string, userId: string) {
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
export function encodeBase64(uint8Array: ArrayBuffer): string {
export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string {
return Buffer.from(uint8Array).toString("base64");
}

View File

@@ -35,7 +35,7 @@ export function encodeRecoveryKey(key: ArrayLike<number>): string {
return base58key.match(/.{1,4}/g).join(" ");
}
export function decodeRecoveryKey(recoveryKey: string): ArrayLike<number> {
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
const result = bs58.decode(recoveryKey.replace(/ /g, ''));
let parity = 0;