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) {
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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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
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) {

View file

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

View file

@ -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) {

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