mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2025-01-22 18:21:39 +01:00
don't reuse existing transaction to read from 4S, as webcrypto terminates idb transactions
This commit is contained in:
parent
dd59f37dce
commit
762a91bd16
@ -78,7 +78,7 @@ export class SessionLoadViewModel extends ViewModel {
|
||||
this._ready(client);
|
||||
}
|
||||
if (loadError) {
|
||||
console.error("session load error", loadError);
|
||||
console.error("session load error", loadError.stack);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
|
@ -56,7 +56,7 @@ export class KeyBackupViewModel extends ViewModel<SegmentType, Options> {
|
||||
super(options);
|
||||
const onKeyBackupSet = (keyBackup: KeyBackup | undefined) => {
|
||||
if (keyBackup && !this._keyBackupSubscription) {
|
||||
this._keyBackupSubscription = this.track(this._session.keyBackup.disposableOn("change", () => {
|
||||
this._keyBackupSubscription = this.track(this._session.keyBackup.get().disposableOn("change", () => {
|
||||
this._onKeyBackupChange();
|
||||
}));
|
||||
} else if (!keyBackup && this._keyBackupSubscription) {
|
||||
|
@ -254,7 +254,7 @@ export class Session {
|
||||
}
|
||||
// TODO: stop cross-signing
|
||||
const key = await ssssKeyFromCredential(type, credential, this._storage, this._platform, this._olm);
|
||||
if (await this._tryLoadSecretStorage(key, undefined, log)) {
|
||||
if (await this._tryLoadSecretStorage(key, log)) {
|
||||
// only after having read a secret, write the key
|
||||
// as we only find out if it was good if the MAC verification succeeds
|
||||
await this._writeSSSSKey(key, log);
|
||||
@ -318,24 +318,19 @@ export class Session {
|
||||
// TODO: stop cross-signing
|
||||
}
|
||||
|
||||
_tryLoadSecretStorage(ssssKey, existingTxn, log) {
|
||||
_tryLoadSecretStorage(ssssKey, log) {
|
||||
return log.wrap("enable secret storage", async log => {
|
||||
const txn = existingTxn ?? await this._storage.readTxn([
|
||||
this._storage.storeNames.accountData,
|
||||
this._storage.storeNames.crossSigningKeys,
|
||||
this._storage.storeNames.userIdentities,
|
||||
]);
|
||||
const secretStorage = new SecretStorage({key: ssssKey, platform: this._platform});
|
||||
const isValid = await secretStorage.hasValidKeyForAnyAccountData(txn);
|
||||
const secretStorage = new SecretStorage({key: ssssKey, platform: this._platform, storage: this._storage});
|
||||
const isValid = await secretStorage.hasValidKeyForAnyAccountData();
|
||||
log.set("isValid", isValid);
|
||||
if (isValid) {
|
||||
await this._loadSecretStorageServices(secretStorage, txn, log);
|
||||
await this._loadSecretStorageServices(secretStorage, log);
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
}
|
||||
|
||||
async _loadSecretStorageServices(secretStorage, txn, log) {
|
||||
async _loadSecretStorageServices(secretStorage, log) {
|
||||
try {
|
||||
await log.wrap("enable key backup", async log => {
|
||||
const keyBackup = new KeyBackup(
|
||||
@ -345,7 +340,7 @@ export class Session {
|
||||
this._storage,
|
||||
this._platform,
|
||||
);
|
||||
if (await keyBackup.load(secretStorage, txn)) {
|
||||
if (await keyBackup.load(secretStorage, log)) {
|
||||
for (const room of this._rooms.values()) {
|
||||
if (room.isEncrypted) {
|
||||
room.enableKeyBackup(keyBackup);
|
||||
@ -370,7 +365,7 @@ export class Session {
|
||||
ownUserId: this.userId,
|
||||
e2eeAccount: this._e2eeAccount
|
||||
});
|
||||
if (await crossSigning.load(txn, log)) {
|
||||
if (await crossSigning.load(log)) {
|
||||
this._crossSigning.set(crossSigning);
|
||||
}
|
||||
});
|
||||
@ -497,15 +492,8 @@ export class Session {
|
||||
olmWorker: this._olmWorker,
|
||||
txn
|
||||
});
|
||||
if (this._e2eeAccount) {
|
||||
log.set("keys", this._e2eeAccount.identityKeys);
|
||||
this._setupEncryption();
|
||||
// try set up session backup if we stored the ssss key
|
||||
const ssssKey = await ssssReadKey(txn);
|
||||
if (ssssKey) {
|
||||
await this._tryLoadSecretStorage(ssssKey, txn, log);
|
||||
}
|
||||
}
|
||||
log.set("keys", this._e2eeAccount.identityKeys);
|
||||
this._setupEncryption();
|
||||
}
|
||||
const pendingEventsByRoomId = await this._getPendingEventsByRoom(txn);
|
||||
// load invites
|
||||
@ -530,6 +518,14 @@ export class Session {
|
||||
room.setInvite(invite);
|
||||
}
|
||||
}
|
||||
if (this._olm && this._e2eeAccount) {
|
||||
// try set up session backup and cross-signing if we stored the ssss key
|
||||
const ssssKey = await ssssReadKey(txn);
|
||||
if (ssssKey) {
|
||||
// this will close the txn above, so we do it last
|
||||
await this._tryLoadSecretStorage(ssssKey, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@ -570,15 +566,15 @@ export class Session {
|
||||
await log.wrap("SSSSKeyFromDehydratedDeviceKey", async log => {
|
||||
const ssssKey = await createSSSSKeyFromDehydratedDeviceKey(dehydratedDevice.key, this._storage, this._platform);
|
||||
if (ssssKey) {
|
||||
if (await this._tryLoadSecretStorage(ssssKey, undefined, log)) {
|
||||
if (await this._tryLoadSecretStorage(ssssKey, log)) {
|
||||
log.set("success", true);
|
||||
await this._writeSSSSKey(ssssKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this._keyBackup.get()?.start(log);
|
||||
this._crossSigning.get()?.start(log);
|
||||
await this._keyBackup.get()?.start(log);
|
||||
await this._crossSigning.get()?.start(log);
|
||||
|
||||
// restore unfinished operations, like sending out room keys
|
||||
const opsTxn = await this._storage.readWriteTxn([
|
||||
|
@ -163,9 +163,9 @@ export class DeviceTracker {
|
||||
}
|
||||
}
|
||||
|
||||
async getCrossSigningKeyForUser(userId: string, usage: KeyUsage, hsApi: HomeServerApi | undefined, existingTxn: Transaction | undefined, log: ILogItem): Promise<CrossSigningKey | undefined> {
|
||||
async getCrossSigningKeyForUser(userId: string, usage: KeyUsage, hsApi: HomeServerApi | undefined, log: ILogItem): Promise<CrossSigningKey | undefined> {
|
||||
return await log.wrap({l: "DeviceTracker.getCrossSigningKeyForUser", id: userId, usage}, async log => {
|
||||
const txn = existingTxn ?? await this._storage.readTxn([
|
||||
const txn = await this._storage.readTxn([
|
||||
this._storage.storeNames.userIdentities,
|
||||
this._storage.storeNames.crossSigningKeys,
|
||||
]);
|
||||
|
@ -107,9 +107,8 @@ export class KeyBackup extends EventEmitter<{change: never}> {
|
||||
return txn.inboundGroupSessions.markAllAsNotBackedUp();
|
||||
}
|
||||
|
||||
async load(secretStorage: SecretStorage, txn: Transaction) {
|
||||
// TODO: no exception here we should anticipate?
|
||||
const base64PrivateKey = await secretStorage.readSecret("m.megolm_backup.v1", txn);
|
||||
async load(secretStorage: SecretStorage, log: ILogItem) {
|
||||
const base64PrivateKey = await secretStorage.readSecret("m.megolm_backup.v1");
|
||||
if (base64PrivateKey) {
|
||||
this.privateKey = new Uint8Array(this.platform.encoding.base64.decode(base64PrivateKey));
|
||||
return true;
|
||||
|
@ -40,22 +40,22 @@ class DecryptionError extends Error {
|
||||
export class SecretStorage {
|
||||
private readonly _key: Key;
|
||||
private readonly _platform: Platform;
|
||||
private readonly _storage: Storage;
|
||||
|
||||
constructor({key, platform}: {key: Key, platform: Platform}) {
|
||||
constructor({key, platform, storage}: {key: Key, platform: Platform, storage: Storage}) {
|
||||
this._key = key;
|
||||
this._platform = platform;
|
||||
this._storage = storage;
|
||||
}
|
||||
|
||||
async hasValidKeyForAnyAccountData(txn: Transaction) {
|
||||
/** this method will auto-commit any indexeddb transaction because of its use of the webcrypto api */
|
||||
async hasValidKeyForAnyAccountData() {
|
||||
const txn = await this._storage.readTxn([
|
||||
this._storage.storeNames.accountData,
|
||||
]);
|
||||
const allAccountData = await txn.accountData.getAll();
|
||||
for (const accountData of allAccountData) {
|
||||
try {
|
||||
// TODO: fix this, using the webcrypto api closes the transaction
|
||||
if (accountData.type === "m.megolm_backup.v1") {
|
||||
return true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
const secret = await this._decryptAccountData(accountData);
|
||||
return true; // decryption succeeded
|
||||
} catch (err) {
|
||||
@ -69,7 +69,11 @@ export class SecretStorage {
|
||||
return false;
|
||||
}
|
||||
|
||||
async readSecret(name: string, txn: Transaction): Promise<string | undefined> {
|
||||
/** this method will auto-commit any indexeddb transaction because of its use of the webcrypto api */
|
||||
async readSecret(name: string): Promise<string | undefined> {
|
||||
const txn = await this._storage.readTxn([
|
||||
this._storage.storeNames.accountData,
|
||||
]);
|
||||
const accountData = await txn.accountData.get(name);
|
||||
if (!accountData) {
|
||||
return;
|
||||
|
@ -99,22 +99,22 @@ export class CrossSigning {
|
||||
this.e2eeAccount = options.e2eeAccount
|
||||
}
|
||||
|
||||
async load(txn: Transaction, log: ILogItem) {
|
||||
async load(log: ILogItem) {
|
||||
// try to verify the msk without accessing the network
|
||||
return await this.verifyMSKFrom4S(undefined, txn, log);
|
||||
return await this.verifyMSKFrom4S(false, log);
|
||||
}
|
||||
|
||||
async start(log: ILogItem) {
|
||||
if (!this.isMasterKeyTrusted) {
|
||||
// try to verify the msk _with_ access to the network
|
||||
return await this.verifyMSKFrom4S(this.hsApi, undefined, log);
|
||||
return await this.verifyMSKFrom4S(true, log);
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyMSKFrom4S(hsApi: HomeServerApi | undefined, txn: Transaction | undefined, log: ILogItem): Promise<boolean> {
|
||||
private async verifyMSKFrom4S(allowNetwork: boolean, log: ILogItem): Promise<boolean> {
|
||||
return await log.wrap("CrossSigning.verifyMSKFrom4S", async log => {
|
||||
// TODO: use errorboundary here
|
||||
const privateMasterKey = await this.getSigningKey(KeyUsage.Master, txn);
|
||||
const privateMasterKey = await this.getSigningKey(KeyUsage.Master);
|
||||
if (!privateMasterKey) {
|
||||
return false;
|
||||
}
|
||||
@ -125,7 +125,7 @@ export class CrossSigning {
|
||||
} finally {
|
||||
signing.free();
|
||||
}
|
||||
const publishedMasterKey = await this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.Master, hsApi, txn, log);
|
||||
const publishedMasterKey = await this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.Master, allowNetwork ? this.hsApi : undefined, log);
|
||||
if (!publishedMasterKey) {
|
||||
return false;
|
||||
}
|
||||
@ -210,11 +210,11 @@ export class CrossSigning {
|
||||
if (!this.isMasterKeyTrusted) {
|
||||
return UserTrust.OwnSetupError;
|
||||
}
|
||||
const ourMSK = await log.wrap("get our msk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.Master, this.hsApi, txn, log));
|
||||
const ourMSK = await log.wrap("get our msk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.Master, this.hsApi, undefined, log));
|
||||
if (!ourMSK) {
|
||||
return UserTrust.OwnSetupError;
|
||||
}
|
||||
const ourUSK = await log.wrap("get our usk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.UserSigning, this.hsApi, txn, log));
|
||||
const ourUSK = await log.wrap("get our usk", log => this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.UserSigning, this.hsApi, undefined, log));
|
||||
if (!ourUSK) {
|
||||
return UserTrust.OwnSetupError;
|
||||
}
|
||||
@ -222,7 +222,7 @@ export class CrossSigning {
|
||||
if (ourUSKVerification !== SignatureVerification.Valid) {
|
||||
return UserTrust.OwnSetupError;
|
||||
}
|
||||
const theirMSK = await log.wrap("get their msk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.Master, this.hsApi, txn, log));
|
||||
const theirMSK = await log.wrap("get their msk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.Master, this.hsApi, undefined, log));
|
||||
if (!theirMSK) {
|
||||
/* assume that when they don't have an MSK, they've never enabled cross-signing on their client
|
||||
(or it's not supported) rather than assuming a setup error on their side.
|
||||
@ -237,7 +237,7 @@ export class CrossSigning {
|
||||
return UserTrust.UserSignatureMismatch;
|
||||
}
|
||||
}
|
||||
const theirSSK = await log.wrap("get their ssk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.SelfSigning, this.hsApi, txn, log));
|
||||
const theirSSK = await log.wrap("get their ssk", log => this.deviceTracker.getCrossSigningKeyForUser(userId, KeyUsage.SelfSigning, this.hsApi, undefined, log));
|
||||
if (!theirSSK) {
|
||||
return UserTrust.UserSetupError;
|
||||
}
|
||||
@ -290,11 +290,8 @@ export class CrossSigning {
|
||||
return keyToSign;
|
||||
}
|
||||
|
||||
private async getSigningKey(usage: KeyUsage, existingTxn?: Transaction): Promise<Uint8Array | undefined> {
|
||||
const txn = existingTxn ?? await this.storage.readTxn([
|
||||
this.storage.storeNames.accountData,
|
||||
]);
|
||||
const seedStr = await this.secretStorage.readSecret(`m.cross_signing.${usage}`, txn);
|
||||
private async getSigningKey(usage: KeyUsage): Promise<Uint8Array | undefined> {
|
||||
const seedStr = await this.secretStorage.readSecret(`m.cross_signing.${usage}`);
|
||||
if (seedStr) {
|
||||
return new Uint8Array(this.platform.encoding.base64.decode(seedStr));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user