You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Remove node-specific crypto bits, use Node 16's WebCrypto (#2762)
This commit is contained in:
committed by
GitHub
parent
6af3b114e1
commit
30570bcce6
@@ -1133,10 +1133,10 @@ describe("megolm", () => {
|
|||||||
'readonly',
|
'readonly',
|
||||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string) => {
|
beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string | null) => {
|
||||||
const account = new global.Olm.Account();
|
const account = new global.Olm.Account();
|
||||||
try {
|
try {
|
||||||
account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount);
|
account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount!);
|
||||||
p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key);
|
p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key);
|
||||||
} finally {
|
} finally {
|
||||||
account.free();
|
account.free();
|
||||||
@@ -1271,10 +1271,10 @@ describe("megolm", () => {
|
|||||||
'readonly',
|
'readonly',
|
||||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string) => {
|
beccaTestClient.client.crypto!.cryptoStore.getAccount(txn, (pickledAccount: string | null) => {
|
||||||
const account = new global.Olm.Account();
|
const account = new global.Olm.Account();
|
||||||
try {
|
try {
|
||||||
account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount);
|
account.unpickle(beccaTestClient.client.crypto!.olmDevice.pickleKey, pickledAccount!);
|
||||||
p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key);
|
p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key);
|
||||||
} finally {
|
} finally {
|
||||||
account.free();
|
account.free();
|
||||||
|
@@ -16,7 +16,6 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from '../src/logger';
|
import { logger } from '../src/logger';
|
||||||
import * as utils from "../src/utils";
|
|
||||||
|
|
||||||
// try to load the olm library.
|
// try to load the olm library.
|
||||||
try {
|
try {
|
||||||
@@ -26,12 +25,3 @@ try {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn("unable to run crypto tests: libolm not available");
|
logger.warn("unable to run crypto tests: libolm not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
// also try to set node crypto
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const crypto = require('crypto');
|
|
||||||
utils.setCrypto(crypto);
|
|
||||||
} catch (err) {
|
|
||||||
logger.log('nodejs was compiled without crypto support: some tests will fail');
|
|
||||||
}
|
|
||||||
|
@@ -23,19 +23,10 @@ import { makeTestClients } from './verification/util';
|
|||||||
import { encryptAES } from "../../../src/crypto/aes";
|
import { encryptAES } from "../../../src/crypto/aes";
|
||||||
import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils";
|
import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils";
|
||||||
import { logger } from '../../../src/logger';
|
import { logger } from '../../../src/logger';
|
||||||
import * as utils from "../../../src/utils";
|
|
||||||
import { ICreateClientOpts } from '../../../src/client';
|
import { ICreateClientOpts } from '../../../src/client';
|
||||||
import { ISecretStorageKeyInfo } from '../../../src/crypto/api';
|
import { ISecretStorageKeyInfo } from '../../../src/crypto/api';
|
||||||
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
|
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
|
||||||
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const crypto = require('crypto');
|
|
||||||
utils.setCrypto(crypto);
|
|
||||||
} catch (err) {
|
|
||||||
logger.log('nodejs was compiled without crypto support');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeTestClient(userInfo: { userId: string, deviceId: string}, options: Partial<ICreateClientOpts> = {}) {
|
async function makeTestClient(userInfo: { userId: string, deviceId: string}, options: Partial<ICreateClientOpts> = {}) {
|
||||||
const client = (new TestClient(
|
const client = (new TestClient(
|
||||||
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
|
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
|
||||||
|
@@ -31,7 +31,7 @@ import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
|
|||||||
import { OlmDevice } from "./OlmDevice";
|
import { OlmDevice } from "./OlmDevice";
|
||||||
import { ICryptoCallbacks } from "../matrix";
|
import { ICryptoCallbacks } from "../matrix";
|
||||||
import { ISignatures } from "../@types/signed";
|
import { ISignatures } from "../@types/signed";
|
||||||
import { CryptoStore } from "./store/base";
|
import { CryptoStore, SecretStorePrivateKeys } from "./store/base";
|
||||||
import { ISecretStorageKeyInfo } from "./api";
|
import { ISecretStorageKeyInfo } from "./api";
|
||||||
|
|
||||||
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
|
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
|
||||||
@@ -699,7 +699,10 @@ export class DeviceTrustLevel {
|
|||||||
|
|
||||||
export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: OlmDevice): ICacheCallbacks {
|
export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: OlmDevice): ICacheCallbacks {
|
||||||
return {
|
return {
|
||||||
getCrossSigningKeyCache: async function(type: string, _expectedPublicKey: string): Promise<Uint8Array> {
|
getCrossSigningKeyCache: async function(
|
||||||
|
type: keyof SecretStorePrivateKeys,
|
||||||
|
_expectedPublicKey: string,
|
||||||
|
): Promise<Uint8Array> {
|
||||||
const key = await new Promise<any>((resolve) => {
|
const key = await new Promise<any>((resolve) => {
|
||||||
return store.doTxn(
|
return store.doTxn(
|
||||||
'readonly',
|
'readonly',
|
||||||
@@ -718,7 +721,10 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
storeCrossSigningKeyCache: async function(type: string, key: Uint8Array): Promise<void> {
|
storeCrossSigningKeyCache: async function(
|
||||||
|
type: keyof SecretStorePrivateKeys,
|
||||||
|
key: Uint8Array,
|
||||||
|
): Promise<void> {
|
||||||
if (!(key instanceof Uint8Array)) {
|
if (!(key instanceof Uint8Array)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
|
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
|
||||||
|
@@ -308,10 +308,10 @@ export class OlmDevice {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private getAccount(txn: unknown, func: (account: Account) => void): void {
|
private getAccount(txn: unknown, func: (account: Account) => void): void {
|
||||||
this.cryptoStore.getAccount(txn, (pickledAccount: string) => {
|
this.cryptoStore.getAccount(txn, (pickledAccount: string | null) => {
|
||||||
const account = new global.Olm.Account();
|
const account = new global.Olm.Account();
|
||||||
try {
|
try {
|
||||||
account.unpickle(this.pickleKey, pickledAccount);
|
account.unpickle(this.pickleKey, pickledAccount!);
|
||||||
func(account);
|
func(account);
|
||||||
} finally {
|
} finally {
|
||||||
account.free();
|
account.free();
|
||||||
@@ -350,8 +350,8 @@ export class OlmDevice {
|
|||||||
IndexedDBCryptoStore.STORE_SESSIONS,
|
IndexedDBCryptoStore.STORE_SESSIONS,
|
||||||
],
|
],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this.cryptoStore.getAccount(txn, (pickledAccount: string) => {
|
this.cryptoStore.getAccount(txn, (pickledAccount: string | null) => {
|
||||||
result.pickledAccount = pickledAccount;
|
result.pickledAccount = pickledAccount!;
|
||||||
});
|
});
|
||||||
result.sessions = [];
|
result.sessions = [];
|
||||||
// Note that the pickledSession object we get in the callback
|
// Note that the pickledSession object we get in the callback
|
||||||
|
@@ -14,129 +14,39 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BinaryLike } from "crypto";
|
|
||||||
import { getCrypto } from '../utils';
|
|
||||||
import { decodeBase64, encodeBase64 } from './olmlib';
|
import { decodeBase64, encodeBase64 } from './olmlib';
|
||||||
|
import { subtleCrypto, crypto, TextEncoder } from "./crypto";
|
||||||
const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
|
|
||||||
(window.crypto.subtle || window.crypto.webkitSubtle) : null;
|
|
||||||
|
|
||||||
// salt for HKDF, with 8 bytes of zeros
|
// salt for HKDF, with 8 bytes of zeros
|
||||||
const zeroSalt = new Uint8Array(8);
|
const zeroSalt = new Uint8Array(8);
|
||||||
|
|
||||||
export interface IEncryptedPayload {
|
export interface IEncryptedPayload {
|
||||||
[key: string]: any; // extensible
|
[key: string]: any; // extensible
|
||||||
iv?: string;
|
iv: string;
|
||||||
ciphertext?: string;
|
ciphertext: string;
|
||||||
mac?: string;
|
mac: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* encrypt a string in Node.js
|
* encrypt a string
|
||||||
*
|
*
|
||||||
* @param {string} data the plaintext to encrypt
|
* @param {string} data the plaintext to encrypt
|
||||||
* @param {Uint8Array} key the encryption key to use
|
* @param {Uint8Array} key the encryption key to use
|
||||||
* @param {string} name the name of the secret
|
* @param {string} name the name of the secret
|
||||||
* @param {string} ivStr the initialization vector to use
|
* @param {string} ivStr the initialization vector to use
|
||||||
*/
|
*/
|
||||||
async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
|
export async function encryptAES(
|
||||||
const crypto = getCrypto();
|
data: string,
|
||||||
if (!crypto) {
|
key: Uint8Array,
|
||||||
throw new Error("No usable crypto implementation");
|
name: string,
|
||||||
}
|
ivStr?: string,
|
||||||
|
): Promise<IEncryptedPayload> {
|
||||||
let iv;
|
let iv: Uint8Array;
|
||||||
if (ivStr) {
|
|
||||||
iv = decodeBase64(ivStr);
|
|
||||||
} else {
|
|
||||||
iv = crypto.randomBytes(16);
|
|
||||||
|
|
||||||
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
|
|
||||||
// (which would mean we wouldn't be able to decrypt on Android). The loss
|
|
||||||
// of a single bit of iv is a price we have to pay.
|
|
||||||
iv[8] &= 0x7f;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [aesKey, hmacKey] = deriveKeysNode(key, name);
|
|
||||||
|
|
||||||
const cipher = crypto.createCipheriv("aes-256-ctr", aesKey, iv);
|
|
||||||
const ciphertext = Buffer.concat([
|
|
||||||
cipher.update(data, "utf8"),
|
|
||||||
cipher.final(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const hmac = crypto.createHmac("sha256", hmacKey)
|
|
||||||
.update(ciphertext).digest("base64");
|
|
||||||
|
|
||||||
return {
|
|
||||||
iv: encodeBase64(iv),
|
|
||||||
ciphertext: ciphertext.toString("base64"),
|
|
||||||
mac: hmac,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* decrypt a string in Node.js
|
|
||||||
*
|
|
||||||
* @param {object} data the encrypted data
|
|
||||||
* @param {string} data.ciphertext the ciphertext in base64
|
|
||||||
* @param {string} data.iv the initialization vector in base64
|
|
||||||
* @param {string} data.mac the HMAC in base64
|
|
||||||
* @param {Uint8Array} key the encryption key to use
|
|
||||||
* @param {string} name the name of the secret
|
|
||||||
*/
|
|
||||||
async function decryptNode(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
|
||||||
const crypto = getCrypto();
|
|
||||||
if (!crypto) {
|
|
||||||
throw new Error("No usable crypto implementation");
|
|
||||||
}
|
|
||||||
|
|
||||||
const [aesKey, hmacKey] = deriveKeysNode(key, name);
|
|
||||||
|
|
||||||
const hmac = crypto.createHmac("sha256", hmacKey)
|
|
||||||
.update(Buffer.from(data.ciphertext, "base64"))
|
|
||||||
.digest("base64").replace(/=+$/g, '');
|
|
||||||
|
|
||||||
if (hmac !== data.mac.replace(/=+$/g, '')) {
|
|
||||||
throw new Error(`Error decrypting secret ${name}: bad MAC`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const decipher = crypto.createDecipheriv(
|
|
||||||
"aes-256-ctr", aesKey, decodeBase64(data.iv),
|
|
||||||
);
|
|
||||||
return decipher.update(data.ciphertext, "base64", "utf8")
|
|
||||||
+ decipher.final("utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
function deriveKeysNode(key: BinaryLike, name: string): [Buffer, Buffer] {
|
|
||||||
const crypto = getCrypto();
|
|
||||||
const prk = crypto.createHmac("sha256", zeroSalt).update(key).digest();
|
|
||||||
|
|
||||||
const b = Buffer.alloc(1, 1);
|
|
||||||
const aesKey = crypto.createHmac("sha256", prk)
|
|
||||||
.update(name, "utf8").update(b).digest();
|
|
||||||
b[0] = 2;
|
|
||||||
const hmacKey = crypto.createHmac("sha256", prk)
|
|
||||||
.update(aesKey).update(name, "utf8").update(b).digest();
|
|
||||||
|
|
||||||
return [aesKey, hmacKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* encrypt a string in Node.js
|
|
||||||
*
|
|
||||||
* @param {string} data the plaintext to encrypt
|
|
||||||
* @param {Uint8Array} key the encryption key to use
|
|
||||||
* @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): Promise<IEncryptedPayload> {
|
|
||||||
let iv;
|
|
||||||
if (ivStr) {
|
if (ivStr) {
|
||||||
iv = decodeBase64(ivStr);
|
iv = decodeBase64(ivStr);
|
||||||
} else {
|
} else {
|
||||||
iv = new Uint8Array(16);
|
iv = new Uint8Array(16);
|
||||||
window.crypto.getRandomValues(iv);
|
crypto.getRandomValues(iv);
|
||||||
|
|
||||||
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
|
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
|
||||||
// (which would mean we wouldn't be able to decrypt on Android). The loss
|
// (which would mean we wouldn't be able to decrypt on Android). The loss
|
||||||
@@ -144,7 +54,7 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
|
|||||||
iv[8] &= 0x7f;
|
iv[8] &= 0x7f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
|
const [aesKey, hmacKey] = await deriveKeys(key, name);
|
||||||
const encodedData = new TextEncoder().encode(data);
|
const encodedData = new TextEncoder().encode(data);
|
||||||
|
|
||||||
const ciphertext = await subtleCrypto.encrypt(
|
const ciphertext = await subtleCrypto.encrypt(
|
||||||
@@ -171,7 +81,7 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* decrypt a string in the browser
|
* decrypt a string
|
||||||
*
|
*
|
||||||
* @param {object} data the encrypted data
|
* @param {object} data the encrypted data
|
||||||
* @param {string} data.ciphertext the ciphertext in base64
|
* @param {string} data.ciphertext the ciphertext in base64
|
||||||
@@ -180,8 +90,8 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
|
|||||||
* @param {Uint8Array} key the encryption key to use
|
* @param {Uint8Array} key the encryption key to use
|
||||||
* @param {string} name the name of the secret
|
* @param {string} name the name of the secret
|
||||||
*/
|
*/
|
||||||
async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
||||||
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
|
const [aesKey, hmacKey] = await deriveKeys(key, name);
|
||||||
|
|
||||||
const ciphertext = decodeBase64(data.ciphertext);
|
const ciphertext = decodeBase64(data.ciphertext);
|
||||||
|
|
||||||
@@ -207,7 +117,7 @@ async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: st
|
|||||||
return new TextDecoder().decode(new Uint8Array(plaintext));
|
return new TextDecoder().decode(new Uint8Array(plaintext));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
|
async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
|
||||||
const hkdfkey = await subtleCrypto.importKey(
|
const hkdfkey = await subtleCrypto.importKey(
|
||||||
'raw',
|
'raw',
|
||||||
key,
|
key,
|
||||||
@@ -253,14 +163,6 @@ async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[Crypto
|
|||||||
return Promise.all([aesProm, hmacProm]);
|
return Promise.all([aesProm, hmacProm]);
|
||||||
}
|
}
|
||||||
|
|
||||||
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: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
|
||||||
return subtleCrypto ? decryptBrowser(data, key, name) : decryptNode(data, key, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// string of zeroes, for calculating the key check
|
// string of zeroes, for calculating the key check
|
||||||
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||||
|
|
||||||
|
@@ -26,13 +26,20 @@ import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib";
|
|||||||
import { DeviceInfo } from "./deviceinfo";
|
import { DeviceInfo } from "./deviceinfo";
|
||||||
import { DeviceTrustLevel } from './CrossSigning';
|
import { DeviceTrustLevel } from './CrossSigning';
|
||||||
import { keyFromPassphrase } from './key_passphrase';
|
import { keyFromPassphrase } from './key_passphrase';
|
||||||
import { getCrypto, sleep } from "../utils";
|
import { sleep } from "../utils";
|
||||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||||
import { encodeRecoveryKey } from './recoverykey';
|
import { encodeRecoveryKey } from './recoverykey';
|
||||||
import { calculateKeyCheck, decryptAES, encryptAES } from './aes';
|
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
|
||||||
import { IAes256AuthData, ICurve25519AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup";
|
import {
|
||||||
|
Curve25519SessionData,
|
||||||
|
IAes256AuthData,
|
||||||
|
ICurve25519AuthData,
|
||||||
|
IKeyBackupInfo,
|
||||||
|
IKeyBackupSession,
|
||||||
|
} from "./keybackup";
|
||||||
import { UnstableValue } from "../NamespacedValue";
|
import { UnstableValue } from "../NamespacedValue";
|
||||||
import { CryptoEvent, IMegolmSessionData } from "./index";
|
import { CryptoEvent, IMegolmSessionData } from "./index";
|
||||||
|
import { crypto } from "./crypto";
|
||||||
|
|
||||||
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
|
||||||
@@ -677,7 +684,9 @@ export class Curve25519 implements BackupAlgorithm {
|
|||||||
return this.publicKey.encrypt(JSON.stringify(plainText));
|
return this.publicKey.encrypt(JSON.stringify(plainText));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async decryptSessions(sessions: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]> {
|
public async decryptSessions(
|
||||||
|
sessions: Record<string, IKeyBackupSession<Curve25519SessionData>>,
|
||||||
|
): Promise<IMegolmSessionData[]> {
|
||||||
const privKey = await this.getKey();
|
const privKey = await this.getKey();
|
||||||
const decryption = new global.Olm.PkDecryption();
|
const decryption = new global.Olm.PkDecryption();
|
||||||
try {
|
try {
|
||||||
@@ -711,7 +720,7 @@ export class Curve25519 implements BackupAlgorithm {
|
|||||||
|
|
||||||
public async keyMatches(key: Uint8Array): Promise<boolean> {
|
public async keyMatches(key: Uint8Array): Promise<boolean> {
|
||||||
const decryption = new global.Olm.PkDecryption();
|
const decryption = new global.Olm.PkDecryption();
|
||||||
let pubKey;
|
let pubKey: string;
|
||||||
try {
|
try {
|
||||||
pubKey = decryption.init_with_private_key(key);
|
pubKey = decryption.init_with_private_key(key);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -727,18 +736,9 @@ export class Curve25519 implements BackupAlgorithm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function randomBytes(size: number): Uint8Array {
|
function randomBytes(size: number): Uint8Array {
|
||||||
const crypto: {randomBytes: (n: number) => Uint8Array} | undefined = getCrypto() as any;
|
const buf = new Uint8Array(size);
|
||||||
if (crypto) {
|
crypto.getRandomValues(buf);
|
||||||
// nodejs version
|
return buf;
|
||||||
return crypto.randomBytes(size);
|
|
||||||
}
|
|
||||||
if (window?.crypto) {
|
|
||||||
// browser version
|
|
||||||
const buf = new Uint8Array(size);
|
|
||||||
window.crypto.getRandomValues(buf);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
throw new Error("No usable crypto implementation");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNSTABLE_MSC3270_NAME = new UnstableValue(null, "org.matrix.msc3270.v1.aes-hmac-sha2");
|
const UNSTABLE_MSC3270_NAME = new UnstableValue(null, "org.matrix.msc3270.v1.aes-hmac-sha2");
|
||||||
@@ -807,7 +807,9 @@ export class Aes256 implements BackupAlgorithm {
|
|||||||
return encryptAES(JSON.stringify(plainText), this.key, data.session_id);
|
return encryptAES(JSON.stringify(plainText), this.key, data.session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async decryptSessions(sessions: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]> {
|
public async decryptSessions(
|
||||||
|
sessions: Record<string, IKeyBackupSession<IEncryptedPayload>>,
|
||||||
|
): Promise<IMegolmSessionData[]> {
|
||||||
const keys: IMegolmSessionData[] = [];
|
const keys: IMegolmSessionData[] = [];
|
||||||
|
|
||||||
for (const [sessionId, sessionData] of Object.entries(sessions)) {
|
for (const [sessionId, sessionData] of Object.entries(sessions)) {
|
||||||
|
31
src/crypto/crypto.ts
Normal file
31
src/crypto/crypto.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export let crypto = global.window?.crypto;
|
||||||
|
export let subtleCrypto = global.window?.crypto?.subtle ?? global.window?.crypto?.webkitSubtle;
|
||||||
|
export let TextEncoder = global.window?.TextEncoder;
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
if (!crypto) {
|
||||||
|
crypto = require("crypto").webcrypto;
|
||||||
|
}
|
||||||
|
if (!subtleCrypto) {
|
||||||
|
subtleCrypto = crypto?.subtle;
|
||||||
|
}
|
||||||
|
if (!TextEncoder) {
|
||||||
|
TextEncoder = require("util").TextEncoder;
|
||||||
|
}
|
||||||
|
/* eslint-enable @typescript-eslint/no-var-requires */
|
@@ -15,10 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
import { getCrypto } from '../utils';
|
import { subtleCrypto, TextEncoder } from "./crypto";
|
||||||
|
|
||||||
const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
|
|
||||||
(window.crypto.subtle || window.crypto.webkitSubtle) : null;
|
|
||||||
|
|
||||||
const DEFAULT_ITERATIONS = 500000;
|
const DEFAULT_ITERATIONS = 500000;
|
||||||
|
|
||||||
@@ -75,21 +72,8 @@ export async function deriveKey(
|
|||||||
iterations: number,
|
iterations: number,
|
||||||
numBits = DEFAULT_BITSIZE,
|
numBits = DEFAULT_BITSIZE,
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
return subtleCrypto
|
|
||||||
? deriveKeyBrowser(password, salt, iterations, numBits)
|
|
||||||
: deriveKeyNode(password, salt, iterations, numBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deriveKeyBrowser(
|
|
||||||
password: string,
|
|
||||||
salt: string,
|
|
||||||
iterations: number,
|
|
||||||
numBits: number,
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
const subtleCrypto = global.crypto.subtle;
|
|
||||||
const TextEncoder = global.TextEncoder;
|
|
||||||
if (!subtleCrypto || !TextEncoder) {
|
if (!subtleCrypto || !TextEncoder) {
|
||||||
throw new Error("Password-based backup is not avaiable on this platform");
|
throw new Error("Password-based backup is not available on this platform");
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = await subtleCrypto.importKey(
|
const key = await subtleCrypto.importKey(
|
||||||
@@ -113,17 +97,3 @@ async function deriveKeyBrowser(
|
|||||||
|
|
||||||
return new Uint8Array(keybits);
|
return new Uint8Array(keybits);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deriveKeyNode(
|
|
||||||
password: string,
|
|
||||||
salt: string,
|
|
||||||
iterations: number,
|
|
||||||
numBits: number,
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
const crypto = getCrypto();
|
|
||||||
if (!crypto) {
|
|
||||||
throw new Error("No usable crypto implementation");
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.pbkdf2Sync(password, Buffer.from(salt, 'binary'), iterations, numBits, 'sha512');
|
|
||||||
}
|
|
||||||
|
@@ -23,18 +23,18 @@ export interface Curve25519SessionData {
|
|||||||
mac: string;
|
mac: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKeyBackupSession {
|
/* eslint-disable camelcase */
|
||||||
first_message_index: number; // eslint-disable-line camelcase
|
export interface IKeyBackupSession<T = Curve25519SessionData | IEncryptedPayload> {
|
||||||
forwarded_count: number; // eslint-disable-line camelcase
|
first_message_index: number;
|
||||||
is_verified: boolean; // eslint-disable-line camelcase
|
forwarded_count: number;
|
||||||
session_data: Curve25519SessionData | IEncryptedPayload; // eslint-disable-line camelcase
|
is_verified: boolean;
|
||||||
|
session_data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKeyBackupRoomSessions {
|
export interface IKeyBackupRoomSessions {
|
||||||
[sessionId: string]: IKeyBackupSession;
|
[sessionId: string]: IKeyBackupSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
export interface ICurve25519AuthData {
|
export interface ICurve25519AuthData {
|
||||||
public_key: string;
|
public_key: string;
|
||||||
private_key_salt?: string;
|
private_key_salt?: string;
|
||||||
|
@@ -24,8 +24,9 @@ import { IDevice } from "../deviceinfo";
|
|||||||
import { ICrossSigningInfo } from "../CrossSigning";
|
import { ICrossSigningInfo } from "../CrossSigning";
|
||||||
import { PrefixedLogger } from "../../logger";
|
import { PrefixedLogger } from "../../logger";
|
||||||
import { InboundGroupSessionData } from "../OlmDevice";
|
import { InboundGroupSessionData } from "../OlmDevice";
|
||||||
import { IEncryptedPayload } from "../aes";
|
|
||||||
import { MatrixEvent } from "../../models/event";
|
import { MatrixEvent } from "../../models/event";
|
||||||
|
import { DehydrationManager } from "../dehydration";
|
||||||
|
import { IEncryptedPayload } from "../aes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal module. Definitions for storage for the crypto module
|
* Internal module. Definitions for storage for the crypto module
|
||||||
@@ -33,6 +34,16 @@ import { MatrixEvent } from "../../models/event";
|
|||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export interface SecretStorePrivateKeys {
|
||||||
|
dehydration: {
|
||||||
|
keyInfo: DehydrationManager["keyInfo"];
|
||||||
|
key: IEncryptedPayload;
|
||||||
|
deviceDisplayName: string;
|
||||||
|
time: number;
|
||||||
|
} | null;
|
||||||
|
"m.megolm_backup.v1": IEncryptedPayload;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction of things that can store data required for end-to-end encryption
|
* Abstraction of things that can store data required for end-to-end encryption
|
||||||
*
|
*
|
||||||
@@ -58,12 +69,20 @@ export interface CryptoStore {
|
|||||||
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
|
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
|
||||||
|
|
||||||
// Olm Account
|
// Olm Account
|
||||||
getAccount(txn: unknown, func: (accountPickle: string) => void);
|
getAccount(txn: unknown, func: (accountPickle: string | null) => void);
|
||||||
storeAccount(txn: unknown, accountPickle: string): void;
|
storeAccount(txn: unknown, accountPickle: string): void;
|
||||||
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void;
|
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
|
||||||
getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void;
|
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
txn: unknown,
|
||||||
|
func: (key: SecretStorePrivateKeys[K] | null) => void,
|
||||||
|
type: K,
|
||||||
|
): void;
|
||||||
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
|
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
|
||||||
storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void;
|
storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
txn: unknown,
|
||||||
|
type: K,
|
||||||
|
key: SecretStorePrivateKeys[K],
|
||||||
|
): void;
|
||||||
|
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
countEndToEndSessions(txn: unknown, func: (count: number) => void): void;
|
countEndToEndSessions(txn: unknown, func: (count: number) => void): void;
|
||||||
|
@@ -25,14 +25,13 @@ import {
|
|||||||
IWithheld,
|
IWithheld,
|
||||||
Mode,
|
Mode,
|
||||||
OutgoingRoomKeyRequest,
|
OutgoingRoomKeyRequest,
|
||||||
ParkedSharedHistory,
|
ParkedSharedHistory, SecretStorePrivateKeys,
|
||||||
} from "./base";
|
} from "./base";
|
||||||
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
|
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
|
||||||
import { ICrossSigningKey } from "../../client";
|
import { ICrossSigningKey } from "../../client";
|
||||||
import { IOlmDevice } from "../algorithms/megolm";
|
import { IOlmDevice } from "../algorithms/megolm";
|
||||||
import { IRoomEncryption } from "../RoomList";
|
import { IRoomEncryption } from "../RoomList";
|
||||||
import { InboundGroupSessionData } from "../OlmDevice";
|
import { InboundGroupSessionData } from "../OlmDevice";
|
||||||
import { IEncryptedPayload } from "../aes";
|
|
||||||
|
|
||||||
const PROFILE_TRANSACTIONS = false;
|
const PROFILE_TRANSACTIONS = false;
|
||||||
|
|
||||||
@@ -369,7 +368,7 @@ export class Backend implements CryptoStore {
|
|||||||
|
|
||||||
// Olm Account
|
// Olm Account
|
||||||
|
|
||||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void): void {
|
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void): void {
|
||||||
const objectStore = txn.objectStore("account");
|
const objectStore = txn.objectStore("account");
|
||||||
const getReq = objectStore.get("-");
|
const getReq = objectStore.get("-");
|
||||||
getReq.onsuccess = function() {
|
getReq.onsuccess = function() {
|
||||||
@@ -386,7 +385,10 @@ export class Backend implements CryptoStore {
|
|||||||
objectStore.put(accountPickle, "-");
|
objectStore.put(accountPickle, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
public getCrossSigningKeys(
|
||||||
|
txn: IDBTransaction,
|
||||||
|
func: (keys: Record<string, ICrossSigningKey> | null) => void,
|
||||||
|
): void {
|
||||||
const objectStore = txn.objectStore("account");
|
const objectStore = txn.objectStore("account");
|
||||||
const getReq = objectStore.get("crossSigningKeys");
|
const getReq = objectStore.get("crossSigningKeys");
|
||||||
getReq.onsuccess = function() {
|
getReq.onsuccess = function() {
|
||||||
@@ -398,10 +400,10 @@ export class Backend implements CryptoStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSecretStorePrivateKey(
|
public getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
txn: IDBTransaction,
|
txn: IDBTransaction,
|
||||||
func: (key: IEncryptedPayload | null) => void,
|
func: (key: SecretStorePrivateKeys[K] | null) => void,
|
||||||
type: string,
|
type: K,
|
||||||
): void {
|
): void {
|
||||||
const objectStore = txn.objectStore("account");
|
const objectStore = txn.objectStore("account");
|
||||||
const getReq = objectStore.get(`ssss_cache:${type}`);
|
const getReq = objectStore.get(`ssss_cache:${type}`);
|
||||||
@@ -419,7 +421,11 @@ export class Backend implements CryptoStore {
|
|||||||
objectStore.put(keys, "crossSigningKeys");
|
objectStore.put(keys, "crossSigningKeys");
|
||||||
}
|
}
|
||||||
|
|
||||||
public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void {
|
public storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
txn: IDBTransaction,
|
||||||
|
type: K,
|
||||||
|
key: SecretStorePrivateKeys[K],
|
||||||
|
): void {
|
||||||
const objectStore = txn.objectStore("account");
|
const objectStore = txn.objectStore("account");
|
||||||
objectStore.put(key, `ssss_cache:${type}`);
|
objectStore.put(key, `ssss_cache:${type}`);
|
||||||
}
|
}
|
||||||
|
@@ -29,14 +29,13 @@ import {
|
|||||||
IWithheld,
|
IWithheld,
|
||||||
Mode,
|
Mode,
|
||||||
OutgoingRoomKeyRequest,
|
OutgoingRoomKeyRequest,
|
||||||
ParkedSharedHistory,
|
ParkedSharedHistory, SecretStorePrivateKeys,
|
||||||
} from "./base";
|
} from "./base";
|
||||||
import { IRoomKeyRequestBody } from "../index";
|
import { IRoomKeyRequestBody } from "../index";
|
||||||
import { ICrossSigningKey } from "../../client";
|
import { ICrossSigningKey } from "../../client";
|
||||||
import { IOlmDevice } from "../algorithms/megolm";
|
import { IOlmDevice } from "../algorithms/megolm";
|
||||||
import { IRoomEncryption } from "../RoomList";
|
import { IRoomEncryption } from "../RoomList";
|
||||||
import { InboundGroupSessionData } from "../OlmDevice";
|
import { InboundGroupSessionData } from "../OlmDevice";
|
||||||
import { IEncryptedPayload } from "../aes";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal module. indexeddb storage for e2e.
|
* Internal module. indexeddb storage for e2e.
|
||||||
@@ -323,7 +322,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
|||||||
* @param {*} txn An active transaction. See doTxn().
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
* @param {function(string)} func Called with the account pickle
|
* @param {function(string)} func Called with the account pickle
|
||||||
*/
|
*/
|
||||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void) {
|
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void) {
|
||||||
this.backend.getAccount(txn, func);
|
this.backend.getAccount(txn, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +345,10 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
|||||||
* @param {function(string)} func Called with the account keys object:
|
* @param {function(string)} func Called with the account keys object:
|
||||||
* { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed
|
* { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed
|
||||||
*/
|
*/
|
||||||
public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
public getCrossSigningKeys(
|
||||||
|
txn: IDBTransaction,
|
||||||
|
func: (keys: Record<string, ICrossSigningKey> | null) => void,
|
||||||
|
): void {
|
||||||
this.backend.getCrossSigningKeys(txn, func);
|
this.backend.getCrossSigningKeys(txn, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,10 +357,10 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
|||||||
* @param {function(string)} func Called with the private key
|
* @param {function(string)} func Called with the private key
|
||||||
* @param {string} type A key type
|
* @param {string} type A key type
|
||||||
*/
|
*/
|
||||||
public getSecretStorePrivateKey(
|
public getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
txn: IDBTransaction,
|
txn: IDBTransaction,
|
||||||
func: (key: IEncryptedPayload | null) => void,
|
func: (key: SecretStorePrivateKeys[K] | null) => void,
|
||||||
type: string,
|
type: K,
|
||||||
): void {
|
): void {
|
||||||
this.backend.getSecretStorePrivateKey(txn, func, type);
|
this.backend.getSecretStorePrivateKey(txn, func, type);
|
||||||
}
|
}
|
||||||
@@ -380,7 +382,11 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
|||||||
* @param {string} type The type of cross-signing private key to store
|
* @param {string} type The type of cross-signing private key to store
|
||||||
* @param {string} key keys object as getCrossSigningKeys()
|
* @param {string} key keys object as getCrossSigningKeys()
|
||||||
*/
|
*/
|
||||||
public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void {
|
public storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
txn: IDBTransaction,
|
||||||
|
type: K,
|
||||||
|
key: SecretStorePrivateKeys[K],
|
||||||
|
): void {
|
||||||
this.backend.storeSecretStorePrivateKey(txn, type, key);
|
this.backend.storeSecretStorePrivateKey(txn, type, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,12 +16,11 @@ limitations under the License.
|
|||||||
|
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
import { MemoryCryptoStore } from './memory-crypto-store';
|
import { MemoryCryptoStore } from './memory-crypto-store';
|
||||||
import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode } from "./base";
|
import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode, SecretStorePrivateKeys } from "./base";
|
||||||
import { IOlmDevice } from "../algorithms/megolm";
|
import { IOlmDevice } from "../algorithms/megolm";
|
||||||
import { IRoomEncryption } from "../RoomList";
|
import { IRoomEncryption } from "../RoomList";
|
||||||
import { ICrossSigningKey } from "../../client";
|
import { ICrossSigningKey } from "../../client";
|
||||||
import { InboundGroupSessionData } from "../OlmDevice";
|
import { InboundGroupSessionData } from "../OlmDevice";
|
||||||
import { IEncryptedPayload } from "../aes";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal module. Partial localStorage backed storage for e2e.
|
* Internal module. Partial localStorage backed storage for e2e.
|
||||||
@@ -374,7 +373,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
|
|
||||||
// Olm account
|
// Olm account
|
||||||
|
|
||||||
public getAccount(txn: unknown, func: (accountPickle: string) => void): void {
|
public getAccount(txn: unknown, func: (accountPickle: string | null) => void): void {
|
||||||
const accountPickle = getJsonItem<string>(this.store, KEY_END_TO_END_ACCOUNT);
|
const accountPickle = getJsonItem<string>(this.store, KEY_END_TO_END_ACCOUNT);
|
||||||
func(accountPickle);
|
func(accountPickle);
|
||||||
}
|
}
|
||||||
@@ -383,13 +382,17 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle);
|
setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void {
|
||||||
const keys = getJsonItem<Record<string, ICrossSigningKey>>(this.store, KEY_CROSS_SIGNING_KEYS);
|
const keys = getJsonItem<Record<string, ICrossSigningKey>>(this.store, KEY_CROSS_SIGNING_KEYS);
|
||||||
func(keys);
|
func(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void {
|
public getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
const key = getJsonItem<IEncryptedPayload>(this.store, E2E_PREFIX + `ssss_cache.${type}`);
|
txn: unknown,
|
||||||
|
func: (key: SecretStorePrivateKeys[K] | null) => void,
|
||||||
|
type: K,
|
||||||
|
): void {
|
||||||
|
const key = getJsonItem<SecretStorePrivateKeys[K]>(this.store, E2E_PREFIX + `ssss_cache.${type}`);
|
||||||
func(key);
|
func(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +400,11 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys);
|
setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void {
|
public storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
txn: unknown,
|
||||||
|
type: K,
|
||||||
|
key: SecretStorePrivateKeys[K],
|
||||||
|
): void {
|
||||||
setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key);
|
setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,14 +25,13 @@ import {
|
|||||||
IWithheld,
|
IWithheld,
|
||||||
Mode,
|
Mode,
|
||||||
OutgoingRoomKeyRequest,
|
OutgoingRoomKeyRequest,
|
||||||
ParkedSharedHistory,
|
ParkedSharedHistory, SecretStorePrivateKeys,
|
||||||
} from "./base";
|
} from "./base";
|
||||||
import { IRoomKeyRequestBody } from "../index";
|
import { IRoomKeyRequestBody } from "../index";
|
||||||
import { ICrossSigningKey } from "../../client";
|
import { ICrossSigningKey } from "../../client";
|
||||||
import { IOlmDevice } from "../algorithms/megolm";
|
import { IOlmDevice } from "../algorithms/megolm";
|
||||||
import { IRoomEncryption } from "../RoomList";
|
import { IRoomEncryption } from "../RoomList";
|
||||||
import { InboundGroupSessionData } from "../OlmDevice";
|
import { InboundGroupSessionData } from "../OlmDevice";
|
||||||
import { IEncryptedPayload } from "../aes";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal module. in-memory storage for e2e.
|
* Internal module. in-memory storage for e2e.
|
||||||
@@ -45,9 +44,9 @@ import { IEncryptedPayload } from "../aes";
|
|||||||
*/
|
*/
|
||||||
export class MemoryCryptoStore implements CryptoStore {
|
export class MemoryCryptoStore implements CryptoStore {
|
||||||
private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = [];
|
private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = [];
|
||||||
private account: string = null;
|
private account: string | null = null;
|
||||||
private crossSigningKeys: Record<string, ICrossSigningKey> = null;
|
private crossSigningKeys: Record<string, ICrossSigningKey> | null = null;
|
||||||
private privateKeys: Record<string, IEncryptedPayload> = {};
|
private privateKeys: Partial<SecretStorePrivateKeys> = {};
|
||||||
|
|
||||||
private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {};
|
private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {};
|
||||||
private sessionProblems: { [deviceKey: string]: IProblem[] } = {};
|
private sessionProblems: { [deviceKey: string]: IProblem[] } = {};
|
||||||
@@ -280,7 +279,7 @@ export class MemoryCryptoStore implements CryptoStore {
|
|||||||
|
|
||||||
// Olm Account
|
// Olm Account
|
||||||
|
|
||||||
public getAccount(txn: unknown, func: (accountPickle: string) => void) {
|
public getAccount(txn: unknown, func: (accountPickle: string | null) => void) {
|
||||||
func(this.account);
|
func(this.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,12 +287,16 @@ export class MemoryCryptoStore implements CryptoStore {
|
|||||||
this.account = accountPickle;
|
this.account = accountPickle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void {
|
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void {
|
||||||
func(this.crossSigningKeys);
|
func(this.crossSigningKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSecretStorePrivateKey(txn: unknown, func: (key: IEncryptedPayload | null) => void, type: string): void {
|
public getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
const result = this.privateKeys[type];
|
txn: unknown,
|
||||||
|
func: (key: SecretStorePrivateKeys[K] | null) => void,
|
||||||
|
type: K,
|
||||||
|
): void {
|
||||||
|
const result = this.privateKeys[type] as SecretStorePrivateKeys[K] | undefined;
|
||||||
func(result || null);
|
func(result || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +304,11 @@ export class MemoryCryptoStore implements CryptoStore {
|
|||||||
this.crossSigningKeys = keys;
|
this.crossSigningKeys = keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void {
|
public storeSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
txn: unknown,
|
||||||
|
type: K,
|
||||||
|
key: SecretStorePrivateKeys[K],
|
||||||
|
): void {
|
||||||
this.privateKeys[type] = key;
|
this.privateKeys[type] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/index.ts
10
src/index.ts
@@ -15,22 +15,12 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as matrixcs from "./matrix";
|
import * as matrixcs from "./matrix";
|
||||||
import * as utils from "./utils";
|
|
||||||
import { logger } from './logger';
|
|
||||||
|
|
||||||
if (global.__js_sdk_entrypoint) {
|
if (global.__js_sdk_entrypoint) {
|
||||||
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
||||||
}
|
}
|
||||||
global.__js_sdk_entrypoint = true;
|
global.__js_sdk_entrypoint = true;
|
||||||
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const crypto = require('crypto');
|
|
||||||
utils.setCrypto(crypto);
|
|
||||||
} catch (err) {
|
|
||||||
logger.log('nodejs was compiled without crypto support');
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "./matrix";
|
export * from "./matrix";
|
||||||
export default matrixcs;
|
export default matrixcs;
|
||||||
|
|
||||||
|
15
src/utils.ts
15
src/utils.ts
@@ -23,7 +23,6 @@ limitations under the License.
|
|||||||
import unhomoglyph from "unhomoglyph";
|
import unhomoglyph from "unhomoglyph";
|
||||||
import promiseRetry from "p-retry";
|
import promiseRetry from "p-retry";
|
||||||
|
|
||||||
import type * as NodeCrypto from "crypto";
|
|
||||||
import { MatrixEvent } from "./models/event";
|
import { MatrixEvent } from "./models/event";
|
||||||
import { M_TIMESTAMP } from "./@types/location";
|
import { M_TIMESTAMP } from "./@types/location";
|
||||||
import { ReceiptType } from "./@types/read_receipts";
|
import { ReceiptType } from "./@types/read_receipts";
|
||||||
@@ -453,20 +452,6 @@ export function simpleRetryOperation<T>(promiseFn: (attempt: number) => Promise<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to be able to access the Node.js crypto library from within the
|
|
||||||
// Matrix SDK without needing to `require("crypto")`, which will fail in
|
|
||||||
// browsers. So `index.ts` will call `setCrypto` to store it, and when we need
|
|
||||||
// it, we can call `getCrypto`.
|
|
||||||
let crypto: typeof NodeCrypto;
|
|
||||||
|
|
||||||
export function setCrypto(c: typeof NodeCrypto) {
|
|
||||||
crypto = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCrypto(): typeof NodeCrypto {
|
|
||||||
return crypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// String averaging inspired by https://stackoverflow.com/a/2510816
|
// String averaging inspired by https://stackoverflow.com/a/2510816
|
||||||
// Dev note: We make the alphabet a string because it's easier to write syntactically
|
// Dev note: We make the alphabet a string because it's easier to write syntactically
|
||||||
// than arrays. Thankfully, strings implement the useful parts of the Array interface
|
// than arrays. Thankfully, strings implement the useful parts of the Array interface
|
||||||
|
Reference in New Issue
Block a user