Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: validate account_data values instead of checking them in syncUpdates #1584

Merged
merged 1 commit into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions lib/encryption/ssss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,16 @@ class SSSS {
}()
.firstWhere((keyId) => getKey(keyId) == null);

final accountDataType = EventTypes.secretStorageKey(keyId);
final accountDataTypeKeyId = EventTypes.secretStorageKey(keyId);
// noooow we set the account data
final waitForAccountData = client.onSync.stream.firstWhere((syncUpdate) =>
syncUpdate.accountData != null &&
syncUpdate.accountData!
.any((accountData) => accountData.type == accountDataType));

await client.setAccountData(
client.userID!, accountDataType, content.toJson());
await waitForAccountData;
client.userID!, accountDataTypeKeyId, content.toJson());

while (!client.accountData.containsKey(accountDataTypeKeyId)) {
Logs().v('Waiting accountData to have $accountDataTypeKeyId');
await client.oneShotSync();
}

final key = open(keyId);
await key.setPrivateKey(privateKey);
Expand Down Expand Up @@ -327,7 +328,7 @@ class SSSS {
}
final enc = encryptedContent.tryGetMap<String, Object?>(keyId);
if (enc == null) {
throw Exception('Wrong / unknown key');
throw Exception('Wrong / unknown key: $type, $keyId');
}
final ciphertext = enc.tryGet<String>('ciphertext');
final iv = enc.tryGet<String>('iv');
Expand Down Expand Up @@ -750,6 +751,14 @@ class OpenSSSS {
throw Exception('SSSS not unlocked');
}
await ssss.store(type, secret, keyId, privateKey, add: add);
while (!ssss.client.accountData.containsKey(type) ||
!(ssss.client.accountData[type]!.content
.tryGetMap<String, Object?>('encrypted')!
.containsKey(keyId)) ||
await getStored(type) != secret) {
Logs().d('Wait for secret of $type to match in accountdata');
await ssss.client.oneShotSync();
}
}

Future<void> validateAndStripOtherKeys(String type, String secret) async {
Expand Down
58 changes: 21 additions & 37 deletions lib/encryption/utils/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class Bootstrap {

// cache the secret analyzing so that we don't drop stuff a different client sets during bootstrapping
Map<String, Set<String>>? _secretsCache;

/// returns ssss from accountdata, eg: m.megolm_backup.v1, or your m.cross_signing stuff
Map<String, Set<String>> analyzeSecrets() {
final secretsCache = _secretsCache;
if (secretsCache != null) {
Expand Down Expand Up @@ -292,12 +294,12 @@ class Bootstrap {
}
// alright, we re-encrypted all the secrets. We delete the dead weight only *after* we set our key to the default key
}
final updatedAccountData = client.onSync.stream.firstWhere((syncUpdate) =>
syncUpdate.accountData != null &&
syncUpdate.accountData!.any((accountData) =>
accountData.type == EventTypes.SecretStorageDefaultKey));
await encryption.ssss.setDefaultKeyId(newSsssKey!.keyId);
await updatedAccountData;
while (encryption.ssss.defaultKeyId != newSsssKey!.keyId) {
Logs().v(
'Waiting accountData to have the correct m.secret_storage.default_key');
await client.oneShotSync();
}
if (oldSsssKeys != null) {
for (final entry in secretMap!.entries) {
Logs().v('Validate and stripe other keys ${entry.key}...');
Expand Down Expand Up @@ -479,33 +481,23 @@ class Bootstrap {
));
Logs().v('Device signing keys have been uploaded.');
// aaaand set the SSSS secrets
final futures = <Future<void>>[];
if (masterKey != null) {
futures.add(
client.onSync.stream
.firstWhere((syncUpdate) =>
masterKey?.publicKey != null &&
client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key ==
masterKey?.publicKey)
.then((_) => Logs().v('New Master Key was created')),
);
while (!(masterKey.publicKey != null &&
client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key ==
masterKey.publicKey)) {
Logs().v('Waiting for master to be created');
await client.oneShotSync();
}
}
for (final entry in secretsToStore.entries) {
futures.add(
client.onSync.stream
.firstWhere((syncUpdate) =>
syncUpdate.accountData != null &&
syncUpdate.accountData!
.any((accountData) => accountData.type == entry.key))
.then((_) =>
Logs().v('New Key with type ${entry.key} was created')),
);
Logs().v('Store new SSSS key ${entry.key}...');
await newSsssKey?.store(entry.key, entry.value);
if (newSsssKey != null) {
final storeFutures = <Future<void>>[];
for (final entry in secretsToStore.entries) {
storeFutures.add(newSsssKey!.store(entry.key, entry.value));
}
Logs().v('Store new SSSS key entries...');
await Future.wait(storeFutures);
}
Logs().v(
'Wait for MasterKey and ${secretsToStore.entries.length} keys to be created');
await Future.wait<void>(futures);

final keysToSign = <SignableKey>[];
if (masterKey != null) {
if (client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key !=
Expand Down Expand Up @@ -581,14 +573,6 @@ class Bootstrap {
);
Logs().v('Store the secret...');
await newSsssKey?.store(megolmKey, base64.encode(privKey));
krille-chan marked this conversation as resolved.
Show resolved Hide resolved
Logs().v('Wait for secret to come down sync');

if (!await encryption.keyManager.isCached()) {
await client.onSync.stream.firstWhere((syncUpdate) =>
syncUpdate.accountData != null &&
syncUpdate.accountData!
.any((accountData) => accountData.type == megolmKey));
}

Logs().v(
'And finally set all megolm keys as needing to be uploaded again...');
Expand Down
9 changes: 6 additions & 3 deletions test/encryption/ssss_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ void main() {
await handle.unlock(recoveryKey: ssssKey);
expect(handle.isUnlocked, true);
FakeMatrixApi.calledEndpoints.clear();
await handle.store('best animal', 'foxies');
// alright, since we don't properly sync we will manually have to update
// account_data for this test

// OpenSSSS store waits for accountdata to be updated before returning
// but we can't update that before the below endpoint is not hit.
await handle.ssss
.store('best animal', 'foxies', handle.keyId, handle.privateKey!);

final content = FakeMatrixApi
.calledEndpoints[
'/client/v3/user/%40test%3AfakeServer.notExisting/account_data/best%20animal']!
Expand Down