/*
* Famedly Matrix SDK
* Copyright (C) 2020 Famedly GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
import 'dart:async';
import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
void main() {
group('Bootstrap', tags: 'olm', () {
Logs().level = Level.error;
late Client client;
late Map oldSecret;
late String origKeyId;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();
});
test(
'setup',
() async {
Bootstrap? bootstrap;
bootstrap = client.encryption!.bootstrap(
onUpdate: (bootstrap) async {
if (bootstrap.state == BootstrapState.askWipeSsss) {
bootstrap.wipeSsss(true);
} else if (bootstrap.state == BootstrapState.askNewSsss) {
await bootstrap.newSsss('foxies');
} else if (bootstrap.state == BootstrapState.askWipeCrossSigning) {
await bootstrap.wipeCrossSigning(true);
} else if (bootstrap.state == BootstrapState.askSetupCrossSigning) {
await bootstrap.askSetupCrossSigning(
setupMasterKey: true,
setupSelfSigningKey: true,
setupUserSigningKey: true,
);
} else if (bootstrap.state ==
BootstrapState.askWipeOnlineKeyBackup) {
bootstrap.wipeOnlineKeyBackup(true);
} else if (bootstrap.state ==
BootstrapState.askSetupOnlineKeyBackup) {
await bootstrap.askSetupOnlineKeyBackup(true);
}
},
);
while (bootstrap.state != BootstrapState.done) {
await Future.delayed(Duration(milliseconds: 50));
}
final defaultKey = client.encryption!.ssss.open();
await defaultKey.unlock(passphrase: 'foxies');
// test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey =
await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = vod.PkSigning.fromSecretKey(privateKey);
final pubKey = keyObj.publicKey.toBase64();
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
}
await defaultKey.store('foxes', 'floof');
await Future.delayed(Duration(milliseconds: 50));
oldSecret =
json.decode(json.encode(client.accountData['foxes']!.content));
origKeyId = defaultKey.keyId;
},
timeout: Timeout(Duration(minutes: 2)),
);
test(
'change recovery passphrase',
() async {
Bootstrap? bootstrap;
bootstrap = client.encryption!.bootstrap(
onUpdate: (bootstrap) async {
if (bootstrap.state == BootstrapState.askWipeSsss) {
bootstrap.wipeSsss(false);
} else if (bootstrap.state == BootstrapState.askUseExistingSsss) {
bootstrap.useExistingSsss(false);
} else if (bootstrap.state == BootstrapState.askUnlockSsss) {
await bootstrap
.oldSsssKeys![client.encryption!.ssss.defaultKeyId]!
.unlock(passphrase: 'foxies');
bootstrap.unlockedSsss();
} else if (bootstrap.state == BootstrapState.askNewSsss) {
await bootstrap.newSsss('newfoxies');
} else if (bootstrap.state == BootstrapState.askWipeCrossSigning) {
await bootstrap.wipeCrossSigning(false);
} else if (bootstrap.state ==
BootstrapState.askWipeOnlineKeyBackup) {
bootstrap.wipeOnlineKeyBackup(false);
}
},
);
while (bootstrap.state != BootstrapState.done) {
await Future.delayed(Duration(milliseconds: 50));
}
final defaultKey = client.encryption!.ssss.open();
await defaultKey.unlock(passphrase: 'newfoxies');
// test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey =
await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = vod.PkSigning.fromSecretKey(privateKey);
final pubKey = keyObj.publicKey.toBase64();
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
}
expect(await defaultKey.getStored('foxes'), 'floof');
},
timeout: Timeout(Duration(minutes: 2)),
);
test(
'change passphrase with multiple keys',
() async {
await client.setAccountData(client.userID!, 'foxes', oldSecret);
await Future.delayed(Duration(milliseconds: 50));
Bootstrap? bootstrap;
bootstrap = client.encryption!.bootstrap(
onUpdate: (bootstrap) async {
if (bootstrap.state == BootstrapState.askWipeSsss) {
bootstrap.wipeSsss(false);
} else if (bootstrap.state == BootstrapState.askUseExistingSsss) {
bootstrap.useExistingSsss(false);
} else if (bootstrap.state == BootstrapState.askUnlockSsss) {
await bootstrap
.oldSsssKeys![client.encryption!.ssss.defaultKeyId]!
.unlock(passphrase: 'newfoxies');
await bootstrap.oldSsssKeys![origKeyId]!
.unlock(passphrase: 'foxies');
bootstrap.unlockedSsss();
} else if (bootstrap.state == BootstrapState.askNewSsss) {
await bootstrap.newSsss('supernewfoxies');
} else if (bootstrap.state == BootstrapState.askWipeCrossSigning) {
await bootstrap.wipeCrossSigning(false);
} else if (bootstrap.state ==
BootstrapState.askWipeOnlineKeyBackup) {
bootstrap.wipeOnlineKeyBackup(false);
}
},
);
while (bootstrap.state != BootstrapState.done) {
await Future.delayed(Duration(milliseconds: 50));
}
final defaultKey = client.encryption!.ssss.open();
await defaultKey.unlock(passphrase: 'supernewfoxies');
// test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey =
await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = vod.PkSigning.fromSecretKey(privateKey);
final pubKey = keyObj.publicKey.toBase64();
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
}
expect(await defaultKey.getStored('foxes'), 'floof');
},
timeout: Timeout(Duration(minutes: 2)),
);
test(
'setup new ssss',
() async {
client.accountData.clear();
Bootstrap? bootstrap;
bootstrap = client.encryption!.bootstrap(
onUpdate: (bootstrap) async {
if (bootstrap.state == BootstrapState.askNewSsss) {
await bootstrap.newSsss('thenewestfoxies');
} else if (bootstrap.state == BootstrapState.askSetupCrossSigning) {
await bootstrap.askSetupCrossSigning();
} else if (bootstrap.state ==
BootstrapState.askSetupOnlineKeyBackup) {
await bootstrap.askSetupOnlineKeyBackup(false);
}
},
);
while (bootstrap.state != BootstrapState.done) {
await Future.delayed(Duration(milliseconds: 50));
}
final defaultKey = client.encryption!.ssss.open();
await defaultKey.unlock(passphrase: 'thenewestfoxies');
},
timeout: Timeout(Duration(minutes: 2)),
);
test('bad ssss', () async {
client.accountData.clear();
await client.setAccountData(client.userID!, 'foxes', oldSecret);
await Future.delayed(Duration(milliseconds: 50));
var askedBadSsss = false;
Bootstrap? bootstrap;
bootstrap = client.encryption!.bootstrap(
onUpdate: (bootstrap) async {
if (bootstrap.state == BootstrapState.askWipeSsss) {
bootstrap.wipeSsss(false);
} else if (bootstrap.state == BootstrapState.askBadSsss) {
askedBadSsss = true;
bootstrap.ignoreBadSecrets(false);
}
},
);
while (bootstrap.state != BootstrapState.error) {
await Future.delayed(Duration(milliseconds: 50));
}
expect(askedBadSsss, true);
});
test('dispose client', () async {
await client.dispose(closeDatabase: true);
});
});
}