WIP for enabling session backup from dehydration key
This commit is contained in:
parent
6d9d8797fe
commit
567cdd5510
5 changed files with 59 additions and 14 deletions
|
@ -40,7 +40,8 @@ import {
|
||||||
keyFromCredential as ssssKeyFromCredential,
|
keyFromCredential as ssssKeyFromCredential,
|
||||||
readKey as ssssReadKey,
|
readKey as ssssReadKey,
|
||||||
writeKey as ssssWriteKey,
|
writeKey as ssssWriteKey,
|
||||||
removeKey as ssssRemoveKey
|
removeKey as ssssRemoveKey,
|
||||||
|
keyFromDehydratedDeviceKey as createSSSSKeyFromDehydratedDeviceKey
|
||||||
} from "./ssss/index.js";
|
} from "./ssss/index.js";
|
||||||
import {SecretStorage} from "./ssss/SecretStorage.js";
|
import {SecretStorage} from "./ssss/SecretStorage.js";
|
||||||
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue";
|
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue";
|
||||||
|
@ -205,6 +206,12 @@ export class Session {
|
||||||
this._storage.storeNames.accountData,
|
this._storage.storeNames.accountData,
|
||||||
]);
|
]);
|
||||||
await this._createSessionBackup(key, readTxn);
|
await this._createSessionBackup(key, readTxn);
|
||||||
|
this._writeSSSSKey(key);
|
||||||
|
this._hasSecretStorageKey.set(true);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _writeSSSSKey(key) {
|
||||||
// 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
|
||||||
const writeTxn = await this._storage.readWriteTxn([
|
const writeTxn = await this._storage.readWriteTxn([
|
||||||
|
@ -217,8 +224,6 @@ export class Session {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
await writeTxn.complete();
|
await writeTxn.complete();
|
||||||
this._hasSecretStorageKey.set(true);
|
|
||||||
return key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async disableSecretStorage() {
|
async disableSecretStorage() {
|
||||||
|
@ -419,7 +424,7 @@ export class Session {
|
||||||
* and useful to store so we can later tell what capabilities
|
* and useful to store so we can later tell what capabilities
|
||||||
* our homeserver has.
|
* our homeserver has.
|
||||||
*/
|
*/
|
||||||
async start(lastVersionResponse, log) {
|
async start(lastVersionResponse, dehydratedDevice, log) {
|
||||||
if (lastVersionResponse) {
|
if (lastVersionResponse) {
|
||||||
// store /versions response
|
// store /versions response
|
||||||
const txn = await this._storage.readWriteTxn([
|
const txn = await this._storage.readWriteTxn([
|
||||||
|
@ -431,6 +436,12 @@ export class Session {
|
||||||
}
|
}
|
||||||
// enable session backup, this requests the latest backup version
|
// enable session backup, this requests the latest backup version
|
||||||
if (!this._sessionBackup) {
|
if (!this._sessionBackup) {
|
||||||
|
if (dehydratedDevice) {
|
||||||
|
const ssssKey = await createSSSSKeyFromDehydratedDeviceKey(dehydratedDevice.key, this._storage);
|
||||||
|
if (ssssKey) {
|
||||||
|
this._writeSSSSKey(ssssKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
const txn = await this._storage.readTxn([
|
const txn = await this._storage.readTxn([
|
||||||
this._storage.storeNames.session,
|
this._storage.storeNames.session,
|
||||||
this._storage.storeNames.accountData,
|
this._storage.storeNames.accountData,
|
||||||
|
|
|
@ -259,7 +259,9 @@ export class SessionContainer {
|
||||||
this._requestScheduler.start();
|
this._requestScheduler.start();
|
||||||
this._sync.start();
|
this._sync.start();
|
||||||
this._sessionStartedByReconnector = true;
|
this._sessionStartedByReconnector = true;
|
||||||
await log.wrap("session start", log => this._session.start(this._reconnector.lastVersionsResponse, log));
|
const d = dehydratedDevice;
|
||||||
|
dehydratedDevice = undefined;
|
||||||
|
await log.wrap("session start", log => this._session.start(this._reconnector.lastVersionsResponse, d, log));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -278,8 +280,10 @@ export class SessionContainer {
|
||||||
if (this._isDisposed) {
|
if (this._isDisposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const d = dehydratedDevice;
|
||||||
|
dehydratedDevice = undefined;
|
||||||
// log as ref as we don't want to await it
|
// log as ref as we don't want to await it
|
||||||
await log.wrap("session start", log => this._session.start(lastVersionsResponse, log));
|
await log.wrap("session start", log => this._session.start(lastVersionsResponse, d, log));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ class EncryptedDehydratedDevice {
|
||||||
try {
|
try {
|
||||||
const pickledAccount = this._dehydratedDevice.device_data.account;
|
const pickledAccount = this._dehydratedDevice.device_data.account;
|
||||||
account.unpickle(key.binaryKey, pickledAccount);
|
account.unpickle(key.binaryKey, pickledAccount);
|
||||||
return new DehydratedDevice(this._dehydratedDevice, account, keyType, key);
|
return new DehydratedDevice(this._dehydratedDevice, account, key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
account.free();
|
account.free();
|
||||||
if (err.message === "OLM.BAD_ACCOUNT_KEY") {
|
if (err.message === "OLM.BAD_ACCOUNT_KEY") {
|
||||||
|
@ -78,10 +78,9 @@ class EncryptedDehydratedDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
class DehydratedDevice {
|
class DehydratedDevice {
|
||||||
constructor(dehydratedDevice, account, keyType, key) {
|
constructor(dehydratedDevice, account, key) {
|
||||||
this._dehydratedDevice = dehydratedDevice;
|
this._dehydratedDevice = dehydratedDevice;
|
||||||
this._account = account;
|
this._account = account;
|
||||||
this._keyType = keyType;
|
|
||||||
this._key = key;
|
this._key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +108,6 @@ class DehydratedDevice {
|
||||||
return this._key;
|
return this._key;
|
||||||
}
|
}
|
||||||
|
|
||||||
get keyType() {
|
|
||||||
return this._keyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._account?.free();
|
this._account?.free();
|
||||||
this._account = undefined;
|
this._account = undefined;
|
||||||
|
|
|
@ -31,6 +31,28 @@ export class KeyDescription {
|
||||||
get algorithm() {
|
get algorithm() {
|
||||||
return this._keyDescription?.algorithm;
|
return this._keyDescription?.algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCompatible(d) {
|
||||||
|
const kd = this._keyDescription;
|
||||||
|
const kdOther = d._keyDescription;
|
||||||
|
if (kd.algorithm === "m.secret_storage.v1.aes-hmac-sha2") {
|
||||||
|
if (kdOther.algorithm !== kd.algorithm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (kd.passphrase) {
|
||||||
|
if (!kdOther.passphrase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return kd.passphrase.algorithm === kdOther.passphrase.algorithm &&
|
||||||
|
kd.passphrase.iterations === kdOther.passphrase.iterations &&
|
||||||
|
kd.passphrase.salt === kdOther.passphrase.salt;
|
||||||
|
} else {
|
||||||
|
return !!kd.iv && kd.iv === kdOther.iv &&
|
||||||
|
!!kd.mac && kd.mac === kdOther.mac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Key {
|
export class Key {
|
||||||
|
@ -39,6 +61,10 @@ export class Key {
|
||||||
this._binaryKey = binaryKey;
|
this._binaryKey = binaryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withDescription(description) {
|
||||||
|
return new Key(description, this._binaryKey);
|
||||||
|
}
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
return this._keyDescription;
|
return this._keyDescription;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,9 @@ export async function readKey(txn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${keyData.id}`);
|
const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${keyData.id}`);
|
||||||
|
if (keyAccountData) {
|
||||||
return new Key(new KeyDescription(keyData.id, keyAccountData.content), keyData.binaryKey);
|
return new Key(new KeyDescription(keyData.id, keyAccountData.content), keyData.binaryKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,3 +79,10 @@ export async function keyFromCredentialAndDescription(type, credential, keyDescr
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function keyFromDehydratedDeviceKey(key, storage) {
|
||||||
|
const keyDescription = await readDefaultKeyDescription(storage);
|
||||||
|
if (key.description.isCompatible(keyDescription)) {
|
||||||
|
return key.withDescription(keyDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue