From bbab1e9ecc7c4cceede1d8e4f9026c68936bc404 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 12 Feb 2021 16:01:54 +0100 Subject: [PATCH] move base64/58 encoding into platform fixes https://github.com/vector-im/hydrogen-web/issues/99 --- src/matrix/Session.js | 7 ++++- src/matrix/e2ee/attachment.js | 21 ++++---------- src/matrix/e2ee/megolm/SessionBackup.js | 6 ++-- src/matrix/net/MediaRepository.js | 2 +- src/matrix/ssss/SecretStorage.js | 8 ++---- src/matrix/ssss/index.js | 2 +- src/matrix/ssss/passphrase.js | 5 ++-- src/matrix/ssss/recoveryKey.js | 5 ++-- src/platform/web/Platform.js | 4 +-- src/platform/web/dom/Crypto.js | 3 +- src/platform/web/utils/Base58.js | 27 ++++++++++++++++++ src/platform/web/utils/Base64.js | 37 +++++++++++++++++++++++++ src/platform/web/utils/Encoding.js | 27 ++++++++++++++++++ 13 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 src/platform/web/utils/Base58.js create mode 100644 src/platform/web/utils/Base64.js create mode 100644 src/platform/web/utils/Encoding.js diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 9952414c..17e4001f 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -207,7 +207,12 @@ export class Session { async _createSessionBackup(ssssKey, txn) { const secretStorage = new SecretStorage({key: ssssKey, platform: this._platform}); - this._sessionBackup = await SessionBackup.fromSecretStorage({olm: this._olm, secretStorage, hsApi: this._hsApi, txn}); + this._sessionBackup = await SessionBackup.fromSecretStorage({ + platform: this._platform, + olm: this._olm, secretStorage, + hsApi: this._hsApi, + txn + }); if (this._sessionBackup) { for (const room of this._rooms.values()) { if (room.isEncrypted) { diff --git a/src/matrix/e2ee/attachment.js b/src/matrix/e2ee/attachment.js index 3ead83eb..647cced8 100644 --- a/src/matrix/e2ee/attachment.js +++ b/src/matrix/e2ee/attachment.js @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import base64 from "../../../lib/base64-arraybuffer/index.js"; - /** * Decrypt an attachment. * @param {ArrayBuffer} ciphertextBuffer The encrypted attachment data buffer. @@ -25,12 +23,14 @@ import base64 from "../../../lib/base64-arraybuffer/index.js"; * @param {string} info.hashes.sha256 Base64 encoded SHA-256 hash of the ciphertext. * @return {Promise} A promise that resolves with an ArrayBuffer when the attachment is decrypted. */ -export async function decryptAttachment(crypto, ciphertextBuffer, info) { +export async function decryptAttachment(platform, ciphertextBuffer, info) { if (info === undefined || info.key === undefined || info.iv === undefined || info.hashes === undefined || info.hashes.sha256 === undefined) { throw new Error("Invalid info. Missing info.key, info.iv or info.hashes.sha256 key"); } + const {crypto} = platform; + const {base64} = platform.encoding; var ivArray = base64.decode(info.iv); // re-encode to not deal with padded vs unpadded var expectedSha256base64 = base64.encode(base64.decode(info.hashes.sha256)); @@ -59,6 +59,7 @@ export async function decryptAttachment(crypto, ciphertextBuffer, info) { export async function encryptAttachment(platform, blob) { const {crypto} = platform; + const {base64} = platform.encoding; const iv = await crypto.aes.generateIV(); const key = await crypto.aes.generateKey("jwk", 256); const buffer = await blob.readAsBuffer(); @@ -69,20 +70,10 @@ export async function encryptAttachment(platform, blob) { info: { v: "v2", key, - iv: encodeUnpaddedBase64(iv), + iv: base64.encodeUnpadded(iv), hashes: { - sha256: encodeUnpaddedBase64(digest) + sha256: base64.encodeUnpadded(digest) } } }; } - -function encodeUnpaddedBase64(buffer) { - const str = base64.encode(buffer); - const paddingIdx = str.indexOf("="); - if (paddingIdx !== -1) { - return str.substr(0, paddingIdx); - } else { - return str; - } -} diff --git a/src/matrix/e2ee/megolm/SessionBackup.js b/src/matrix/e2ee/megolm/SessionBackup.js index a5095311..e4c3788c 100644 --- a/src/matrix/e2ee/megolm/SessionBackup.js +++ b/src/matrix/e2ee/megolm/SessionBackup.js @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import base64 from "../../../../lib/base64-arraybuffer/index.js"; - export class SessionBackup { constructor({backupInfo, decryption, hsApi}) { this._backupInfo = backupInfo; @@ -41,10 +39,10 @@ export class SessionBackup { this._decryption.free(); } - static async fromSecretStorage({olm, secretStorage, hsApi, txn}) { + static async fromSecretStorage({platform, olm, secretStorage, hsApi, txn}) { const base64PrivateKey = await secretStorage.readSecret("m.megolm_backup.v1", txn); if (base64PrivateKey) { - const privateKey = new Uint8Array(base64.decode(base64PrivateKey)); + const privateKey = new Uint8Array(platform.encoding.base64.decode(base64PrivateKey)); const backupInfo = await hsApi.roomKeysVersion().response(); const expectedPubKey = backupInfo.auth_data.public_key; const decryption = new olm.PkDecryption(); diff --git a/src/matrix/net/MediaRepository.js b/src/matrix/net/MediaRepository.js index 7bd20cce..f7e47cfd 100644 --- a/src/matrix/net/MediaRepository.js +++ b/src/matrix/net/MediaRepository.js @@ -55,7 +55,7 @@ export class MediaRepository { async downloadEncryptedFile(fileEntry, cache = false) { const url = this.mxcUrl(fileEntry.url); const {body: encryptedBuffer} = await this._platform.request(url, {method: "GET", format: "buffer", cache}).response(); - const decryptedBuffer = await decryptAttachment(this._platform.crypto, encryptedBuffer, fileEntry); + const decryptedBuffer = await decryptAttachment(this._platform, encryptedBuffer, fileEntry); return this._platform.createBlob(decryptedBuffer, fileEntry.mimetype); } diff --git a/src/matrix/ssss/SecretStorage.js b/src/matrix/ssss/SecretStorage.js index 2b5a702f..04597084 100644 --- a/src/matrix/ssss/SecretStorage.js +++ b/src/matrix/ssss/SecretStorage.js @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import base64 from "../../../lib/base64-arraybuffer/index.js"; - export class SecretStorage { constructor({key, platform}) { this._key = key; @@ -40,17 +38,17 @@ export class SecretStorage { } async _decryptAESSecret(type, encryptedData) { + const {base64, utf8} = this._platform.encoding; // now derive the aes and mac key from the 4s key const hkdfKey = await this._platform.crypto.derive.hkdf( this._key.binaryKey, new Uint8Array(8).buffer, //zero salt - this._platform.utf8.encode(type), // info + utf8.encode(type), // info "SHA-256", 512 // 512 bits or 64 bytes ); const aesKey = hkdfKey.slice(0, 32); const hmacKey = hkdfKey.slice(32); - const ciphertextBytes = base64.decode(encryptedData.ciphertext); const isVerified = await this._platform.crypto.hmac.verify( @@ -67,6 +65,6 @@ export class SecretStorage { data: ciphertextBytes }); - return this._platform.utf8.decode(plaintextBytes); + return utf8.decode(plaintextBytes); } } diff --git a/src/matrix/ssss/index.js b/src/matrix/ssss/index.js index 1d8fea17..66ea3ea0 100644 --- a/src/matrix/ssss/index.js +++ b/src/matrix/ssss/index.js @@ -56,7 +56,7 @@ export async function keyFromCredential(type, credential, storage, platform, olm if (type === "phrase") { key = await keyFromPassphrase(keyDescription, credential, platform); } else if (type === "key") { - key = keyFromRecoveryKey(olm, keyDescription, credential); + key = keyFromRecoveryKey(keyDescription, credential, olm, platform); } else { throw new Error(`Invalid type: ${type}`); } diff --git a/src/matrix/ssss/passphrase.js b/src/matrix/ssss/passphrase.js index 828d15d9..681e4548 100644 --- a/src/matrix/ssss/passphrase.js +++ b/src/matrix/ssss/passphrase.js @@ -33,11 +33,12 @@ export async function keyFromPassphrase(keyDescription, passphrase, platform) { if (passphraseParams.algorithm !== "m.pbkdf2") { throw new Error(`Unsupported passphrase algorithm: ${passphraseParams.algorithm}`); } + const {utf8} = platform.encoding; const keyBits = await platform.crypto.derive.pbkdf2( - platform.utf8.encode(passphrase), + utf8.encode(passphrase), passphraseParams.iterations || DEFAULT_ITERATIONS, // salt is just a random string, not encoded in any way - platform.utf8.encode(passphraseParams.salt), + utf8.encode(passphraseParams.salt), "SHA-512", passphraseParams.bits || DEFAULT_BITSIZE); return new Key(keyDescription, keyBits); diff --git a/src/matrix/ssss/recoveryKey.js b/src/matrix/ssss/recoveryKey.js index 5d2b1d7a..bfe132a4 100644 --- a/src/matrix/ssss/recoveryKey.js +++ b/src/matrix/ssss/recoveryKey.js @@ -13,7 +13,6 @@ 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 bs58 from "../../../lib/bs58/index.js"; import {Key} from "./common.js"; const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; @@ -24,8 +23,8 @@ const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; * @param {string} recoveryKey * @return {Key} */ -export function keyFromRecoveryKey(olm, keyDescription, recoveryKey) { - const result = bs58.decode(recoveryKey.replace(/ /g, '')); +export function keyFromRecoveryKey(keyDescription, recoveryKey, olm, platform) { + const result = platform.encoding.base58.decode(recoveryKey.replace(/ /g, '')); let parity = 0; for (const b of result) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 73dca9c2..e5300779 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -19,7 +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 {Encoding} from "./utils/Encoding.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js"; import {IDBLogger} from "../../logs/IDBLogger.js"; import {RootView} from "./ui/RootView.js"; @@ -85,8 +85,8 @@ export class Platform { constructor(container, paths, cryptoExtras = null) { this._paths = paths; this._container = container; - this.utf8 = new UTF8(); this.logger = new IDBLogger("hydrogen_logs", this); + this.encoding = new Encoding(); this.clock = new Clock(); this.history = new History(); this.onlineStatus = new OnlineStatus(); diff --git a/src/platform/web/dom/Crypto.js b/src/platform/web/dom/Crypto.js index 2362ecda..b28897fa 100644 --- a/src/platform/web/dom/Crypto.js +++ b/src/platform/web/dom/Crypto.js @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import base64 from "../../../../lib/base64-arraybuffer/index.js"; + // turn IE11 result into promise function subtleCryptoResult(promiseOrOp, method) { if (promiseOrOp instanceof Promise) { @@ -302,7 +304,6 @@ function rawKeyToJwk(key) { }; } -import base64 from "../../../../lib/base64-arraybuffer/index.js"; class AESLegacyCrypto { constructor(aesjs, crypto) { diff --git a/src/platform/web/utils/Base58.js b/src/platform/web/utils/Base58.js new file mode 100644 index 00000000..ebe4a969 --- /dev/null +++ b/src/platform/web/utils/Base58.js @@ -0,0 +1,27 @@ +/* +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. +*/ + +import bs58 from "../../../../lib/bs58/index.js"; + +export class Base58 { + encode(buffer) { + return bs58.encode(buffer); + } + + decode(str) { + return bs58.decode(str); + } +} diff --git a/src/platform/web/utils/Base64.js b/src/platform/web/utils/Base64.js new file mode 100644 index 00000000..6c114fe6 --- /dev/null +++ b/src/platform/web/utils/Base64.js @@ -0,0 +1,37 @@ +/* +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. +*/ + +import base64 from "../../../../lib/base64-arraybuffer/index.js"; + +export class Base64 { + encodeUnpadded(buffer) { + const str = base64.encode(buffer); + const paddingIdx = str.indexOf("="); + if (paddingIdx !== -1) { + return str.substr(0, paddingIdx); + } else { + return str; + } + } + + encode(buffer) { + return base64.encode(buffer); + } + + decode(str) { + return base64.decode(str); + } +} diff --git a/src/platform/web/utils/Encoding.js b/src/platform/web/utils/Encoding.js new file mode 100644 index 00000000..5f7753e5 --- /dev/null +++ b/src/platform/web/utils/Encoding.js @@ -0,0 +1,27 @@ +/* +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. +*/ + +import {UTF8} from "../dom/UTF8.js"; +import {Base64} from "./Base64.js"; +import {Base58} from "./Base58.js"; + +export class Encoding { + constructor() { + this.utf8 = new UTF8(); + this.base64 = new Base64(); + this.base58 = new Base58(); + } +}