Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"@types/content-type": "^1.1.5",
"@types/debug": "^4.1.7",
"@types/jest": "^29.0.0",
"@types/node": "18",
"@types/node": "^22",
"@types/sdp-transform": "^2.4.5",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
Expand Down
2 changes: 1 addition & 1 deletion spec/integ/crypto/cross-signing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe("cross-signing", () => {
function createCryptoCallbacks(): CryptoCallbacks {
return {
getSecretStorageKey: (keys, name) => {
return Promise.resolve<[string, Uint8Array]>(["key_id", encryptionKey]);
return Promise.resolve<[string, Uint8Array<ArrayBuffer>]>(["key_id", encryptionKey]);
},
};
}
Expand Down
11 changes: 8 additions & 3 deletions spec/integ/crypto/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,15 +321,20 @@ describe("crypto", () => {
*/
function createCryptoCallbacks(): CryptoCallbacks {
// Store the cached secret storage key and return it when `getSecretStorageKey` is called
let cachedKey: { keyId: string; key: Uint8Array };
const cacheSecretStorageKey = (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => {
let cachedKey: { keyId: string; key: Uint8Array<ArrayBuffer> };
const cacheSecretStorageKey = (
keyId: string,
keyInfo: SecretStorageKeyDescription,
key: Uint8Array<ArrayBuffer>,
) => {
cachedKey = {
keyId,
key,
};
};

const getSecretStorageKey = () => Promise.resolve<[string, Uint8Array]>([cachedKey.keyId, cachedKey.key]);
const getSecretStorageKey = () =>
Promise.resolve<[string, Uint8Array<ArrayBuffer>]>([cachedKey.keyId, cachedKey.key]);

return {
cacheSecretStorageKey,
Expand Down
3 changes: 2 additions & 1 deletion spec/unit/oidc/authorize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ describe("oidc authorization", () => {
jest.setSystemTime(now);

fetchMock.get(delegatedAuthConfig.issuer + ".well-known/openid-configuration", mockOpenIdConfiguration());
globalThis.TextEncoder = TextEncoder;
// XXX: jsdom lacks a TextEncoder and Node's implementation has marginally different types, so we fudge it
(globalThis as any).TextEncoder = TextEncoder;
});

beforeEach(() => {
Expand Down
20 changes: 0 additions & 20 deletions src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,4 @@ declare global {
// on webkit: we should check if we still need to do this
webkitGetUserMedia?: unknown;
}

export interface Uint8ArrayToBase64Options {
alphabet?: "base64" | "base64url";
omitPadding?: boolean;
}

interface Uint8Array {
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
toBase64?(options?: Uint8ArrayToBase64Options): string;
}

export interface Uint8ArrayFromBase64Options {
alphabet?: "base64"; // Our fallback code only handles base64.
lastChunkHandling?: "loose"; // Our fallback code doesn't support other handling at this time.
}

interface Uint8ArrayConstructor {
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
fromBase64?(base64: string, options?: Uint8ArrayFromBase64Options): Uint8Array;
}
}
26 changes: 24 additions & 2 deletions src/base64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ limitations under the License.
* Base64 encoding and decoding utilities
*/

declare global {
export interface Uint8ArrayToBase64Options {
alphabet?: "base64" | "base64url";
omitPadding?: boolean;
}

interface Uint8Array {
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
toBase64?(options?: Uint8ArrayToBase64Options): string;
}

export interface Uint8ArrayFromBase64Options {
alphabet?: "base64"; // Our fallback code only handles base64.
lastChunkHandling?: "loose"; // Our fallback code doesn't support other handling at this time.
}

interface Uint8ArrayConstructor {
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
fromBase64?(base64: string, options?: Uint8ArrayFromBase64Options): Uint8Array<ArrayBuffer>;
}
}

function toBase64(uint8Array: Uint8Array, options: Uint8ArrayToBase64Options): string {
if (typeof uint8Array.toBase64 === "function") {
// Currently this is only supported in Firefox,
Expand Down Expand Up @@ -64,7 +86,7 @@ export function encodeUnpaddedBase64Url(uint8Array: Uint8Array): string {
return toBase64(uint8Array, { alphabet: "base64url", omitPadding: true });
}

function fromBase64(base64: string, options: Uint8ArrayFromBase64Options): Uint8Array {
function fromBase64(base64: string, options: Uint8ArrayFromBase64Options): Uint8Array<ArrayBuffer> {
if (typeof Uint8Array.fromBase64 === "function") {
// Currently this is only supported in Firefox,
// but we match the options in the hope in the future we can rely on it for all environments.
Expand All @@ -80,7 +102,7 @@ function fromBase64(base64: string, options: Uint8ArrayFromBase64Options): Uint8
* @param base64 - The base64 to decode.
* @returns The decoded data.
*/
export function decodeBase64(base64: string): Uint8Array {
export function decodeBase64(base64: string): Uint8Array<ArrayBuffer> {
// The function requires us to select an alphabet, but we don't know if base64url was used so we convert.
return fromBase64(base64.replace(/-/g, "+").replace(/_/g, "/"), { alphabet: "base64", lastChunkHandling: "loose" });
}
6 changes: 3 additions & 3 deletions src/crypto-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ export interface CryptoCallbacks {
keys: Record<string, SecretStorageKeyDescription>;
},
name: string,
) => Promise<[string, Uint8Array] | null>;
) => Promise<[string, Uint8Array<ArrayBuffer>] | null>;

/**
* Called by {@link CryptoApi.bootstrapSecretStorage} when a new default secret storage key is created.
Expand All @@ -1140,7 +1140,7 @@ export interface CryptoCallbacks {
* @param keyInfo - secret storage key info
* @param key - private key to store
*/
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void;
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array<ArrayBuffer>) => void;
}

/**
Expand Down Expand Up @@ -1197,7 +1197,7 @@ export interface GeneratedSecretStorageKey {
name?: string;
};
/** The raw generated private key. */
privateKey: Uint8Array;
privateKey: Uint8Array<ArrayBuffer>;
/** The generated key, encoded for display to the user per https://spec.matrix.org/v1.7/client-server-api/#key-representation. */
encodedPrivateKey?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion src/crypto-api/key-passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function deriveRecoveryKeyFromPassphrase(
salt: string,
iterations: number,
numBits = DEFAULT_BIT_SIZE,
): Promise<Uint8Array> {
): Promise<Uint8Array<ArrayBuffer>> {
if (!globalThis.crypto.subtle || !TextEncoder) {
throw new Error("Password-based backup is not available on this platform");
}
Expand Down
2 changes: 1 addition & 1 deletion src/crypto-api/recovery-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
* Decode a recovery key encoded with the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation} encoding.
* @param recoveryKey
*/
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
export function decodeRecoveryKey(recoveryKey: string): Uint8Array<ArrayBuffer> {
const result = bs58.decode(recoveryKey.replace(/ /g, ""));

let parity = 0;
Expand Down
4 changes: 2 additions & 2 deletions src/rust-crypto/libolm_migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ async function migrateBaseData(
userId: string,
deviceId: string,
legacyStore: CryptoStore,
pickleKey: Uint8Array,
pickleKey: Uint8Array<ArrayBuffer>,
storeHandle: RustSdkCryptoJs.StoreHandle,
logger: Logger,
): Promise<void> {
Expand Down Expand Up @@ -414,7 +414,7 @@ export async function migrateRoomSettingsFromLegacyCrypto({

async function getAndDecryptCachedSecretKey(
legacyStore: CryptoStore,
legacyPickleKey: Uint8Array,
legacyPickleKey: Uint8Array<ArrayBuffer>,
name: string,
): Promise<string | undefined> {
const key = await new Promise<any>((resolve) => {
Expand Down
3 changes: 2 additions & 1 deletion src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1556,7 +1556,8 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
}

// 2. Upload the encrypted bundle to the server
const uploadResponse = await this.http.uploadContent(bundle.encryptedData);
// XXX: `slice` workaround for TS5.9 lib DOM type changes which are incompatible with latest rust crypto release
const uploadResponse = await this.http.uploadContent(bundle.encryptedData.slice());
logger.info(`Uploaded encrypted key blob: ${JSON.stringify(uploadResponse)}`);

// 3. We may not share a room with the user, so get a fresh list of devices for the invited user.
Expand Down
11 changes: 7 additions & 4 deletions src/secret-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export interface AddSecretStorageKeyOpts {
/** Optional name of the key. */
name?: string;
/** The private key. Will be used to generate the key check values in the key info; it will not be stored on the server */
key: Uint8Array;
key: Uint8Array<ArrayBuffer>;
}

/**
Expand Down Expand Up @@ -202,7 +202,7 @@ export interface SecretStorageCallbacks {
keys: Record<string, SecretStorageKeyDescription>;
},
name: string,
) => Promise<[string, Uint8Array] | null>;
) => Promise<[string, Uint8Array<ArrayBuffer>] | null>;
}

/**
Expand Down Expand Up @@ -493,7 +493,7 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
*
* @returns whether or not the key matches
*/
public async checkKey(key: Uint8Array, info: SecretStorageKeyDescriptionAesV1): Promise<boolean> {
public async checkKey(key: Uint8Array<ArrayBuffer>, info: SecretStorageKeyDescriptionAesV1): Promise<boolean> {
if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (info.mac) {
const { mac } = await calculateKeyCheck(key, info.iv);
Expand Down Expand Up @@ -706,6 +706,9 @@ 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
* 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> {
export function calculateKeyCheck(
key: Uint8Array<ArrayBuffer>,
iv?: string,
): Promise<AESEncryptedSecretStoragePayload> {
return encryptAESSecretStorageItem(ZERO_STR, key, "", iv);
}
2 changes: 1 addition & 1 deletion src/utils/decryptAESSecretStorageItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { type AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSec
*/
export default async function decryptAESSecretStorageItem(
data: AESEncryptedSecretStoragePayload,
key: Uint8Array,
key: Uint8Array<ArrayBuffer>,
name: string,
): Promise<string> {
const [aesKey, hmacKey] = await deriveKeys(key, name);
Expand Down
4 changes: 2 additions & 2 deletions src/utils/encryptAESSecretStorageItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ import { type AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSec
*/
export default async function encryptAESSecretStorageItem(
data: string,
key: Uint8Array,
key: Uint8Array<ArrayBuffer>,
name: string,
ivStr?: string,
): Promise<AESEncryptedSecretStoragePayload> {
let iv: Uint8Array;
let iv: Uint8Array<ArrayBuffer>;
if (ivStr) {
iv = decodeBase64(ivStr);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/internal/deriveKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const zeroSalt = new Uint8Array(8);
* @param key
* @param name
*/
export async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
export async function deriveKeys(key: Uint8Array<ArrayBuffer>, 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(
{
Expand Down
2 changes: 1 addition & 1 deletion src/webrtc/callFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
private measuringVolumeActivity = false;
private audioContext?: AudioContext;
private analyser?: AnalyserNode;
private frequencyBinCount?: Float32Array;
private frequencyBinCount?: Float32Array<ArrayBuffer>;
private speakingThreshold = SPEAKING_THRESHOLD;
private speaking = false;
private volumeLooperTimeout?: ReturnType<typeof setTimeout>;
Expand Down
21 changes: 13 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2155,12 +2155,12 @@
dependencies:
undici-types "~5.26.4"

"@types/node@18":
version "18.19.123"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.123.tgz#08a3e4f5e0c73b8840c677b7635ce59d5dc1f76d"
integrity sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==
"@types/node@^22":
version "22.17.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.17.2.tgz#47a93d6f4b79327da63af727e7c54e8cab8c4d33"
integrity sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==
dependencies:
undici-types "~5.26.4"
undici-types "~6.21.0"

"@types/normalize-package-data@^2.4.0":
version "2.4.4"
Expand Down Expand Up @@ -6752,9 +6752,9 @@ typedoc@^0.28.1:
yaml "^2.8.0"

typescript@^5.4.2:
version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
version "5.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"
integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==

uc.micro@^2.0.0, uc.micro@^2.1.0:
version "2.1.0"
Expand All @@ -6776,6 +6776,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==

undici-types@~6.21.0:
version "6.21.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==

unhomoglyph@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3"
Expand Down