/* * 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:matrix/matrix.dart'; import 'package:matrix/encryption.dart'; import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; import '../fake_client.dart'; void main() { group('Bootstrap', () { Logs().level = Level.error; var olmEnabled = true; late Client client; late Map oldSecret; late String origKeyId; test('setupClient', () async { client = await getClient(); await client.abortSync(); }); test('setup', () async { try { await olm.init(); olm.get_library_version(); } catch (e) { olmEnabled = false; Logs().w('[LibOlm] Failed to load LibOlm', e); } Logs().i('[LibOlm] Enabled: $olmEnabled'); if (!olmEnabled) return; Bootstrap? bootstrap; bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); } if (bootstrap.state == BootstrapState.askWipeSsss) { bootstrap.wipeSsss(true); } else if (bootstrap.state == BootstrapState.askNewSsss) { await bootstrap.newSsss('foxies'); } else if (bootstrap.state == BootstrapState.askWipeCrossSigning) { 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 = base64 .decode(await defaultKey.getStored('m.cross_signing.$keyType')); final keyObj = olm.PkSigning(); try { final pubKey = keyObj.init_with_seed(privateKey); expect( pubKey, client.userDeviceKeys[client.userID] ?.getCrossSigningKey(keyType) ?.publicKey); } finally { keyObj.free(); } } 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 { if (!olmEnabled) return; Bootstrap? bootstrap; bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); } 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) { 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 = base64 .decode(await defaultKey.getStored('m.cross_signing.$keyType')); final keyObj = olm.PkSigning(); try { final pubKey = keyObj.init_with_seed(privateKey); expect( pubKey, client.userDeviceKeys[client.userID] ?.getCrossSigningKey(keyType) ?.publicKey); } finally { keyObj.free(); } } expect(await defaultKey.getStored('foxes'), 'floof'); }, timeout: Timeout(Duration(minutes: 2))); test('change passphrase with multiple keys', () async { if (!olmEnabled) return; await client.setAccountData(client.userID!, 'foxes', oldSecret); await Future.delayed(Duration(milliseconds: 50)); Bootstrap? bootstrap; bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); } 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) { 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 = base64 .decode(await defaultKey.getStored('m.cross_signing.$keyType')); final keyObj = olm.PkSigning(); try { final pubKey = keyObj.init_with_seed(privateKey); expect( pubKey, client.userDeviceKeys[client.userID] ?.getCrossSigningKey(keyType) ?.publicKey); } finally { keyObj.free(); } } expect(await defaultKey.getStored('foxes'), 'floof'); }, timeout: Timeout(Duration(minutes: 2))); test('setup new ssss', () async { if (!olmEnabled) return; client.accountData.clear(); Bootstrap? bootstrap; bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); } 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 { if (!olmEnabled) return; 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: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); } 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 { if (!olmEnabled) return; await client.dispose(closeDatabase: true); }); }); }