You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Move out crypto/aes (#4431)
* Move `SecretEncryptedPayload` in `src/utils/@types` * Move `encryptAES` to a dedicated file. Moved in a utils folder. * Move `deriveKeys` to a dedicated file in order to share it * Move `decryptAES` to a dedicated file. Moved in a utils folder. * Move `calculateKeyCheck` to a dedicated file. Moved in a utils folder. * Remove AES functions in `aes.ts` and export new ones for backward compatibility * Update import to use new functions * Add `src/utils` entrypoint in `README.md` * - Rename `SecretEncryptedPayload` to `AESEncryptedSecretStoragePayload`. - Move into `src/@types` * Move `calculateKeyCheck` into `secret-storage.ts`. * Move `deriveKeys` into `src/utils/internal` folder. * - Rename `encryptAES` on `encryptAESSecretStorageItem` - Change named export by default export * - Rename `decryptAES` on `decryptAESSecretStorageItem` - Change named export by default export * Update documentation * Update `decryptAESSecretStorageItem` doc * Add lnk to spec for `calculateKeyCheck` * Fix downstream tests
This commit is contained in:
@@ -191,6 +191,7 @@ As well as the primary entry point (`matrix-js-sdk`), there are several other en
|
||||
| `matrix-js-sdk/lib/crypto-api` | Cryptography functionality. |
|
||||
| `matrix-js-sdk/lib/types` | Low-level types, reflecting data structures defined in the Matrix spec. |
|
||||
| `matrix-js-sdk/lib/testing` | Test utilities, which may be useful in test code but should not be used in production code. |
|
||||
| `matrix-js-sdk/lib/utils/*.js` | A set of modules exporting standalone functions (and their types). |
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IDBFactory } from "fake-indexeddb";
|
||||
import { CRYPTO_BACKENDS, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
||||
import { AuthDict, createClient, CryptoEvent, MatrixClient } from "../../../src";
|
||||
import { mockInitialApiRequests, mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
|
||||
import { CryptoCallbacks, CrossSigningKey } from "../../../src/crypto-api";
|
||||
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
||||
import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
|
||||
@@ -169,17 +169,17 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
|
||||
mockInitialApiRequests(aliceClient.getHomeserverUrl());
|
||||
|
||||
// Encrypt the private keys and return them in the /sync response as if they are in Secret Storage
|
||||
const masterKey = await encryptAES(
|
||||
const masterKey = await encryptAESSecretStorageItem(
|
||||
MASTER_CROSS_SIGNING_PRIVATE_KEY_BASE64,
|
||||
encryptionKey,
|
||||
"m.cross_signing.master",
|
||||
);
|
||||
const selfSigningKey = await encryptAES(
|
||||
const selfSigningKey = await encryptAESSecretStorageItem(
|
||||
SELF_CROSS_SIGNING_PRIVATE_KEY_BASE64,
|
||||
encryptionKey,
|
||||
"m.cross_signing.self_signing",
|
||||
);
|
||||
const userSigningKey = await encryptAES(
|
||||
const userSigningKey = await encryptAESSecretStorageItem(
|
||||
USER_CROSS_SIGNING_PRIVATE_KEY_BASE64,
|
||||
encryptionKey,
|
||||
"m.cross_signing.user_signing",
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IObject } from "../../../src/crypto/olmlib";
|
||||
import { MatrixEvent } from "../../../src/models/event";
|
||||
import { TestClient } from "../../TestClient";
|
||||
import { makeTestClients } from "./verification/util";
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
|
||||
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
|
||||
import { logger } from "../../../src/logger";
|
||||
import { ClientEvent, ICreateClientOpts, MatrixClient } from "../../../src/client";
|
||||
@@ -612,7 +612,7 @@ describe("Secrets", function () {
|
||||
type: "m.megolm_backup.v1",
|
||||
content: {
|
||||
encrypted: {
|
||||
key_id: await encryptAES(
|
||||
key_id: await encryptAESSecretStorageItem(
|
||||
"123,45,6,7,89,1,234,56,78,90,12,34,5,67,8,90",
|
||||
secretStorageKeys.key_id,
|
||||
"m.megolm_backup.v1",
|
||||
|
||||
@@ -69,7 +69,7 @@ import { logger } from "../../../src/logger";
|
||||
import { OutgoingRequestsManager } from "../../../src/rust-crypto/OutgoingRequestsManager";
|
||||
import { ClientEvent, ClientEventHandlerMap } from "../../../src/client";
|
||||
import { Curve25519AuthData } from "../../../src/crypto-api/keybackup";
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
|
||||
import { CryptoStore, SecretStorePrivateKeys } from "../../../src/crypto/store/base";
|
||||
|
||||
const TEST_USER = "@alice:example.com";
|
||||
@@ -425,7 +425,7 @@ describe("initRustCrypto", () => {
|
||||
}, 10000);
|
||||
|
||||
async function encryptAndStoreSecretKey(type: string, key: Uint8Array, pickleKey: string, store: CryptoStore) {
|
||||
const encryptedKey = await encryptAES(encodeBase64(key), Buffer.from(pickleKey), type);
|
||||
const encryptedKey = await encryptAESSecretStorageItem(encodeBase64(key), Buffer.from(pickleKey), type);
|
||||
store.storeSecretStorePrivateKey(undefined, type as keyof SecretStorePrivateKeys, encryptedKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
ServerSideSecretStorageImpl,
|
||||
trimTrailingEquals,
|
||||
} from "../../src/secret-storage";
|
||||
import { calculateKeyCheck } from "../../src/crypto/aes";
|
||||
import { randomString } from "../../src/randomstring";
|
||||
import { calculateKeyCheck } from "../../src/calculateKeyCheck.ts";
|
||||
|
||||
describe("ServerSideSecretStorageImpl", function () {
|
||||
describe(".addKey", function () {
|
||||
|
||||
29
src/@types/AESEncryptedSecretStoragePayload.ts
Normal file
29
src/@types/AESEncryptedSecretStoragePayload.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An AES-encrypted secret storage payload.
|
||||
* See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2-1
|
||||
*/
|
||||
export interface AESEncryptedSecretStoragePayload {
|
||||
[key: string]: any; // extensible
|
||||
/** the initialization vector in base64 */
|
||||
iv: string;
|
||||
/** the ciphertext in base64 */
|
||||
ciphertext: string;
|
||||
/** the HMAC in base64 */
|
||||
mac: string;
|
||||
}
|
||||
34
src/calculateKeyCheck.ts
Normal file
34
src/calculateKeyCheck.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// string of zeroes, for calculating the key check
|
||||
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "./@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
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";
|
||||
|
||||
/**
|
||||
* Calculate the MAC for checking the key.
|
||||
* See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2, steps 3 and 4.
|
||||
*
|
||||
* @param key - the key to use
|
||||
* @param iv - The initialization vector as a base64-encoded string.
|
||||
* If omitted, a random initialization vector will be created.
|
||||
* @returns An object that contains, `mac` and `iv` properties.
|
||||
*/
|
||||
export function calculateKeyCheck(key: Uint8Array, iv?: string): Promise<AESEncryptedSecretStoragePayload> {
|
||||
return encryptAESSecretStorageItem(ZERO_STR, key, "", iv);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { ISigned } from "../@types/signed.ts";
|
||||
import { IEncryptedPayload } from "../crypto/aes.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
export interface Curve25519AuthData {
|
||||
public_key: string;
|
||||
@@ -77,7 +77,7 @@ export interface Curve25519SessionData {
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface KeyBackupSession<T = Curve25519SessionData | IEncryptedPayload> {
|
||||
export interface KeyBackupSession<T = Curve25519SessionData | AESEncryptedSecretStoragePayload> {
|
||||
first_message_index: number;
|
||||
forwarded_count: number;
|
||||
is_verified: boolean;
|
||||
|
||||
@@ -22,7 +22,6 @@ import type { PkSigning } from "@matrix-org/olm";
|
||||
import { IObject, pkSign, pkVerify } from "./olmlib.ts";
|
||||
import { logger } from "../logger.ts";
|
||||
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store.ts";
|
||||
import { decryptAES, encryptAES } from "./aes.ts";
|
||||
import { DeviceInfo } from "./deviceinfo.ts";
|
||||
import { ISignedKey, MatrixClient } from "../client.ts";
|
||||
import { OlmDevice } from "./OlmDevice.ts";
|
||||
@@ -36,6 +35,8 @@ import {
|
||||
UserVerificationStatus as UserTrustLevel,
|
||||
} from "../crypto-api/index.ts";
|
||||
import { decodeBase64, encodeBase64 } from "../base64.ts";
|
||||
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
|
||||
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
|
||||
|
||||
// backwards-compatibility re-exports
|
||||
export { UserTrustLevel };
|
||||
@@ -662,7 +663,7 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
|
||||
|
||||
if (key && key.ciphertext) {
|
||||
const pickleKey = Buffer.from(olmDevice.pickleKey);
|
||||
const decrypted = await decryptAES(key, pickleKey, type);
|
||||
const decrypted = await decryptAESSecretStorageItem(key, pickleKey, type);
|
||||
return decodeBase64(decrypted);
|
||||
} else {
|
||||
return key;
|
||||
@@ -676,7 +677,7 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
|
||||
throw new Error(`storeCrossSigningKeyCache expects Uint8Array, got ${key}`);
|
||||
}
|
||||
const pickleKey = Buffer.from(olmDevice.pickleKey);
|
||||
const encryptedKey = await encryptAES(encodeBase64(key), pickleKey, type);
|
||||
const encryptedKey = await encryptAESSecretStorageItem(encodeBase64(key), pickleKey, type);
|
||||
return store.doTxn("readwrite", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
store.storeSecretStorePrivateKey(txn, type, encryptedKey);
|
||||
});
|
||||
|
||||
@@ -14,153 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { decodeBase64, encodeBase64 } from "../base64.ts";
|
||||
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
|
||||
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
// salt for HKDF, with 8 bytes of zeros
|
||||
const zeroSalt = new Uint8Array(8);
|
||||
|
||||
export interface IEncryptedPayload {
|
||||
[key: string]: any; // extensible
|
||||
/** the initialization vector in base64 */
|
||||
iv: string;
|
||||
/** the ciphertext in base64 */
|
||||
ciphertext: string;
|
||||
/** the HMAC in base64 */
|
||||
mac: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string using AES-CTR.
|
||||
*
|
||||
* @param data - the plaintext to encrypt
|
||||
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key for
|
||||
* encryption. Obviously, the same key must be provided when decrypting.
|
||||
* @param name - the name of the secret. Used as an input to the HKDF operation which is used to derive the AES key,
|
||||
* so again the same value must be provided when decrypting.
|
||||
* @param ivStr - the base64-encoded initialization vector to use. If not supplied, a random one will be generated.
|
||||
*
|
||||
* @returns The encrypted result, including the ciphertext itself, the initialization vector (as supplied in `ivStr`,
|
||||
* or generated), and an HMAC on the ciphertext — all base64-encoded.
|
||||
*/
|
||||
export async function encryptAES(
|
||||
data: string,
|
||||
key: Uint8Array,
|
||||
name: string,
|
||||
ivStr?: string,
|
||||
): Promise<IEncryptedPayload> {
|
||||
let iv: Uint8Array;
|
||||
if (ivStr) {
|
||||
iv = decodeBase64(ivStr);
|
||||
} else {
|
||||
iv = new Uint8Array(16);
|
||||
globalThis.crypto.getRandomValues(iv);
|
||||
|
||||
// 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] = await deriveKeys(key, name);
|
||||
const encodedData = new TextEncoder().encode(data);
|
||||
|
||||
const ciphertext = await globalThis.crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-CTR",
|
||||
counter: iv,
|
||||
length: 64,
|
||||
},
|
||||
aesKey,
|
||||
encodedData,
|
||||
);
|
||||
|
||||
const hmac = await globalThis.crypto.subtle.sign({ name: "HMAC" }, hmacKey, ciphertext);
|
||||
|
||||
return {
|
||||
iv: encodeBase64(iv),
|
||||
ciphertext: encodeBase64(ciphertext),
|
||||
mac: encodeBase64(hmac),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an AES-encrypted string.
|
||||
*
|
||||
* @param data - the encrypted data, returned by {@link encryptAES}.
|
||||
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key. Must
|
||||
* be the same as provided to {@link encryptAES}.
|
||||
* @param name - the name of the secret. Also used as an input to the HKDF operation which is used to derive the AES
|
||||
* key, so again must be the same as provided to {@link encryptAES}.
|
||||
*/
|
||||
export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
||||
const [aesKey, hmacKey] = await deriveKeys(key, name);
|
||||
|
||||
const ciphertext = decodeBase64(data.ciphertext);
|
||||
|
||||
if (!(await globalThis.crypto.subtle.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
|
||||
throw new Error(`Error decrypting secret ${name}: bad MAC`);
|
||||
}
|
||||
|
||||
const plaintext = await globalThis.crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-CTR",
|
||||
counter: decodeBase64(data.iv),
|
||||
length: 64,
|
||||
},
|
||||
aesKey,
|
||||
ciphertext,
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(new Uint8Array(plaintext));
|
||||
}
|
||||
|
||||
async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
|
||||
const hkdfkey = await globalThis.crypto.subtle.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
|
||||
const keybits = await globalThis.crypto.subtle.deriveBits(
|
||||
{
|
||||
name: "HKDF",
|
||||
salt: zeroSalt,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
|
||||
info: new TextEncoder().encode(name),
|
||||
hash: "SHA-256",
|
||||
},
|
||||
hkdfkey,
|
||||
512,
|
||||
);
|
||||
|
||||
const aesKey = keybits.slice(0, 32);
|
||||
const hmacKey = keybits.slice(32);
|
||||
|
||||
const aesProm = globalThis.crypto.subtle.importKey("raw", aesKey, { name: "AES-CTR" }, false, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
|
||||
const hmacProm = globalThis.crypto.subtle.importKey(
|
||||
"raw",
|
||||
hmacKey,
|
||||
{
|
||||
name: "HMAC",
|
||||
hash: { name: "SHA-256" },
|
||||
},
|
||||
false,
|
||||
["sign", "verify"],
|
||||
);
|
||||
|
||||
return Promise.all([aesProm, hmacProm]);
|
||||
}
|
||||
|
||||
// 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";
|
||||
|
||||
/** Calculate the MAC for checking the key.
|
||||
*
|
||||
* @param key - the key to use
|
||||
* @param iv - The initialization vector as a base64-encoded string.
|
||||
* If omitted, a random initialization vector will be created.
|
||||
* @returns An object that contains, `mac` and `iv` properties.
|
||||
*/
|
||||
export function calculateKeyCheck(key: Uint8Array, iv?: string): Promise<IEncryptedPayload> {
|
||||
return encryptAES(ZERO_STR, key, "", iv);
|
||||
}
|
||||
// Export for backwards compatibility
|
||||
export type { AESEncryptedSecretStoragePayload as IEncryptedPayload };
|
||||
// Export with new names instead of using `as` to not break react-sdk tests
|
||||
export const encryptAES = encryptAESSecretStorageItem;
|
||||
export const decryptAES = decryptAESSecretStorageItem;
|
||||
export { calculateKeyCheck } from "../calculateKeyCheck.ts";
|
||||
|
||||
@@ -27,7 +27,6 @@ import { DeviceTrustLevel } from "./CrossSigning.ts";
|
||||
import { keyFromPassphrase } from "./key_passphrase.ts";
|
||||
import { encodeUri, safeSet, sleep } from "../utils.ts";
|
||||
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
|
||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts";
|
||||
import {
|
||||
Curve25519SessionData,
|
||||
IAes256AuthData,
|
||||
@@ -41,6 +40,10 @@ import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.
|
||||
import { BackupTrustInfo } from "../crypto-api/keybackup.ts";
|
||||
import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
|
||||
import { encodeRecoveryKey } from "../crypto-api/index.ts";
|
||||
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
|
||||
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
import { calculateKeyCheck } from "../calculateKeyCheck.ts";
|
||||
|
||||
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
||||
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
|
||||
@@ -94,7 +97,7 @@ interface BackupAlgorithmClass {
|
||||
|
||||
interface BackupAlgorithm {
|
||||
untrusted: boolean;
|
||||
encryptSession(data: Record<string, any>): Promise<Curve25519SessionData | IEncryptedPayload>;
|
||||
encryptSession(data: Record<string, any>): Promise<Curve25519SessionData | AESEncryptedSecretStoragePayload>;
|
||||
decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>;
|
||||
authData: AuthData;
|
||||
keyMatches(key: ArrayLike<number>): Promise<boolean>;
|
||||
@@ -825,22 +828,24 @@ export class Aes256 implements BackupAlgorithm {
|
||||
return false;
|
||||
}
|
||||
|
||||
public encryptSession(data: Record<string, any>): Promise<IEncryptedPayload> {
|
||||
public encryptSession(data: Record<string, any>): Promise<AESEncryptedSecretStoragePayload> {
|
||||
const plainText: Record<string, any> = Object.assign({}, data);
|
||||
delete plainText.session_id;
|
||||
delete plainText.room_id;
|
||||
delete plainText.first_known_index;
|
||||
return encryptAES(JSON.stringify(plainText), this.key, data.session_id);
|
||||
return encryptAESSecretStorageItem(JSON.stringify(plainText), this.key, data.session_id);
|
||||
}
|
||||
|
||||
public async decryptSessions(
|
||||
sessions: Record<string, IKeyBackupSession<IEncryptedPayload>>,
|
||||
sessions: Record<string, IKeyBackupSession<AESEncryptedSecretStoragePayload>>,
|
||||
): Promise<IMegolmSessionData[]> {
|
||||
const keys: IMegolmSessionData[] = [];
|
||||
|
||||
for (const [sessionId, sessionData] of Object.entries(sessions)) {
|
||||
try {
|
||||
const decrypted = JSON.parse(await decryptAES(sessionData.session_data, this.key, sessionId));
|
||||
const decrypted = JSON.parse(
|
||||
await decryptAESSecretStorageItem(sessionData.session_data, this.key, sessionId),
|
||||
);
|
||||
decrypted.session_id = sessionId;
|
||||
keys.push(decrypted);
|
||||
} catch (e) {
|
||||
|
||||
@@ -19,11 +19,12 @@ import anotherjson from "another-json";
|
||||
import type { IDeviceKeys, IOneTimeKey } from "../@types/crypto.ts";
|
||||
import { decodeBase64, encodeBase64 } from "../base64.ts";
|
||||
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store.ts";
|
||||
import { decryptAES, encryptAES } from "./aes.ts";
|
||||
import { logger } from "../logger.ts";
|
||||
import { Crypto } from "./index.ts";
|
||||
import { Method } from "../http-api/index.ts";
|
||||
import { SecretStorageKeyDescription } from "../secret-storage.ts";
|
||||
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
|
||||
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
|
||||
|
||||
export interface IDehydratedDevice {
|
||||
device_id: string; // eslint-disable-line camelcase
|
||||
@@ -61,7 +62,7 @@ export class DehydrationManager {
|
||||
if (result) {
|
||||
const { key, keyInfo, deviceDisplayName, time } = result;
|
||||
const pickleKey = Buffer.from(this.crypto.olmDevice.pickleKey);
|
||||
const decrypted = await decryptAES(key, pickleKey, DEHYDRATION_ALGORITHM);
|
||||
const decrypted = await decryptAESSecretStorageItem(key, pickleKey, DEHYDRATION_ALGORITHM);
|
||||
this.key = decodeBase64(decrypted);
|
||||
this.keyInfo = keyInfo;
|
||||
this.deviceDisplayName = deviceDisplayName;
|
||||
@@ -141,7 +142,7 @@ export class DehydrationManager {
|
||||
const pickleKey = Buffer.from(this.crypto.olmDevice.pickleKey);
|
||||
|
||||
// update the crypto store with the timestamp
|
||||
const key = await encryptAES(encodeBase64(this.key!), pickleKey, DEHYDRATION_ALGORITHM);
|
||||
const key = await encryptAESSecretStorageItem(encodeBase64(this.key!), pickleKey, DEHYDRATION_ALGORITHM);
|
||||
await this.crypto.cryptoStore.doTxn("readwrite", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
this.crypto.cryptoStore.storeSecretStorePrivateKey(txn, "dehydration", {
|
||||
keyInfo: this.keyInfo,
|
||||
|
||||
@@ -47,7 +47,6 @@ import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChan
|
||||
import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel.ts";
|
||||
import { IllegalMethod } from "./verification/IllegalMethod.ts";
|
||||
import { KeySignatureUploadError } from "../errors.ts";
|
||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts";
|
||||
import { DehydrationManager } from "./dehydration.ts";
|
||||
import { BackupManager, LibOlmBackupDecryptor, backupTrustInfoFromLegacyTrustInfo } from "./backup.ts";
|
||||
import { IStore } from "../store/index.ts";
|
||||
@@ -107,6 +106,10 @@ import { deviceInfoToDevice } from "./device-converter.ts";
|
||||
import { ClientPrefix, MatrixError, Method } from "../http-api/index.ts";
|
||||
import { decodeBase64, encodeBase64 } from "../base64.ts";
|
||||
import { KnownMembership } from "../@types/membership.ts";
|
||||
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
|
||||
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
import { calculateKeyCheck } from "../calculateKeyCheck.ts";
|
||||
|
||||
/* re-exports for backwards compatibility */
|
||||
export type {
|
||||
@@ -1322,11 +1325,13 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @returns the key, if any, or null
|
||||
*/
|
||||
public async getSessionBackupPrivateKey(): Promise<Uint8Array | null> {
|
||||
const encodedKey = await new Promise<Uint8Array | IEncryptedPayload | string | null>((resolve) => {
|
||||
const encodedKey = await new Promise<Uint8Array | AESEncryptedSecretStoragePayload | string | null>(
|
||||
(resolve) => {
|
||||
this.cryptoStore.doTxn("readonly", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
this.cryptoStore.getSecretStorePrivateKey(txn, resolve, "m.megolm_backup.v1");
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let key: Uint8Array | null = null;
|
||||
|
||||
@@ -1337,7 +1342,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
if (encodedKey && typeof encodedKey === "object" && "ciphertext" in encodedKey) {
|
||||
const pickleKey = Buffer.from(this.olmDevice.pickleKey);
|
||||
const decrypted = await decryptAES(encodedKey, pickleKey, "m.megolm_backup.v1");
|
||||
const decrypted = await decryptAESSecretStorageItem(encodedKey, pickleKey, "m.megolm_backup.v1");
|
||||
key = decodeBase64(decrypted);
|
||||
}
|
||||
return key;
|
||||
@@ -1354,7 +1359,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`);
|
||||
}
|
||||
const pickleKey = Buffer.from(this.olmDevice.pickleKey);
|
||||
const encryptedKey = await encryptAES(encodeBase64(key), pickleKey, "m.megolm_backup.v1");
|
||||
const encryptedKey = await encryptAESSecretStorageItem(encodeBase64(key), pickleKey, "m.megolm_backup.v1");
|
||||
return this.cryptoStore.doTxn("readwrite", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", encryptedKey);
|
||||
});
|
||||
|
||||
@@ -25,8 +25,8 @@ import { Logger } from "../../logger.ts";
|
||||
import { InboundGroupSessionData } from "../OlmDevice.ts";
|
||||
import { MatrixEvent } from "../../models/event.ts";
|
||||
import { DehydrationManager } from "../dehydration.ts";
|
||||
import { IEncryptedPayload } from "../aes.ts";
|
||||
import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
/**
|
||||
* Internal module. Definitions for storage for the crypto module
|
||||
@@ -35,11 +35,11 @@ import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
|
||||
export interface SecretStorePrivateKeys {
|
||||
"dehydration": {
|
||||
keyInfo: DehydrationManager["keyInfo"];
|
||||
key: IEncryptedPayload;
|
||||
key: AESEncryptedSecretStoragePayload;
|
||||
deviceDisplayName: string;
|
||||
time: number;
|
||||
} | null;
|
||||
"m.megolm_backup.v1": IEncryptedPayload;
|
||||
"m.megolm_backup.v1": AESEncryptedSecretStoragePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,10 +33,10 @@ import { encodeUri, logDuration } from "../utils.ts";
|
||||
import { OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
|
||||
import { sleep } from "../utils.ts";
|
||||
import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
|
||||
import { IEncryptedPayload } from "../crypto/aes.ts";
|
||||
import { ImportRoomKeyProgressData, ImportRoomKeysOpts } from "../crypto-api/index.ts";
|
||||
import { IKeyBackupInfo } from "../crypto/keybackup.ts";
|
||||
import { IKeyBackup } from "../crypto/backup.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
/** Authentification of the backup info, depends on algorithm */
|
||||
type AuthData = KeyBackupInfo["auth_data"];
|
||||
@@ -622,7 +622,7 @@ export class RustBackupDecryptor implements BackupDecryptor {
|
||||
* Implements {@link BackupDecryptor#decryptSessions}
|
||||
*/
|
||||
public async decryptSessions(
|
||||
ciphertexts: Record<string, KeyBackupSession<Curve25519SessionData | IEncryptedPayload>>,
|
||||
ciphertexts: Record<string, KeyBackupSession<Curve25519SessionData | AESEncryptedSecretStoragePayload>>,
|
||||
): Promise<IMegolmSessionData[]> {
|
||||
const keys: IMegolmSessionData[] = [];
|
||||
for (const [sessionId, sessionData] of Object.entries(ciphertexts)) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { Logger } from "../logger.ts";
|
||||
import { CryptoStore, MigrationState, SecretStorePrivateKeys } from "../crypto/store/base.ts";
|
||||
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store.ts";
|
||||
import { decryptAES, IEncryptedPayload } from "../crypto/aes.ts";
|
||||
import { IHttpOpts, MatrixHttpApi } from "../http-api/index.ts";
|
||||
import { requestKeyBackupVersion } from "./backup.ts";
|
||||
import { IRoomEncryption } from "../crypto/RoomList.ts";
|
||||
@@ -28,6 +27,8 @@ import { RustCrypto } from "./rust-crypto.ts";
|
||||
import { KeyBackupInfo } from "../crypto-api/keybackup.ts";
|
||||
import { sleep } from "../utils.ts";
|
||||
import { encodeBase64 } from "../base64.ts";
|
||||
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
/**
|
||||
* Determine if any data needs migrating from the legacy store, and do so.
|
||||
@@ -421,7 +422,7 @@ async function getAndDecryptCachedSecretKey(
|
||||
});
|
||||
|
||||
if (key && key.ciphertext && key.iv && key.mac) {
|
||||
return await decryptAES(key as IEncryptedPayload, legacyPickleKey, name);
|
||||
return await decryptAESSecretStorageItem(key as AESEncryptedSecretStoragePayload, legacyPickleKey, name);
|
||||
} else if (key instanceof Uint8Array) {
|
||||
// This is a legacy backward compatibility case where the key was stored in clear.
|
||||
return encodeBase64(key);
|
||||
|
||||
@@ -23,9 +23,12 @@ limitations under the License.
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter.ts";
|
||||
import { ClientEvent, ClientEventHandlerMap } from "./client.ts";
|
||||
import { MatrixEvent } from "./models/event.ts";
|
||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./crypto/aes.ts";
|
||||
import { randomString } from "./randomstring.ts";
|
||||
import { logger } from "./logger.ts";
|
||||
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts";
|
||||
import decryptAESSecretStorageItem from "./utils/decryptAESSecretStorageItem.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "./@types/AESEncryptedSecretStoragePayload.ts";
|
||||
import { calculateKeyCheck } from "./crypto/aes.ts";
|
||||
|
||||
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
|
||||
|
||||
@@ -200,13 +203,13 @@ export interface SecretStorageCallbacks {
|
||||
|
||||
interface SecretInfo {
|
||||
encrypted: {
|
||||
[keyId: string]: IEncryptedPayload;
|
||||
[keyId: string]: AESEncryptedSecretStoragePayload;
|
||||
};
|
||||
}
|
||||
|
||||
interface Decryptors {
|
||||
encrypt: (plaintext: string) => Promise<IEncryptedPayload>;
|
||||
decrypt: (ciphertext: IEncryptedPayload) => Promise<string>;
|
||||
encrypt: (plaintext: string) => Promise<AESEncryptedSecretStoragePayload>;
|
||||
decrypt: (ciphertext: AESEncryptedSecretStoragePayload) => Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,7 +494,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
||||
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key.
|
||||
*/
|
||||
public async store(name: string, secret: string, keys?: string[] | null): Promise<void> {
|
||||
const encrypted: Record<string, IEncryptedPayload> = {};
|
||||
const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};
|
||||
|
||||
if (!keys) {
|
||||
const defaultKeyId = await this.getDefaultKeyId();
|
||||
@@ -638,11 +641,11 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
||||
|
||||
if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||
const decryption = {
|
||||
encrypt: function (secret: string): Promise<IEncryptedPayload> {
|
||||
return encryptAES(secret, privateKey, name);
|
||||
encrypt: function (secret: string): Promise<AESEncryptedSecretStoragePayload> {
|
||||
return encryptAESSecretStorageItem(secret, privateKey, name);
|
||||
},
|
||||
decrypt: function (encInfo: IEncryptedPayload): Promise<string> {
|
||||
return decryptAES(encInfo, privateKey, name);
|
||||
decrypt: function (encInfo: AESEncryptedSecretStoragePayload): Promise<string> {
|
||||
return decryptAESSecretStorageItem(encInfo, privateKey, name);
|
||||
},
|
||||
};
|
||||
return [keyId, decryption];
|
||||
|
||||
@@ -26,6 +26,7 @@ export * from "./@types/membership.ts";
|
||||
export type * from "./@types/event.ts";
|
||||
export type * from "./@types/events.ts";
|
||||
export type * from "./@types/state_events.ts";
|
||||
export type * from "./@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
/** The different methods for device and user verification */
|
||||
export enum VerificationMethod {
|
||||
|
||||
54
src/utils/decryptAESSecretStorageItem.ts
Normal file
54
src/utils/decryptAESSecretStorageItem.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
import { decodeBase64 } from "../base64.ts";
|
||||
import { deriveKeys } from "./internal/deriveKeys.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
/**
|
||||
* Decrypt an AES-encrypted Secret Storage item.
|
||||
*
|
||||
* @param data - the encrypted data, returned by {@link utils/encryptAESSecretStorageItem.default | encryptAESSecretStorageItem}.
|
||||
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key. Must
|
||||
* be the same as provided to {@link utils/encryptAESSecretStorageItem.default | encryptAESSecretStorageItem}.
|
||||
* @param name - the name of the secret. Also used as an input to the HKDF operation which is used to derive the AES
|
||||
* key, so again must be the same as provided to {@link utils/encryptAESSecretStorageItem.default | encryptAESSecretStorageItem}.
|
||||
*/
|
||||
export default async function decryptAESSecretStorageItem(
|
||||
data: AESEncryptedSecretStoragePayload,
|
||||
key: Uint8Array,
|
||||
name: string,
|
||||
): Promise<string> {
|
||||
const [aesKey, hmacKey] = await deriveKeys(key, name);
|
||||
|
||||
const ciphertext = decodeBase64(data.ciphertext);
|
||||
|
||||
if (!(await globalThis.crypto.subtle.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
|
||||
throw new Error(`Error decrypting secret ${name}: bad MAC`);
|
||||
}
|
||||
|
||||
const plaintext = await globalThis.crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-CTR",
|
||||
counter: decodeBase64(data.iv),
|
||||
length: 64,
|
||||
},
|
||||
aesKey,
|
||||
ciphertext,
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(new Uint8Array(plaintext));
|
||||
}
|
||||
73
src/utils/encryptAESSecretStorageItem.ts
Normal file
73
src/utils/encryptAESSecretStorageItem.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
import { decodeBase64, encodeBase64 } from "../base64.ts";
|
||||
import { deriveKeys } from "./internal/deriveKeys.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
/**
|
||||
* Encrypt a string as a secret storage item, using AES-CTR.
|
||||
*
|
||||
* @param data - the plaintext to encrypt
|
||||
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key for
|
||||
* encryption. Obviously, the same key must be provided when decrypting.
|
||||
* @param name - the name of the secret. Used as an input to the HKDF operation which is used to derive the AES key,
|
||||
* so again the same value must be provided when decrypting.
|
||||
* @param ivStr - the base64-encoded initialization vector to use. If not supplied, a random one will be generated.
|
||||
*
|
||||
* @returns The encrypted result, including the ciphertext itself, the initialization vector (as supplied in `ivStr`,
|
||||
* or generated), and an HMAC on the ciphertext — all base64-encoded.
|
||||
*/
|
||||
export default async function encryptAESSecretStorageItem(
|
||||
data: string,
|
||||
key: Uint8Array,
|
||||
name: string,
|
||||
ivStr?: string,
|
||||
): Promise<AESEncryptedSecretStoragePayload> {
|
||||
let iv: Uint8Array;
|
||||
if (ivStr) {
|
||||
iv = decodeBase64(ivStr);
|
||||
} else {
|
||||
iv = new Uint8Array(16);
|
||||
globalThis.crypto.getRandomValues(iv);
|
||||
|
||||
// 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] = await deriveKeys(key, name);
|
||||
const encodedData = new TextEncoder().encode(data);
|
||||
|
||||
const ciphertext = await globalThis.crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-CTR",
|
||||
counter: iv,
|
||||
length: 64,
|
||||
},
|
||||
aesKey,
|
||||
encodedData,
|
||||
);
|
||||
|
||||
const hmac = await globalThis.crypto.subtle.sign({ name: "HMAC" }, hmacKey, ciphertext);
|
||||
|
||||
return {
|
||||
iv: encodeBase64(iv),
|
||||
ciphertext: encodeBase64(ciphertext),
|
||||
mac: encodeBase64(hmac),
|
||||
};
|
||||
}
|
||||
63
src/utils/internal/deriveKeys.ts
Normal file
63
src/utils/internal/deriveKeys.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// salt for HKDF, with 8 bytes of zeros
|
||||
const zeroSalt = new Uint8Array(8);
|
||||
|
||||
/**
|
||||
* Derive AES and HMAC keys from a master key.
|
||||
*
|
||||
* This is used for deriving secret storage keys: see https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2 (step 1).
|
||||
*
|
||||
* @param key
|
||||
* @param name
|
||||
*/
|
||||
export async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
|
||||
const hkdfkey = await globalThis.crypto.subtle.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
|
||||
const keybits = await globalThis.crypto.subtle.deriveBits(
|
||||
{
|
||||
name: "HKDF",
|
||||
salt: zeroSalt,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
|
||||
info: new TextEncoder().encode(name),
|
||||
hash: "SHA-256",
|
||||
},
|
||||
hkdfkey,
|
||||
512,
|
||||
);
|
||||
|
||||
const aesKey = keybits.slice(0, 32);
|
||||
const hmacKey = keybits.slice(32);
|
||||
|
||||
const aesProm = globalThis.crypto.subtle.importKey("raw", aesKey, { name: "AES-CTR" }, false, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
|
||||
const hmacProm = globalThis.crypto.subtle.importKey(
|
||||
"raw",
|
||||
hmacKey,
|
||||
{
|
||||
name: "HMAC",
|
||||
hash: { name: "SHA-256" },
|
||||
},
|
||||
false,
|
||||
["sign", "verify"],
|
||||
);
|
||||
|
||||
return Promise.all([aesProm, hmacProm]);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"plugin": ["typedoc-plugin-mdn-links", "typedoc-plugin-missing-exports", "typedoc-plugin-coverage"],
|
||||
"coverageLabel": "TypeDoc",
|
||||
"entryPoints": ["src/matrix.ts", "src/types.ts", "src/testing.ts"],
|
||||
"entryPoints": ["src/matrix.ts", "src/types.ts", "src/testing.ts", "src/utils/*.ts"],
|
||||
"excludeExternals": true,
|
||||
"out": "_docs"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user