You've already forked matrix-js-sdk
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:
committed by
GitHub
parent
1cad6f4451
commit
beb3721e7a
20
src/@types/global.d.ts
vendored
20
src/@types/global.d.ts
vendored
@@ -67,4 +67,24 @@ declare global {
|
||||
// on webkit: we should check if we still need to do this
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -18,30 +18,32 @@ limitations under the License.
|
||||
* 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.
|
||||
* @param uint8Array - The data to encode.
|
||||
* @returns The base64.
|
||||
*/
|
||||
export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string {
|
||||
// A brief note on the state of base64 encoding in Javascript.
|
||||
// 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!");
|
||||
}
|
||||
export function encodeBase64(uint8Array: Uint8Array): string {
|
||||
return toBase64(uint8Array, { alphabet: "base64", omitPadding: false });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,8 +51,8 @@ export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string {
|
||||
* @param uint8Array - The data to encode.
|
||||
* @returns The unpadded base64.
|
||||
*/
|
||||
export function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): string {
|
||||
return encodeBase64(uint8Array).replace(/={1,2}$/, "");
|
||||
export function encodeUnpaddedBase64(uint8Array: Uint8Array): string {
|
||||
return toBase64(uint8Array, { alphabet: "base64", omitPadding: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,8 +60,19 @@ export function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): stri
|
||||
* @param uint8Array - The data to encode.
|
||||
* @returns The unpadded base64.
|
||||
*/
|
||||
export function encodeUnpaddedBase64Url(uint8Array: ArrayBuffer | Uint8Array): string {
|
||||
return encodeUnpaddedBase64(uint8Array).replace(/\+/g, "-").replace(/\//g, "_");
|
||||
export function encodeUnpaddedBase64Url(uint8Array: Uint8Array): string {
|
||||
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.
|
||||
*/
|
||||
export function decodeBase64(base64: string): Uint8Array {
|
||||
// See encodeBase64 for a short treatise on base64 en/decoding in JS
|
||||
if (typeof Buffer === "function") {
|
||||
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!");
|
||||
}
|
||||
// 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" });
|
||||
}
|
||||
|
@@ -3899,28 +3899,28 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
private async restoreKeyBackup(
|
||||
privKey: ArrayLike<number>,
|
||||
privKey: Uint8Array,
|
||||
targetRoomId: undefined,
|
||||
targetSessionId: undefined,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult>;
|
||||
private async restoreKeyBackup(
|
||||
privKey: ArrayLike<number>,
|
||||
privKey: Uint8Array,
|
||||
targetRoomId: string,
|
||||
targetSessionId: undefined,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult>;
|
||||
private async restoreKeyBackup(
|
||||
privKey: ArrayLike<number>,
|
||||
privKey: Uint8Array,
|
||||
targetRoomId: string,
|
||||
targetSessionId: string,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult>;
|
||||
private async restoreKeyBackup(
|
||||
privKey: ArrayLike<number>,
|
||||
privKey: Uint8Array,
|
||||
targetRoomId: string | undefined,
|
||||
targetSessionId: string | undefined,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
|
@@ -108,7 +108,7 @@ export interface CryptoBackend extends SyncCryptoCallbacks, CryptoApi {
|
||||
* @param backupInfo - The backup information
|
||||
* @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
|
||||
|
@@ -26,7 +26,7 @@ const KEY_SIZE = 32;
|
||||
* @param key
|
||||
*/
|
||||
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(key, OLM_RECOVERY_KEY_PREFIX.length);
|
||||
|
||||
|
@@ -155,7 +155,7 @@ export interface VerificationRequest
|
||||
* @param qrCodeData - the decoded QR code.
|
||||
* @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.
|
||||
@@ -170,7 +170,7 @@ export interface VerificationRequest
|
||||
*
|
||||
* @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.
|
||||
@@ -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;
|
||||
* 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
|
||||
|
@@ -1846,7 +1846,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
/**
|
||||
* 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)) {
|
||||
throw new Error(`getBackupDecryptor expects Uint8Array`);
|
||||
}
|
||||
|
@@ -281,8 +281,9 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
|
||||
*
|
||||
* @deprecated Prefer `generateQRCode`.
|
||||
*/
|
||||
public getQRCodeBytes(): Buffer | undefined {
|
||||
return this._qrCodeData?.getBuffer();
|
||||
public getQRCodeBytes(): Uint8ClampedArray | undefined {
|
||||
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;
|
||||
* otherwise returns `undefined`.
|
||||
*/
|
||||
public async generateQRCode(): Promise<Buffer | undefined> {
|
||||
public async generateQRCode(): Promise<Uint8ClampedArray | undefined> {
|
||||
return this.getQRCodeBytes();
|
||||
}
|
||||
|
||||
@@ -478,7 +479,7 @@ export class VerificationRequest<C extends IVerificationChannel = IVerificationC
|
||||
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");
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
*
|
||||
* @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
|
||||
* 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) {
|
||||
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);
|
||||
|
||||
return digest;
|
||||
return new Uint8Array(digest);
|
||||
}
|
||||
|
@@ -329,7 +329,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
/**
|
||||
* 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)) {
|
||||
throw new Error(`getBackupDecryptor: expects Uint8Array`);
|
||||
}
|
||||
@@ -1178,7 +1178,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
public async getSessionBackupPrivateKey(): Promise<Uint8Array | null> {
|
||||
const backupKeys: RustSdkCryptoJs.BackupKeys = await this.olmMachine.getBackupKeys();
|
||||
if (!backupKeys.decryptionKey) return null;
|
||||
return Buffer.from(backupKeys.decryptionKey.toBase64(), "base64");
|
||||
return decodeBase64(backupKeys.decryptionKey.toBase64());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -381,8 +381,8 @@ export class RustVerificationRequest
|
||||
* @param qrCodeData - the decoded QR code.
|
||||
* @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> {
|
||||
const scan = RustSdkCryptoJs.QrCodeScan.fromBytes(new Uint8ClampedArray(uint8Array));
|
||||
public async scanQRCode(uint8Array: Uint8ClampedArray): Promise<Verifier> {
|
||||
const scan = RustSdkCryptoJs.QrCodeScan.fromBytes(uint8Array);
|
||||
const verifier: RustSdkCryptoJs.Qr = await this.inner.scanQrCode(scan);
|
||||
|
||||
// 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}.
|
||||
*/
|
||||
public getQRCodeBytes(): Buffer | undefined {
|
||||
public getQRCodeBytes(): Uint8ClampedArray | undefined {
|
||||
throw new Error("getQRCodeBytes() unsupported in Rust Crypto; use generateQRCode() instead.");
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ export class RustVerificationRequest
|
||||
*
|
||||
* 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)
|
||||
if (!(await this.getOtherDevice())) {
|
||||
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 (!innerVerifier) return;
|
||||
|
||||
return Buffer.from(innerVerifier.toBytes());
|
||||
return innerVerifier.toBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -67,7 +67,7 @@ export default async function encryptAESSecretStorageItem(
|
||||
|
||||
return {
|
||||
iv: encodeBase64(iv),
|
||||
ciphertext: encodeBase64(ciphertext),
|
||||
mac: encodeBase64(hmac),
|
||||
ciphertext: encodeBase64(new Uint8Array(ciphertext)),
|
||||
mac: encodeBase64(new Uint8Array(hmac)),
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user