move base64/58 encoding into platform

fixes https://github.com/vector-im/hydrogen-web/issues/99
This commit is contained in:
Bruno Windels 2021-02-12 16:01:54 +01:00
parent 2bb7b3b598
commit bbab1e9ecc
13 changed files with 119 additions and 35 deletions

View file

@ -207,7 +207,12 @@ export class Session {
async _createSessionBackup(ssssKey, txn) { async _createSessionBackup(ssssKey, txn) {
const secretStorage = new SecretStorage({key: ssssKey, platform: this._platform}); 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) { if (this._sessionBackup) {
for (const room of this._rooms.values()) { for (const room of this._rooms.values()) {
if (room.isEncrypted) { if (room.isEncrypted) {

View file

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import base64 from "../../../lib/base64-arraybuffer/index.js";
/** /**
* Decrypt an attachment. * Decrypt an attachment.
* @param {ArrayBuffer} ciphertextBuffer The encrypted attachment data buffer. * @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. * @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. * @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 if (info === undefined || info.key === undefined || info.iv === undefined
|| info.hashes === undefined || info.hashes.sha256 === undefined) { || info.hashes === undefined || info.hashes.sha256 === undefined) {
throw new Error("Invalid info. Missing info.key, info.iv or info.hashes.sha256 key"); 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); var ivArray = base64.decode(info.iv);
// re-encode to not deal with padded vs unpadded // re-encode to not deal with padded vs unpadded
var expectedSha256base64 = base64.encode(base64.decode(info.hashes.sha256)); 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) { export async function encryptAttachment(platform, blob) {
const {crypto} = platform; const {crypto} = platform;
const {base64} = platform.encoding;
const iv = await crypto.aes.generateIV(); const iv = await crypto.aes.generateIV();
const key = await crypto.aes.generateKey("jwk", 256); const key = await crypto.aes.generateKey("jwk", 256);
const buffer = await blob.readAsBuffer(); const buffer = await blob.readAsBuffer();
@ -69,20 +70,10 @@ export async function encryptAttachment(platform, blob) {
info: { info: {
v: "v2", v: "v2",
key, key,
iv: encodeUnpaddedBase64(iv), iv: base64.encodeUnpadded(iv),
hashes: { 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;
}
}

View file

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import base64 from "../../../../lib/base64-arraybuffer/index.js";
export class SessionBackup { export class SessionBackup {
constructor({backupInfo, decryption, hsApi}) { constructor({backupInfo, decryption, hsApi}) {
this._backupInfo = backupInfo; this._backupInfo = backupInfo;
@ -41,10 +39,10 @@ export class SessionBackup {
this._decryption.free(); 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); const base64PrivateKey = await secretStorage.readSecret("m.megolm_backup.v1", txn);
if (base64PrivateKey) { 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 backupInfo = await hsApi.roomKeysVersion().response();
const expectedPubKey = backupInfo.auth_data.public_key; const expectedPubKey = backupInfo.auth_data.public_key;
const decryption = new olm.PkDecryption(); const decryption = new olm.PkDecryption();

View file

@ -55,7 +55,7 @@ export class MediaRepository {
async downloadEncryptedFile(fileEntry, cache = false) { async downloadEncryptedFile(fileEntry, cache = false) {
const url = this.mxcUrl(fileEntry.url); const url = this.mxcUrl(fileEntry.url);
const {body: encryptedBuffer} = await this._platform.request(url, {method: "GET", format: "buffer", cache}).response(); 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); return this._platform.createBlob(decryptedBuffer, fileEntry.mimetype);
} }

View file

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import base64 from "../../../lib/base64-arraybuffer/index.js";
export class SecretStorage { export class SecretStorage {
constructor({key, platform}) { constructor({key, platform}) {
this._key = key; this._key = key;
@ -40,17 +38,17 @@ export class SecretStorage {
} }
async _decryptAESSecret(type, encryptedData) { async _decryptAESSecret(type, encryptedData) {
const {base64, utf8} = this._platform.encoding;
// now derive the aes and mac key from the 4s key // now derive the aes and mac key from the 4s key
const hkdfKey = await this._platform.crypto.derive.hkdf( const hkdfKey = await this._platform.crypto.derive.hkdf(
this._key.binaryKey, this._key.binaryKey,
new Uint8Array(8).buffer, //zero salt new Uint8Array(8).buffer, //zero salt
this._platform.utf8.encode(type), // info utf8.encode(type), // info
"SHA-256", "SHA-256",
512 // 512 bits or 64 bytes 512 // 512 bits or 64 bytes
); );
const aesKey = hkdfKey.slice(0, 32); const aesKey = hkdfKey.slice(0, 32);
const hmacKey = hkdfKey.slice(32); const hmacKey = hkdfKey.slice(32);
const ciphertextBytes = base64.decode(encryptedData.ciphertext); const ciphertextBytes = base64.decode(encryptedData.ciphertext);
const isVerified = await this._platform.crypto.hmac.verify( const isVerified = await this._platform.crypto.hmac.verify(
@ -67,6 +65,6 @@ export class SecretStorage {
data: ciphertextBytes data: ciphertextBytes
}); });
return this._platform.utf8.decode(plaintextBytes); return utf8.decode(plaintextBytes);
} }
} }

View file

@ -56,7 +56,7 @@ export async function keyFromCredential(type, credential, storage, platform, olm
if (type === "phrase") { if (type === "phrase") {
key = await keyFromPassphrase(keyDescription, credential, platform); key = await keyFromPassphrase(keyDescription, credential, platform);
} else if (type === "key") { } else if (type === "key") {
key = keyFromRecoveryKey(olm, keyDescription, credential); key = keyFromRecoveryKey(keyDescription, credential, olm, platform);
} else { } else {
throw new Error(`Invalid type: ${type}`); throw new Error(`Invalid type: ${type}`);
} }

View file

@ -33,11 +33,12 @@ export async function keyFromPassphrase(keyDescription, passphrase, platform) {
if (passphraseParams.algorithm !== "m.pbkdf2") { if (passphraseParams.algorithm !== "m.pbkdf2") {
throw new Error(`Unsupported passphrase algorithm: ${passphraseParams.algorithm}`); throw new Error(`Unsupported passphrase algorithm: ${passphraseParams.algorithm}`);
} }
const {utf8} = platform.encoding;
const keyBits = await platform.crypto.derive.pbkdf2( const keyBits = await platform.crypto.derive.pbkdf2(
platform.utf8.encode(passphrase), utf8.encode(passphrase),
passphraseParams.iterations || DEFAULT_ITERATIONS, passphraseParams.iterations || DEFAULT_ITERATIONS,
// salt is just a random string, not encoded in any way // salt is just a random string, not encoded in any way
platform.utf8.encode(passphraseParams.salt), utf8.encode(passphraseParams.salt),
"SHA-512", "SHA-512",
passphraseParams.bits || DEFAULT_BITSIZE); passphraseParams.bits || DEFAULT_BITSIZE);
return new Key(keyDescription, keyBits); return new Key(keyDescription, keyBits);

View file

@ -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 See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import bs58 from "../../../lib/bs58/index.js";
import {Key} from "./common.js"; import {Key} from "./common.js";
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
@ -24,8 +23,8 @@ const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
* @param {string} recoveryKey * @param {string} recoveryKey
* @return {Key} * @return {Key}
*/ */
export function keyFromRecoveryKey(olm, keyDescription, recoveryKey) { export function keyFromRecoveryKey(keyDescription, recoveryKey, olm, platform) {
const result = bs58.decode(recoveryKey.replace(/ /g, '')); const result = platform.encoding.base58.decode(recoveryKey.replace(/ /g, ''));
let parity = 0; let parity = 0;
for (const b of result) { for (const b of result) {

View file

@ -19,7 +19,7 @@ import {xhrRequest} from "./dom/request/xhr.js";
import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js"; import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js";
import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js"; import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js";
import {SettingsStorage} from "./dom/SettingsStorage.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 {OlmWorker} from "../../matrix/e2ee/OlmWorker.js";
import {IDBLogger} from "../../logs/IDBLogger.js"; import {IDBLogger} from "../../logs/IDBLogger.js";
import {RootView} from "./ui/RootView.js"; import {RootView} from "./ui/RootView.js";
@ -85,8 +85,8 @@ export class Platform {
constructor(container, paths, cryptoExtras = null) { constructor(container, paths, cryptoExtras = null) {
this._paths = paths; this._paths = paths;
this._container = container; this._container = container;
this.utf8 = new UTF8();
this.logger = new IDBLogger("hydrogen_logs", this); this.logger = new IDBLogger("hydrogen_logs", this);
this.encoding = new Encoding();
this.clock = new Clock(); this.clock = new Clock();
this.history = new History(); this.history = new History();
this.onlineStatus = new OnlineStatus(); this.onlineStatus = new OnlineStatus();

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import base64 from "../../../../lib/base64-arraybuffer/index.js";
// turn IE11 result into promise // turn IE11 result into promise
function subtleCryptoResult(promiseOrOp, method) { function subtleCryptoResult(promiseOrOp, method) {
if (promiseOrOp instanceof Promise) { if (promiseOrOp instanceof Promise) {
@ -302,7 +304,6 @@ function rawKeyToJwk(key) {
}; };
} }
import base64 from "../../../../lib/base64-arraybuffer/index.js";
class AESLegacyCrypto { class AESLegacyCrypto {
constructor(aesjs, crypto) { constructor(aesjs, crypto) {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}