From e49639fda2a321552d27a3544861f99210854f67 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 11 Feb 2021 17:29:48 +0100 Subject: [PATCH] move textencoder/decoder into platform --- src/matrix/Session.js | 4 ++-- src/matrix/ssss/SecretStorage.js | 17 +++++++--------- src/matrix/ssss/index.js | 4 ++-- src/matrix/ssss/passphrase.js | 12 +++++------ src/platform/web/Platform.js | 2 ++ src/platform/web/dom/UTF8.js | 35 ++++++++++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 src/platform/web/dom/UTF8.js diff --git a/src/matrix/Session.js b/src/matrix/Session.js index ace33fa8..9952414c 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -184,7 +184,7 @@ export class Session { if (this._sessionBackup) { return false; } - const key = await ssssKeyFromCredential(type, credential, this._storage, this._platform.crypto, this._olm); + const key = await ssssKeyFromCredential(type, credential, this._storage, this._platform, this._olm); // and create session backup, which needs to read from accountData const readTxn = this._storage.readTxn([ this._storage.storeNames.accountData, @@ -206,7 +206,7 @@ export class Session { } async _createSessionBackup(ssssKey, txn) { - const secretStorage = new SecretStorage({key: ssssKey, crypto: this._platform.crypto}); + const secretStorage = new SecretStorage({key: ssssKey, platform: this._platform}); this._sessionBackup = await SessionBackup.fromSecretStorage({olm: this._olm, secretStorage, hsApi: this._hsApi, txn}); if (this._sessionBackup) { for (const room of this._rooms.values()) { diff --git a/src/matrix/ssss/SecretStorage.js b/src/matrix/ssss/SecretStorage.js index ae71280d..2b5a702f 100644 --- a/src/matrix/ssss/SecretStorage.js +++ b/src/matrix/ssss/SecretStorage.js @@ -17,9 +17,9 @@ limitations under the License. import base64 from "../../../lib/base64-arraybuffer/index.js"; export class SecretStorage { - constructor({key, crypto}) { + constructor({key, platform}) { this._key = key; - this._crypto = crypto; + this._platform = platform; } async readSecret(name, txn) { @@ -40,14 +40,11 @@ export class SecretStorage { } async _decryptAESSecret(type, encryptedData) { - // TODO: we should we move this to platform specific code - const textEncoder = new TextEncoder(); - const textDecoder = new TextDecoder(); // now derive the aes and mac key from the 4s key - const hkdfKey = await this._crypto.derive.hkdf( + const hkdfKey = await this._platform.crypto.derive.hkdf( this._key.binaryKey, new Uint8Array(8).buffer, //zero salt - textEncoder.encode(type), // info + this._platform.utf8.encode(type), // info "SHA-256", 512 // 512 bits or 64 bytes ); @@ -56,7 +53,7 @@ export class SecretStorage { const ciphertextBytes = base64.decode(encryptedData.ciphertext); - const isVerified = await this._crypto.hmac.verify( + const isVerified = await this._platform.crypto.hmac.verify( hmacKey, base64.decode(encryptedData.mac), ciphertextBytes, "SHA-256"); @@ -64,12 +61,12 @@ export class SecretStorage { throw new Error("Bad MAC"); } - const plaintextBytes = await this._crypto.aes.decryptCTR({ + const plaintextBytes = await this._platform.crypto.aes.decryptCTR({ key: aesKey, iv: base64.decode(encryptedData.iv), data: ciphertextBytes }); - return textDecoder.decode(plaintextBytes); + return this._platform.utf8.decode(plaintextBytes); } } diff --git a/src/matrix/ssss/index.js b/src/matrix/ssss/index.js index 222c4b46..1d8fea17 100644 --- a/src/matrix/ssss/index.js +++ b/src/matrix/ssss/index.js @@ -47,14 +47,14 @@ export async function readKey(txn) { return new Key(new KeyDescription(keyData.id, keyAccountData), keyData.binaryKey); } -export async function keyFromCredential(type, credential, storage, crypto, olm) { +export async function keyFromCredential(type, credential, storage, platform, olm) { const keyDescription = await readDefaultKeyDescription(storage); if (!keyDescription) { throw new Error("Could not find a default secret storage key in account data"); } let key; if (type === "phrase") { - key = await keyFromPassphrase(keyDescription, credential, crypto); + key = await keyFromPassphrase(keyDescription, credential, platform); } else if (type === "key") { key = keyFromRecoveryKey(olm, keyDescription, credential); } else { diff --git a/src/matrix/ssss/passphrase.js b/src/matrix/ssss/passphrase.js index 14f8dc0e..828d15d9 100644 --- a/src/matrix/ssss/passphrase.js +++ b/src/matrix/ssss/passphrase.js @@ -22,10 +22,10 @@ const DEFAULT_BITSIZE = 256; /** * @param {KeyDescription} keyDescription * @param {string} passphrase - * @param {Crypto} crypto + * @param {Platform} platform * @return {Key} */ -export async function keyFromPassphrase(keyDescription, passphrase, crypto) { +export async function keyFromPassphrase(keyDescription, passphrase, platform) { const {passphraseParams} = keyDescription; if (!passphraseParams) { throw new Error("not a passphrase key"); @@ -33,13 +33,11 @@ export async function keyFromPassphrase(keyDescription, passphrase, crypto) { if (passphraseParams.algorithm !== "m.pbkdf2") { throw new Error(`Unsupported passphrase algorithm: ${passphraseParams.algorithm}`); } - // TODO: we should we move this to platform specific code - const textEncoder = new TextEncoder(); - const keyBits = await crypto.derive.pbkdf2( - textEncoder.encode(passphrase), + const keyBits = await platform.crypto.derive.pbkdf2( + platform.utf8.encode(passphrase), passphraseParams.iterations || DEFAULT_ITERATIONS, // salt is just a random string, not encoded in any way - textEncoder.encode(passphraseParams.salt), + platform.utf8.encode(passphraseParams.salt), "SHA-512", passphraseParams.bits || DEFAULT_BITSIZE); return new Key(keyDescription, keyBits); diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 1b2226f2..d2e532af 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -19,6 +19,7 @@ import {xhrRequest} from "./dom/request/xhr.js"; import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js"; import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js"; import {SettingsStorage} from "./dom/SettingsStorage.js"; +import {UTF8} from "./dom/UTF8.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js"; import {RootView} from "./ui/RootView.js"; import {Clock} from "./dom/Clock.js"; @@ -83,6 +84,7 @@ export class Platform { constructor(container, paths, cryptoExtras = null) { this._paths = paths; this._container = container; + this.utf8 = new UTF8(); this.clock = new Clock(); this.history = new History(); this.onlineStatus = new OnlineStatus(); diff --git a/src/platform/web/dom/UTF8.js b/src/platform/web/dom/UTF8.js new file mode 100644 index 00000000..d30d0882 --- /dev/null +++ b/src/platform/web/dom/UTF8.js @@ -0,0 +1,35 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +export class UTF8 { + constructor() { + this._encoder = null; + this._decoder = null; + } + + encode(str) { + if (!this._encoder) { + this._encoder = new TextEncoder(); + } + return this._encoder.encode(str); + } + + decode(buffer) { + if (!this._decoder) { + this._decoder = new TextDecoder(); + } + return this._decoder.decode(buffer); + } +}