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