forked from mystiq/hydrogen-web
move ssssKey to e2ee prefix as well so it gets backed up too
This commit is contained in:
parent
77bd0d3f3c
commit
2ef7251079
4 changed files with 139 additions and 11 deletions
|
@ -17,6 +17,9 @@ limitations under the License.
|
||||||
import {KeyDescription, Key} from "./common.js";
|
import {KeyDescription, Key} from "./common.js";
|
||||||
import {keyFromPassphrase} from "./passphrase.js";
|
import {keyFromPassphrase} from "./passphrase.js";
|
||||||
import {keyFromRecoveryKey} from "./recoveryKey.js";
|
import {keyFromRecoveryKey} from "./recoveryKey.js";
|
||||||
|
import {SESSION_E2EE_KEY_PREFIX} from "../e2ee/common.js";
|
||||||
|
|
||||||
|
const SSSS_KEY = `${SESSION_E2EE_KEY_PREFIX}ssssKey`;
|
||||||
|
|
||||||
async function readDefaultKeyDescription(storage) {
|
async function readDefaultKeyDescription(storage) {
|
||||||
const txn = await storage.readTxn([
|
const txn = await storage.readTxn([
|
||||||
|
@ -35,11 +38,11 @@ async function readDefaultKeyDescription(storage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeKey(key, txn) {
|
export async function writeKey(key, txn) {
|
||||||
txn.session.set("ssssKey", {id: key.id, binaryKey: key.binaryKey});
|
txn.session.set(SSSS_KEY, {id: key.id, binaryKey: key.binaryKey});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readKey(txn) {
|
export async function readKey(txn) {
|
||||||
const keyData = await txn.session.get("ssssKey");
|
const keyData = await txn.session.get(SSSS_KEY);
|
||||||
if (!keyData) {
|
if (!keyData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {IDOMStorage} from "./types";
|
||||||
import {iterateCursor, NOT_DONE, reqAsPromise} from "./utils";
|
import {iterateCursor, NOT_DONE, reqAsPromise} from "./utils";
|
||||||
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
|
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
|
||||||
import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js";
|
import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js";
|
||||||
|
import {SESSION_E2EE_KEY_PREFIX} from "../../e2ee/common.js";
|
||||||
import {SummaryData} from "../../room/RoomSummary";
|
import {SummaryData} from "../../room/RoomSummary";
|
||||||
import {RoomMemberStore, MemberData} from "./stores/RoomMemberStore";
|
import {RoomMemberStore, MemberData} from "./stores/RoomMemberStore";
|
||||||
import {RoomStateEntry} from "./stores/RoomStateStore";
|
import {RoomStateEntry} from "./stores/RoomStateStore";
|
||||||
|
@ -25,7 +26,8 @@ export const schema: MigrationFunc[] = [
|
||||||
createArchivedRoomSummaryStore,
|
createArchivedRoomSummaryStore,
|
||||||
migrateOperationScopeIndex,
|
migrateOperationScopeIndex,
|
||||||
createTimelineRelationsStore,
|
createTimelineRelationsStore,
|
||||||
fixMissingRoomsInUserIdentities
|
fixMissingRoomsInUserIdentities,
|
||||||
|
changeSSSSKeyPrefix,
|
||||||
];
|
];
|
||||||
// TODO: how to deal with git merge conflicts of this array?
|
// TODO: how to deal with git merge conflicts of this array?
|
||||||
|
|
||||||
|
@ -204,3 +206,12 @@ async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransact
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v12 move ssssKey to e2ee:ssssKey so it will get backed up in the next step
|
||||||
|
async function changeSSSSKeyPrefix(db: IDBDatabase, txn: IDBTransaction) {
|
||||||
|
const session = txn.objectStore("session");
|
||||||
|
const ssssKey = await reqAsPromise(session.get("ssssKey"));
|
||||||
|
if (ssssKey) {
|
||||||
|
session.put({key: `${SESSION_E2EE_KEY_PREFIX}ssssKey`, value: ssssKey});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
import {Store} from "../Store";
|
import {Store} from "../Store";
|
||||||
import {IDOMStorage} from "../types";
|
import {IDOMStorage} from "../types";
|
||||||
import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js";
|
import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js";
|
||||||
|
import {LogItem} from "../../../../logging/LogItem.js";
|
||||||
|
import {parse, stringify} from "../../../../utils/typedJSON";
|
||||||
|
|
||||||
export interface SessionEntry {
|
export interface SessionEntry {
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -42,33 +44,38 @@ export class SessionStore {
|
||||||
// we backup to localStorage so when idb gets cleared for some reason, we don't lose our e2ee identity
|
// we backup to localStorage so when idb gets cleared for some reason, we don't lose our e2ee identity
|
||||||
try {
|
try {
|
||||||
const lsKey = `${this._sessionStore.databaseName}.session.${key}`;
|
const lsKey = `${this._sessionStore.databaseName}.session.${key}`;
|
||||||
const lsValue = JSON.stringify(value);
|
const lsValue = stringify(value);
|
||||||
this._localStorage.setItem(lsKey, lsValue);
|
this._localStorage.setItem(lsKey, lsValue);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("could not write to localStorage", err);
|
console.error("could not write to localStorage", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeToLocalStorage() {
|
writeE2EEIdentityToLocalStorage() {
|
||||||
this._sessionStore.iterateValues(undefined, (value: any, key: string) => {
|
this._sessionStore.iterateValues(undefined, (entry: SessionEntry, key: string) => {
|
||||||
if (key.startsWith(SESSION_E2EE_KEY_PREFIX)) {
|
if (key.startsWith(SESSION_E2EE_KEY_PREFIX)) {
|
||||||
this._writeKeyToLocalStorage(key, value);
|
this._writeKeyToLocalStorage(key, entry.value);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tryRestoreFromLocalStorage(): boolean {
|
async tryRestoreE2EEIdentityFromLocalStorage(log: LogItem): Promise<boolean> {
|
||||||
let success = false;
|
let success = false;
|
||||||
const lsPrefix = `${this._sessionStore.databaseName}.session.`;
|
const lsPrefix = `${this._sessionStore.databaseName}.session.`;
|
||||||
const prefix = `${lsPrefix}${SESSION_E2EE_KEY_PREFIX}`;
|
const prefix = `${lsPrefix}${SESSION_E2EE_KEY_PREFIX}`;
|
||||||
for(let i = 0; i < this._localStorage.length; i += 1) {
|
for(let i = 0; i < this._localStorage.length; i += 1) {
|
||||||
const lsKey = this._localStorage.key(i)!;
|
const lsKey = this._localStorage.key(i)!;
|
||||||
if (lsKey.startsWith(prefix)) {
|
if (lsKey.startsWith(prefix)) {
|
||||||
const value = JSON.parse(this._localStorage.getItem(lsKey)!);
|
const value = parse(this._localStorage.getItem(lsKey)!);
|
||||||
const key = lsKey.substr(lsPrefix.length);
|
const key = lsKey.substr(lsPrefix.length);
|
||||||
this._sessionStore.put({key, value});
|
// we check if we don't have this key already, as we don't want to override anything
|
||||||
success = true;
|
const hasKey = (await this._sessionStore.getKey(key)) === key;
|
||||||
|
log.set(key, !hasKey);
|
||||||
|
if (!hasKey) {
|
||||||
|
this._sessionStore.put({key, value});
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
|
|
107
src/utils/typedJSON.ts
Normal file
107
src/utils/typedJSON.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 function stringify(value: any): string {
|
||||||
|
return JSON.stringify(encodeValue(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(value: string): any {
|
||||||
|
return decodeValue(JSON.parse(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function encodeValue(value: any): any {
|
||||||
|
switch (typeof value) {
|
||||||
|
case "object": {
|
||||||
|
if (value === null || Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// TypedArray
|
||||||
|
if (value.byteLength) {
|
||||||
|
return {_type: value.constructor.name, value: Array.from(value)};
|
||||||
|
}
|
||||||
|
let newObj = {};
|
||||||
|
for (const prop in value) {
|
||||||
|
if (value.hasOwnProperty(prop)) {
|
||||||
|
newObj[prop] = encodeValue(value[prop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeValue(value: any): any {
|
||||||
|
switch (typeof value) {
|
||||||
|
case "object": {
|
||||||
|
if (value === null || Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value._type === "string") {
|
||||||
|
switch (value._type) {
|
||||||
|
case "Int8Array": return Int8Array.from(value.value);
|
||||||
|
case "Uint8Array": return Uint8Array.from(value.value);
|
||||||
|
case "Uint8ClampedArray": return Uint8ClampedArray.from(value.value);
|
||||||
|
case "Int16Array": return Int16Array.from(value.value);
|
||||||
|
case "Uint16Array": return Uint16Array.from(value.value);
|
||||||
|
case "Int32Array": return Int32Array.from(value.value);
|
||||||
|
case "Uint32Array": return Uint32Array.from(value.value);
|
||||||
|
case "Float32Array": return Float32Array.from(value.value);
|
||||||
|
case "Float64Array": return Float64Array.from(value.value);
|
||||||
|
case "BigInt64Array": return BigInt64Array.from(value.value);
|
||||||
|
case "BigUint64Array": return BigUint64Array.from(value.value);
|
||||||
|
default:
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let newObj = {};
|
||||||
|
for (const prop in value) {
|
||||||
|
if (value.hasOwnProperty(prop)) {
|
||||||
|
newObj[prop] = decodeValue(value[prop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tests() {
|
||||||
|
return {
|
||||||
|
"Uint8Array and primitives": assert => {
|
||||||
|
const value = {
|
||||||
|
foo: "bar",
|
||||||
|
bar: 5,
|
||||||
|
baz: false,
|
||||||
|
fuzz: new Uint8Array([3, 1, 2])
|
||||||
|
};
|
||||||
|
const serialized = stringify(value);
|
||||||
|
assert.strictEqual(typeof serialized, "string");
|
||||||
|
const deserialized = parse(serialized);
|
||||||
|
assert.strictEqual(deserialized.foo, "bar");
|
||||||
|
assert.strictEqual(deserialized.bar, 5);
|
||||||
|
assert.strictEqual(deserialized.baz, false);
|
||||||
|
assert(deserialized.fuzz instanceof Uint8Array);
|
||||||
|
assert.strictEqual(deserialized.fuzz.length, 3);
|
||||||
|
assert.strictEqual(deserialized.fuzz[0], 3);
|
||||||
|
assert.strictEqual(deserialized.fuzz[1], 1);
|
||||||
|
assert.strictEqual(deserialized.fuzz[2], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue