1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +03:00

Avoid use of Buffer as it does not exist in the Web natively (#4569)

This commit is contained in:
Michael Telatynski
2024-12-04 22:32:09 +00:00
committed by GitHub
parent 1cad6f4451
commit beb3721e7a
15 changed files with 112 additions and 113 deletions

View File

@@ -473,21 +473,23 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
expect(request.phase).toEqual(VerificationPhase.Ready); expect(request.phase).toEqual(VerificationPhase.Ready);
// we should now have QR data we can display // we should now have QR data we can display
const qrCodeBuffer = (await request.generateQRCode())!; const rawQrCodeBuffer = (await request.generateQRCode())!;
expect(qrCodeBuffer).toBeTruthy(); expect(rawQrCodeBuffer).toBeTruthy();
const qrCodeBuffer = new Uint8Array(rawQrCodeBuffer);
const textDecoder = new TextDecoder();
// https://spec.matrix.org/v1.7/client-server-api/#qr-code-format // https://spec.matrix.org/v1.7/client-server-api/#qr-code-format
expect(qrCodeBuffer.subarray(0, 6).toString("latin1")).toEqual("MATRIX"); expect(textDecoder.decode(qrCodeBuffer.slice(0, 6))).toEqual("MATRIX");
expect(qrCodeBuffer.readUint8(6)).toEqual(0x02); // version expect(qrCodeBuffer[6]).toEqual(0x02); // version
expect(qrCodeBuffer.readUint8(7)).toEqual(0x02); // mode expect(qrCodeBuffer[7]).toEqual(0x02); // mode
const txnIdLen = qrCodeBuffer.readUint16BE(8); const txnIdLen = (qrCodeBuffer[8] << 8) + qrCodeBuffer[9];
expect(qrCodeBuffer.subarray(10, 10 + txnIdLen).toString("utf-8")).toEqual(transactionId); expect(textDecoder.decode(qrCodeBuffer.slice(10, 10 + txnIdLen))).toEqual(transactionId);
// Alice's device's public key comes next, but we have nothing to do with it here. // Alice's device's public key comes next, but we have nothing to do with it here.
// const aliceDevicePubKey = qrCodeBuffer.subarray(10 + txnIdLen, 32 + 10 + txnIdLen); // const aliceDevicePubKey = qrCodeBuffer.slice(10 + txnIdLen, 32 + 10 + txnIdLen);
expect(qrCodeBuffer.subarray(42 + txnIdLen, 32 + 42 + txnIdLen)).toEqual( expect(encodeUnpaddedBase64(qrCodeBuffer.slice(42 + txnIdLen, 32 + 42 + txnIdLen))).toEqual(
Buffer.from(MASTER_CROSS_SIGNING_PUBLIC_KEY_BASE64, "base64"), MASTER_CROSS_SIGNING_PUBLIC_KEY_BASE64,
); );
const sharedSecret = qrCodeBuffer.subarray(74 + txnIdLen); const sharedSecret = qrCodeBuffer.slice(74 + txnIdLen);
// we should still be "Ready" and have no verifier // we should still be "Ready" and have no verifier
expect(request.phase).toEqual(VerificationPhase.Ready); expect(request.phase).toEqual(VerificationPhase.Ready);
@@ -805,7 +807,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
// we should now have QR data we can display // we should now have QR data we can display
const qrCodeBuffer = (await request.generateQRCode())!; const qrCodeBuffer = (await request.generateQRCode())!;
expect(qrCodeBuffer).toBeTruthy(); expect(qrCodeBuffer).toBeTruthy();
const sharedSecret = qrCodeBuffer.subarray(74 + transactionId.length); const sharedSecret = qrCodeBuffer.slice(74 + transactionId.length);
// the dummy device "scans" the displayed QR code and acknowledges it with a "m.key.verification.start" // the dummy device "scans" the displayed QR code and acknowledges it with a "m.key.verification.start"
returnToDeviceMessageFromSync(buildReciprocateStartMessage(transactionId, sharedSecret)); returnToDeviceMessageFromSync(buildReciprocateStartMessage(transactionId, sharedSecret));
@@ -1627,7 +1629,7 @@ function buildReadyMessage(
} }
/** build an m.key.verification.start to-device message suitable for the m.reciprocate.v1 flow, originating from the dummy device */ /** build an m.key.verification.start to-device message suitable for the m.reciprocate.v1 flow, originating from the dummy device */
function buildReciprocateStartMessage(transactionId: string, sharedSecret: Uint8Array) { function buildReciprocateStartMessage(transactionId: string, sharedSecret: ArrayBuffer) {
return { return {
type: "m.key.verification.start", type: "m.key.verification.start",
content: { content: {
@@ -1723,7 +1725,7 @@ function buildQRCode(
key2Base64: string, key2Base64: string,
sharedSecret: string, sharedSecret: string,
mode = 0x02, mode = 0x02,
): Uint8Array { ): Uint8ClampedArray {
// https://spec.matrix.org/v1.7/client-server-api/#qr-code-format // https://spec.matrix.org/v1.7/client-server-api/#qr-code-format
const qrCodeBuffer = Buffer.alloc(150); // oversize const qrCodeBuffer = Buffer.alloc(150); // oversize
@@ -1739,5 +1741,5 @@ function buildQRCode(
idx += qrCodeBuffer.write(sharedSecret, idx); idx += qrCodeBuffer.write(sharedSecret, idx);
// truncate to the right length // truncate to the right length
return qrCodeBuffer.subarray(0, idx); return new Uint8ClampedArray(qrCodeBuffer.subarray(0, idx));
} }

View File

@@ -15,34 +15,10 @@ limitations under the License.
*/ */
import { TextEncoder, TextDecoder } from "util"; import { TextEncoder, TextDecoder } from "util";
import NodeBuffer from "node:buffer";
import { decodeBase64, encodeBase64, encodeUnpaddedBase64, encodeUnpaddedBase64Url } from "../../src/base64"; import { decodeBase64, encodeBase64, encodeUnpaddedBase64, encodeUnpaddedBase64Url } from "../../src/base64";
describe.each(["browser", "node"])("Base64 encoding (%s)", (env) => { describe("Base64 encoding", () => {
let origBuffer = Buffer;
beforeAll(() => {
if (env === "browser") {
origBuffer = Buffer;
// @ts-ignore
// eslint-disable-next-line no-global-assign
Buffer = undefined;
globalThis.atob = NodeBuffer.atob;
globalThis.btoa = NodeBuffer.btoa;
}
});
afterAll(() => {
// eslint-disable-next-line no-global-assign
Buffer = origBuffer;
// @ts-ignore
globalThis.atob = undefined;
// @ts-ignore
globalThis.btoa = undefined;
});
it("Should decode properly encoded data", () => { it("Should decode properly encoded data", () => {
const decoded = new TextDecoder().decode(decodeBase64("ZW5jb2RpbmcgaGVsbG8gd29ybGQ=")); const decoded = new TextDecoder().decode(decodeBase64("ZW5jb2RpbmcgaGVsbG8gd29ybGQ="));

View File

@@ -38,6 +38,8 @@ const membershipTemplate: CallMembershipData = {
const mockFocus = { type: "mock" }; const mockFocus = { type: "mock" };
const textEncoder = new TextEncoder();
describe("MatrixRTCSession", () => { describe("MatrixRTCSession", () => {
let client: MatrixClient; let client: MatrixClient;
let sess: MatrixRTCSession | undefined; let sess: MatrixRTCSession | undefined;
@@ -1345,7 +1347,7 @@ describe("MatrixRTCSession", () => {
sess!.reemitEncryptionKeys(); sess!.reemitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("this is the key", "utf-8"), textEncoder.encode("this is the key"),
0, 0,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );
@@ -1377,7 +1379,7 @@ describe("MatrixRTCSession", () => {
sess!.reemitEncryptionKeys(); sess!.reemitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("this is the key", "utf-8"), textEncoder.encode("this is the key"),
4, 4,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );
@@ -1409,7 +1411,7 @@ describe("MatrixRTCSession", () => {
sess!.reemitEncryptionKeys(); sess!.reemitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("this is the key", "utf-8"), textEncoder.encode("this is the key"),
0, 0,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );
@@ -1436,12 +1438,12 @@ describe("MatrixRTCSession", () => {
sess!.reemitEncryptionKeys(); sess!.reemitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(2); expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(2);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("this is the key", "utf-8"), textEncoder.encode("this is the key"),
0, 0,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("this is the key", "utf-8"), textEncoder.encode("this is the key"),
4, 4,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );
@@ -1489,7 +1491,7 @@ describe("MatrixRTCSession", () => {
sess!.reemitEncryptionKeys(); sess!.reemitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("newer key", "utf-8"), textEncoder.encode("newer key"),
0, 0,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );
@@ -1537,7 +1539,7 @@ describe("MatrixRTCSession", () => {
sess!.reemitEncryptionKeys(); sess!.reemitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith( expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
Buffer.from("second key", "utf-8"), textEncoder.encode("second key"),
0, 0,
"@bob:example.org:bobsphone", "@bob:example.org:bobsphone",
); );

View File

@@ -67,4 +67,24 @@ declare global {
// on webkit: we should check if we still need to do this // on webkit: we should check if we still need to do this
webkitGetUserMedia?: DummyInterfaceWeShouldntBeUsingThis; webkitGetUserMedia?: DummyInterfaceWeShouldntBeUsingThis;
} }
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;
}
} }

View File

@@ -18,30 +18,32 @@ limitations under the License.
* Base64 encoding and decoding utilities * Base64 encoding and decoding utilities
*/ */
function toBase64(uint8Array: Uint8Array, options: Uint8ArrayToBase64Options): string {
if (typeof uint8Array.toBase64 === "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.
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
return uint8Array.toBase64(options);
}
let base64 = btoa(uint8Array.reduce((acc, current) => acc + String.fromCharCode(current), ""));
if (options.omitPadding) {
base64 = base64.replace(/={1,2}$/, "");
}
if (options.alphabet === "base64url") {
base64 = base64.replace(/\+/g, "-").replace(/\//g, "_");
}
return base64;
}
/** /**
* Encode a typed array of uint8 as base64. * Encode a typed array of uint8 as base64.
* @param uint8Array - The data to encode. * @param uint8Array - The data to encode.
* @returns The base64. * @returns The base64.
*/ */
export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string { export function encodeBase64(uint8Array: Uint8Array): string {
// A brief note on the state of base64 encoding in Javascript. return toBase64(uint8Array, { alphabet: "base64", omitPadding: false });
// As of 2023, there is still no common native impl between both browsers and
// node. Older Webpack provides an impl for Buffer and there is a polyfill class
// for it. There are also plenty of pure js impls, eg. base64-js which has 2336
// dependents at current count. Using this would probably be fine although it's
// a little under-docced and run by an individual. The node impl works fine,
// the browser impl works but predates Uint8Array and so only uses strings.
// Right now, switching between native (or polyfilled) impls like this feels
// like the least bad option, but... *shrugs*.
if (typeof Buffer === "function") {
return Buffer.from(uint8Array).toString("base64");
} else if (typeof btoa === "function" && uint8Array instanceof Uint8Array) {
// ArrayBuffer is a node concept so the param should always be a Uint8Array on
// the browser. We need to check because ArrayBuffers don't have reduce.
return btoa(uint8Array.reduce((acc, current) => acc + String.fromCharCode(current), ""));
} else {
throw new Error("No base64 impl found!");
}
} }
/** /**
@@ -49,8 +51,8 @@ export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string {
* @param uint8Array - The data to encode. * @param uint8Array - The data to encode.
* @returns The unpadded base64. * @returns The unpadded base64.
*/ */
export function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): string { export function encodeUnpaddedBase64(uint8Array: Uint8Array): string {
return encodeBase64(uint8Array).replace(/={1,2}$/, ""); return toBase64(uint8Array, { alphabet: "base64", omitPadding: true });
} }
/** /**
@@ -58,8 +60,19 @@ export function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): stri
* @param uint8Array - The data to encode. * @param uint8Array - The data to encode.
* @returns The unpadded base64. * @returns The unpadded base64.
*/ */
export function encodeUnpaddedBase64Url(uint8Array: ArrayBuffer | Uint8Array): string { export function encodeUnpaddedBase64Url(uint8Array: Uint8Array): string {
return encodeUnpaddedBase64(uint8Array).replace(/\+/g, "-").replace(/\//g, "_"); return toBase64(uint8Array, { alphabet: "base64url", omitPadding: true });
}
function fromBase64(base64: string, options: Uint8ArrayFromBase64Options): Uint8Array {
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.
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
return Uint8Array.fromBase64(base64, options);
}
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
} }
/** /**
@@ -68,21 +81,6 @@ export function encodeUnpaddedBase64Url(uint8Array: ArrayBuffer | Uint8Array): s
* @returns The decoded data. * @returns The decoded data.
*/ */
export function decodeBase64(base64: string): Uint8Array { export function decodeBase64(base64: string): Uint8Array {
// See encodeBase64 for a short treatise on base64 en/decoding in JS // The function requires us to select an alphabet, but we don't know if base64url was used so we convert.
if (typeof Buffer === "function") { return fromBase64(base64.replace(/-/g, "+").replace(/_/g, "/"), { alphabet: "base64", lastChunkHandling: "loose" });
return Buffer.from(base64, "base64");
} else if (typeof atob === "function") {
const itFunc = function* (): Generator<number> {
const decoded = atob(
// built-in atob doesn't support base64url: convert so we support either
base64.replace(/-/g, "+").replace(/_/g, "/"),
);
for (let i = 0; i < decoded.length; ++i) {
yield decoded.charCodeAt(i);
}
};
return Uint8Array.from(itFunc());
} else {
throw new Error("No base64 impl found!");
}
} }

View File

@@ -3899,28 +3899,28 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
} }
private async restoreKeyBackup( private async restoreKeyBackup(
privKey: ArrayLike<number>, privKey: Uint8Array,
targetRoomId: undefined, targetRoomId: undefined,
targetSessionId: undefined, targetSessionId: undefined,
backupInfo: IKeyBackupInfo, backupInfo: IKeyBackupInfo,
opts?: IKeyBackupRestoreOpts, opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult>; ): Promise<IKeyBackupRestoreResult>;
private async restoreKeyBackup( private async restoreKeyBackup(
privKey: ArrayLike<number>, privKey: Uint8Array,
targetRoomId: string, targetRoomId: string,
targetSessionId: undefined, targetSessionId: undefined,
backupInfo: IKeyBackupInfo, backupInfo: IKeyBackupInfo,
opts?: IKeyBackupRestoreOpts, opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult>; ): Promise<IKeyBackupRestoreResult>;
private async restoreKeyBackup( private async restoreKeyBackup(
privKey: ArrayLike<number>, privKey: Uint8Array,
targetRoomId: string, targetRoomId: string,
targetSessionId: string, targetSessionId: string,
backupInfo: IKeyBackupInfo, backupInfo: IKeyBackupInfo,
opts?: IKeyBackupRestoreOpts, opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult>; ): Promise<IKeyBackupRestoreResult>;
private async restoreKeyBackup( private async restoreKeyBackup(
privKey: ArrayLike<number>, privKey: Uint8Array,
targetRoomId: string | undefined, targetRoomId: string | undefined,
targetSessionId: string | undefined, targetSessionId: string | undefined,
backupInfo: IKeyBackupInfo, backupInfo: IKeyBackupInfo,

View File

@@ -108,7 +108,7 @@ export interface CryptoBackend extends SyncCryptoCallbacks, CryptoApi {
* @param backupInfo - The backup information * @param backupInfo - The backup information
* @param privKey - The private decryption key. * @param privKey - The private decryption key.
*/ */
getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: ArrayLike<number>): Promise<BackupDecryptor>; getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: Uint8Array): Promise<BackupDecryptor>;
/** /**
* Import a list of room keys restored from backup * Import a list of room keys restored from backup

View File

@@ -26,7 +26,7 @@ const KEY_SIZE = 32;
* @param key * @param key
*/ */
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined { export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1); const buf = new Uint8Array(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
buf.set(OLM_RECOVERY_KEY_PREFIX, 0); buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length); buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);

View File

@@ -155,7 +155,7 @@ export interface VerificationRequest
* @param qrCodeData - the decoded QR code. * @param qrCodeData - the decoded QR code.
* @returns A verifier; call `.verify()` on it to wait for the other side to complete the verification flow. * @returns A verifier; call `.verify()` on it to wait for the other side to complete the verification flow.
*/ */
scanQRCode(qrCodeData: Uint8Array): Promise<Verifier>; scanQRCode(qrCodeData: Uint8ClampedArray): Promise<Verifier>;
/** /**
* The verifier which is doing the actual verification, once the method has been established. * The verifier which is doing the actual verification, once the method has been established.
@@ -170,7 +170,7 @@ export interface VerificationRequest
* *
* @deprecated Not supported in Rust Crypto. Use {@link VerificationRequest#generateQRCode} instead. * @deprecated Not supported in Rust Crypto. Use {@link VerificationRequest#generateQRCode} instead.
*/ */
getQRCodeBytes(): Buffer | undefined; getQRCodeBytes(): Uint8ClampedArray | undefined;
/** /**
* Generate the data for a QR code allowing the other device to verify this one, if it supports it. * Generate the data for a QR code allowing the other device to verify this one, if it supports it.
@@ -178,7 +178,7 @@ export interface VerificationRequest
* Only returns data once `phase` is {@link VerificationPhase.Ready} and the other party can scan a QR code; * Only returns data once `phase` is {@link VerificationPhase.Ready} and the other party can scan a QR code;
* otherwise returns `undefined`. * otherwise returns `undefined`.
*/ */
generateQRCode(): Promise<Buffer | undefined>; generateQRCode(): Promise<Uint8ClampedArray | undefined>;
/** /**
* If this request has been cancelled, the cancellation code (e.g `m.user`) which is responsible for cancelling * If this request has been cancelled, the cancellation code (e.g `m.user`) which is responsible for cancelling

View File

@@ -1846,7 +1846,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
/** /**
* Implementation of {@link CryptoBackend#getBackupDecryptor}. * Implementation of {@link CryptoBackend#getBackupDecryptor}.
*/ */
public async getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: ArrayLike<number>): Promise<BackupDecryptor> { public async getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: Uint8Array): Promise<BackupDecryptor> {
if (!(privKey instanceof Uint8Array)) { if (!(privKey instanceof Uint8Array)) {
throw new Error(`getBackupDecryptor expects Uint8Array`); throw new Error(`getBackupDecryptor expects Uint8Array`);
} }

View File

@@ -281,8 +281,9 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
* *
* @deprecated Prefer `generateQRCode`. * @deprecated Prefer `generateQRCode`.
*/ */
public getQRCodeBytes(): Buffer | undefined { public getQRCodeBytes(): Uint8ClampedArray | undefined {
return this._qrCodeData?.getBuffer(); if (!this._qrCodeData) return;
return new Uint8ClampedArray(this._qrCodeData.getBuffer());
} }
/** /**
@@ -291,7 +292,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
* Only returns data once `phase` is `Ready` and the other party can scan a QR code; * Only returns data once `phase` is `Ready` and the other party can scan a QR code;
* otherwise returns `undefined`. * otherwise returns `undefined`.
*/ */
public async generateQRCode(): Promise<Buffer | undefined> { public async generateQRCode(): Promise<Uint8ClampedArray | undefined> {
return this.getQRCodeBytes(); return this.getQRCodeBytes();
} }
@@ -478,7 +479,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
return verifier; return verifier;
} }
public scanQRCode(qrCodeData: Uint8Array): Promise<Verifier> { public scanQRCode(qrCodeData: Uint8ClampedArray): Promise<Verifier> {
throw new Error("QR code scanning not supported by legacy crypto"); throw new Error("QR code scanning not supported by legacy crypto");
} }

View File

@@ -18,11 +18,11 @@ limitations under the License.
* Computes a SHA-256 hash of a string (after utf-8 encoding) and returns it as an ArrayBuffer. * Computes a SHA-256 hash of a string (after utf-8 encoding) and returns it as an ArrayBuffer.
* *
* @param plaintext The string to hash * @param plaintext The string to hash
* @returns An ArrayBuffer containing the SHA-256 hash of the input string * @returns An Uint8Array containing the SHA-256 hash of the input string
* @throws If the subtle crypto API is not available, for example if the code is running * @throws If the subtle crypto API is not available, for example if the code is running
* in a web page with an insecure context (eg. served over plain HTTP). * in a web page with an insecure context (eg. served over plain HTTP).
*/ */
export async function sha256(plaintext: string): Promise<ArrayBuffer> { export async function sha256(plaintext: string): Promise<Uint8Array> {
if (!globalThis.crypto.subtle) { if (!globalThis.crypto.subtle) {
throw new Error("Crypto.subtle is not available: insecure context?"); throw new Error("Crypto.subtle is not available: insecure context?");
} }
@@ -30,5 +30,5 @@ export async function sha256(plaintext: string): Promise<ArrayBuffer> {
const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8); const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8);
return digest; return new Uint8Array(digest);
} }

View File

@@ -329,7 +329,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
/** /**
* Implementation of {@link CryptoBackend#getBackupDecryptor}. * Implementation of {@link CryptoBackend#getBackupDecryptor}.
*/ */
public async getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: ArrayLike<number>): Promise<BackupDecryptor> { public async getBackupDecryptor(backupInfo: KeyBackupInfo, privKey: Uint8Array): Promise<BackupDecryptor> {
if (!(privKey instanceof Uint8Array)) { if (!(privKey instanceof Uint8Array)) {
throw new Error(`getBackupDecryptor: expects Uint8Array`); throw new Error(`getBackupDecryptor: expects Uint8Array`);
} }
@@ -1178,7 +1178,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
public async getSessionBackupPrivateKey(): Promise<Uint8Array | null> { public async getSessionBackupPrivateKey(): Promise<Uint8Array | null> {
const backupKeys: RustSdkCryptoJs.BackupKeys = await this.olmMachine.getBackupKeys(); const backupKeys: RustSdkCryptoJs.BackupKeys = await this.olmMachine.getBackupKeys();
if (!backupKeys.decryptionKey) return null; if (!backupKeys.decryptionKey) return null;
return Buffer.from(backupKeys.decryptionKey.toBase64(), "base64"); return decodeBase64(backupKeys.decryptionKey.toBase64());
} }
/** /**

View File

@@ -381,8 +381,8 @@ export class RustVerificationRequest
* @param qrCodeData - the decoded QR code. * @param qrCodeData - the decoded QR code.
* @returns A verifier; call `.verify()` on it to wait for the other side to complete the verification flow. * @returns A verifier; call `.verify()` on it to wait for the other side to complete the verification flow.
*/ */
public async scanQRCode(uint8Array: Uint8Array): Promise<Verifier> { public async scanQRCode(uint8Array: Uint8ClampedArray): Promise<Verifier> {
const scan = RustSdkCryptoJs.QrCodeScan.fromBytes(new Uint8ClampedArray(uint8Array)); const scan = RustSdkCryptoJs.QrCodeScan.fromBytes(uint8Array);
const verifier: RustSdkCryptoJs.Qr = await this.inner.scanQrCode(scan); const verifier: RustSdkCryptoJs.Qr = await this.inner.scanQrCode(scan);
// this should have triggered the onChange callback, and we should now have a verifier // this should have triggered the onChange callback, and we should now have a verifier
@@ -416,7 +416,7 @@ export class RustVerificationRequest
/** /**
* Stub implementation of {@link Crypto.VerificationRequest#getQRCodeBytes}. * Stub implementation of {@link Crypto.VerificationRequest#getQRCodeBytes}.
*/ */
public getQRCodeBytes(): Buffer | undefined { public getQRCodeBytes(): Uint8ClampedArray | undefined {
throw new Error("getQRCodeBytes() unsupported in Rust Crypto; use generateQRCode() instead."); throw new Error("getQRCodeBytes() unsupported in Rust Crypto; use generateQRCode() instead.");
} }
@@ -425,7 +425,7 @@ export class RustVerificationRequest
* *
* Implementation of {@link Crypto.VerificationRequest#generateQRCode}. * Implementation of {@link Crypto.VerificationRequest#generateQRCode}.
*/ */
public async generateQRCode(): Promise<Buffer | undefined> { public async generateQRCode(): Promise<Uint8ClampedArray | undefined> {
// make sure that we have a list of the other user's devices (workaround https://github.com/matrix-org/matrix-rust-sdk/issues/2896) // make sure that we have a list of the other user's devices (workaround https://github.com/matrix-org/matrix-rust-sdk/issues/2896)
if (!(await this.getOtherDevice())) { if (!(await this.getOtherDevice())) {
throw new Error("generateQRCode(): other device is unknown"); throw new Error("generateQRCode(): other device is unknown");
@@ -435,7 +435,7 @@ export class RustVerificationRequest
// If we are unable to generate a QRCode, we return undefined // If we are unable to generate a QRCode, we return undefined
if (!innerVerifier) return; if (!innerVerifier) return;
return Buffer.from(innerVerifier.toBytes()); return innerVerifier.toBytes();
} }
/** /**

View File

@@ -67,7 +67,7 @@ export default async function encryptAESSecretStorageItem(
return { return {
iv: encodeBase64(iv), iv: encodeBase64(iv),
ciphertext: encodeBase64(ciphertext), ciphertext: encodeBase64(new Uint8Array(ciphertext)),
mac: encodeBase64(hmac), mac: encodeBase64(new Uint8Array(hmac)),
}; };
} }