diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index eff2fad3..7a815e28 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -24,15 +24,12 @@ import 'package:olm/olm.dart' as olm; import '../famedlysdk.dart'; import 'encryption.dart'; -const SELF_SIGNING_KEY = EventTypes.CrossSigningSelfSigning; -const USER_SIGNING_KEY = EventTypes.CrossSigningUserSigning; -const MASTER_KEY = 'm.cross_signing.master'; - class CrossSigning { final Encryption encryption; Client get client => encryption.client; CrossSigning(this.encryption) { - encryption.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async { + encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning, + (String secret) async { final keyObj = olm.PkSigning(); try { return keyObj.init_with_seed(base64.decode(secret)) == @@ -43,7 +40,8 @@ class CrossSigning { keyObj.free(); } }); - encryption.ssss.setValidator(USER_SIGNING_KEY, (String secret) async { + encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning, + (String secret) async { final keyObj = olm.PkSigning(); try { return keyObj.init_with_seed(base64.decode(secret)) == @@ -57,23 +55,27 @@ class CrossSigning { } bool get enabled => - client.accountData[SELF_SIGNING_KEY] != null && - client.accountData[USER_SIGNING_KEY] != null && - client.accountData[MASTER_KEY] != null; + client.accountData[EventTypes.CrossSigningSelfSigning] != null && + client.accountData[EventTypes.CrossSigningUserSigning] != null && + client.accountData[EventTypes.CrossSigningMasterKey] != null; Future isCached() async { if (!enabled) { return false; } - return (await encryption.ssss.getCached(SELF_SIGNING_KEY)) != null && - (await encryption.ssss.getCached(USER_SIGNING_KEY)) != null; + return (await encryption.ssss + .getCached(EventTypes.CrossSigningSelfSigning)) != + null && + (await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning)) != + null; } Future selfSign({String passphrase, String recoveryKey}) async { - final handle = encryption.ssss.open(MASTER_KEY); + final handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey); await handle.unlock(passphrase: passphrase, recoveryKey: recoveryKey); await handle.maybeCacheAll(); - final masterPrivateKey = base64.decode(await handle.getStored(MASTER_KEY)); + final masterPrivateKey = + base64.decode(await handle.getStored(EventTypes.CrossSigningMasterKey)); final keyObj = olm.PkSigning(); String masterPubkey; try { @@ -85,11 +87,11 @@ class CrossSigning { !client.userDeviceKeys.containsKey(client.userID) || !client.userDeviceKeys[client.userID].deviceKeys .containsKey(client.deviceID)) { - throw 'Master or user keys not found'; + throw Exception('Master or user keys not found'); } final masterKey = client.userDeviceKeys[client.userID].masterKey; if (masterKey == null || masterKey.ed25519Key != masterPubkey) { - throw 'Master pubkey key doesn\'t match'; + throw Exception('Master pubkey key doesn\'t match'); } // master key is valid, set it to verified await masterKey.setVerified(true, false); @@ -146,8 +148,9 @@ class CrossSigning { // we don't care about signing other cross-signing keys } else { // okay, we'll sign a device key with our self signing key - selfSigningKey ??= base64 - .decode(await encryption.ssss.getCached(SELF_SIGNING_KEY) ?? ''); + selfSigningKey ??= base64.decode(await encryption.ssss + .getCached(EventTypes.CrossSigningSelfSigning) ?? + ''); if (selfSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, selfSigningKey); addSignature(key, @@ -156,8 +159,9 @@ class CrossSigning { } } else if (key is CrossSigningKey && key.usage.contains('master')) { // we are signing someone elses master key - userSigningKey ??= base64 - .decode(await encryption.ssss.getCached(USER_SIGNING_KEY) ?? ''); + userSigningKey ??= base64.decode(await encryption.ssss + .getCached(EventTypes.CrossSigningUserSigning) ?? + ''); if (userSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, userSigningKey); addSignature(key, client.userDeviceKeys[client.userID].userSigningKey, diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index e28b8ee0..0207b504 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -46,6 +46,13 @@ class Encryption { String get fingerprintKey => olmManager.fingerprintKey; String get identityKey => olmManager.identityKey; + /// ToDeviceEventDecryptionError erros are coming here. + final StreamController + onToDeviceEventDecryptionError = StreamController.broadcast(); + + /// All other erros are coming here. + final StreamController onError = StreamController.broadcast(); + KeyManager keyManager; OlmManager olmManager; KeyVerificationManager keyVerificationManager; @@ -132,7 +139,21 @@ class Encryption { } Future decryptToDeviceEvent(ToDeviceEvent event) async { - return await olmManager.decryptToDeviceEvent(event); + try { + return await olmManager.decryptToDeviceEvent(event); + } catch (e, s) { + Logs.error( + '[LibOlm] Could not decrypt to device event from ${event.sender} with content: ${event.content}\n${e.toString()}', + s); + onToDeviceEventDecryptionError.add( + ToDeviceEventDecryptionError( + exception: e is Exception ? e : Exception(e), + stackTrace: s, + toDeviceEvent: event, + ), + ); + return event; + } } Event decryptRoomEventSync(String roomId, Event event) { diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index ddb51876..cd2c0ac2 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -53,6 +53,9 @@ Map _deepcopy(Map data) { return json.decode(json.encode(data)); } +/// SSSS: **S**ecure **S**ecret **S**torage and **S**haring +/// Read more about SSSS at: +/// https://matrix.org/docs/guides/implementing-more-advanced-e-2-ee-features-such-as-cross-signing#3-implementing-ssss class SSSS { final Encryption encryption; Client get client => encryption.client; @@ -113,7 +116,7 @@ class SSSS { .encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes) .replaceAll(RegExp(r'=+$'), ''); if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) { - throw 'Bad MAC'; + throw Exception('Bad MAC'); } final decipher = AES(Key(keys.aesKey), mode: AESMode.ctr, padding: null) .decrypt(Encrypted(cipher), iv: IV(base64.decode(data.iv))); @@ -128,26 +131,26 @@ class SSSS { parity ^= b; } if (parity != 0) { - throw 'Incorrect parity'; + throw Exception('Incorrect parity'); } for (var i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; i++) { if (result[i] != OLM_RECOVERY_KEY_PREFIX[i]) { - throw 'Incorrect prefix'; + throw Exception('Incorrect prefix'); } } if (result.length != OLM_RECOVERY_KEY_PREFIX.length + SSSS_KEY_LENGTH + 1) { - throw 'Incorrect length'; + throw Exception('Incorrect length'); } return Uint8List.fromList(result.sublist(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + SSSS_KEY_LENGTH)); } - static Uint8List keyFromPassphrase(String passphrase, _PassphraseInfo info) { + static Uint8List keyFromPassphrase(String passphrase, PassphraseInfo info) { if (info.algorithm != AlgorithmTypes.pbkdf2) { - throw 'Unknown algorithm'; + throw Exception('Unknown algorithm'); } final generator = PBKDF2(hashAlgorithm: sha512); return Uint8List.fromList(generator.generateKey(passphrase, info.salt, @@ -162,55 +165,45 @@ class SSSS { _cacheCallbacks[type] = callback; } - String get defaultKeyId { - final keyData = client.accountData[EventTypes.SecretStorageDefaultKey]; - if (keyData == null || !(keyData.content['key'] is String)) { - return null; - } - return keyData.content['key']; - } + String get defaultKeyId => client + .accountData[EventTypes.SecretStorageDefaultKey] + ?.parsedSecretStorageDefaultKeyContent + ?.key; Future setDefaultKeyId(String keyId) async { await client.setAccountData( - client.userID, EventTypes.SecretStorageDefaultKey, { - 'key': keyId, - }); + client.userID, + EventTypes.SecretStorageDefaultKey, + (SecretStorageDefaultKeyContent()..key = keyId).toJson(), + ); } - BasicEvent getKey(String keyId) { - return client.accountData[EventTypes.secretStorageKey(keyId)]; + SecretStorageKeyContent getKey(String keyId) { + return client.accountData[EventTypes.secretStorageKey(keyId)] + ?.parsedSecretStorageKeyContent; } - bool isKeyValid(String keyId) { - final keyData = getKey(keyId); - if (keyData == null) { - return false; - } - return keyData.content['algorithm'] == - AlgorithmTypes.secretStorageV1AesHmcSha2; - } + bool isKeyValid(String keyId) => + getKey(keyId)?.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2; + /// Creates a new secret storage key, optional encrypts it with [passphrase] + /// and stores it in the user's [accountData]. Future createKey([String passphrase]) async { Uint8List privateKey; - final content = {}; + final content = SecretStorageKeyContent(); if (passphrase != null) { // we need to derive the key off of the passphrase - content['passphrase'] = {}; - content['passphrase']['algorithm'] = AlgorithmTypes.pbkdf2; - content['passphrase']['salt'] = base64 + content.passphrase = PassphraseInfo(); + content.passphrase.algorithm = AlgorithmTypes.pbkdf2; + content.passphrase.salt = base64 .encode(SecureRandom(PBKDF2_SALT_LENGTH).bytes); // generate salt - content['passphrase']['iterations'] = PBKDF2_DEFAULT_ITERATIONS; - content['passphrase']['bits'] = SSSS_KEY_LENGTH * 8; + content.passphrase.iterations = PBKDF2_DEFAULT_ITERATIONS; + content.passphrase.bits = SSSS_KEY_LENGTH * 8; privateKey = await runInBackground( _keyFromPassphrase, _KeyFromPassphraseArgs( passphrase: passphrase, - info: _PassphraseInfo( - algorithm: content['passphrase']['algorithm'], - salt: content['passphrase']['salt'], - iterations: content['passphrase']['iterations'], - bits: content['passphrase']['bits'], - ), + info: content.passphrase, )); } else { // we need to just generate a new key from scratch @@ -218,9 +211,9 @@ class SSSS { } // now that we have the private key, let's create the iv and mac final encrypted = encryptAes(ZERO_STR, privateKey, ''); - content['iv'] = encrypted.iv; - content['mac'] = encrypted.mac; - content['algorithm'] = AlgorithmTypes.secretStorageV1AesHmcSha2; + content.iv = encrypted.iv; + content.mac = encrypted.mac; + content.algorithm = AlgorithmTypes.secretStorageV1AesHmcSha2; const KEYID_BYTE_LENGTH = 24; @@ -235,7 +228,8 @@ class SSSS { final waitForAccountData = client.onSync.stream.firstWhere((syncUpdate) => syncUpdate.accountData .any((accountData) => accountData.type == accountDataType)); - await client.setAccountData(client.userID, accountDataType, content); + await client.setAccountData( + client.userID, accountDataType, content.toJson()); await waitForAccountData; final key = open(keyId); @@ -243,19 +237,18 @@ class SSSS { return key; } - bool checkKey(Uint8List key, BasicEvent keyData) { - final info = keyData.content; - if (info['algorithm'] == AlgorithmTypes.secretStorageV1AesHmcSha2) { - if ((info['mac'] is String) && (info['iv'] is String)) { - final encrypted = encryptAes(ZERO_STR, key, '', info['iv']); - return info['mac'].replaceAll(RegExp(r'=+$'), '') == + bool checkKey(Uint8List key, SecretStorageKeyContent info) { + if (info.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2) { + if ((info.mac is String) && (info.iv is String)) { + final encrypted = encryptAes(ZERO_STR, key, '', info.iv); + return info.mac.replaceAll(RegExp(r'=+$'), '') == encrypted.mac.replaceAll(RegExp(r'=+$'), ''); } else { // no real information about the key, assume it is valid return true; } } else { - throw 'Unknown Algorithm'; + throw Exception('Unknown Algorithm'); } } @@ -287,13 +280,13 @@ class SSSS { Future getStored(String type, String keyId, Uint8List key) async { final secretInfo = client.accountData[type]; if (secretInfo == null) { - throw 'Not found'; + throw Exception('Not found'); } if (!(secretInfo.content['encrypted'] is Map)) { - throw 'Content is not encrypted'; + throw Exception('Content is not encrypted'); } if (!(secretInfo.content['encrypted'][keyId] is Map)) { - throw 'Wrong / unknown key'; + throw Exception('Wrong / unknown key'); } final enc = secretInfo.content['encrypted'][keyId]; final encryptInfo = _Encrypted( @@ -345,7 +338,7 @@ class SSSS { Future validateAndStripOtherKeys( String type, String secret, String keyId, Uint8List key) async { if (await getStored(type, keyId, key) != secret) { - throw 'Secrets do not match up!'; + throw Exception('Secrets do not match up!'); } // now remove all other keys final content = _deepcopy(client.accountData[type].content); @@ -354,7 +347,7 @@ class SSSS { content['encrypted'].removeWhere((k, v) => otherKeys.contains(k)); // yes, we are paranoid... if (await getStored(type, keyId, key) != secret) { - throw 'Secrets do not match up!'; + throw Exception('Secrets do not match up!'); } // store the thing in your account data await client.setAccountData(client.userID, type, content); @@ -561,15 +554,15 @@ class SSSS { OpenSSSS open([String identifier]) { identifier ??= defaultKeyId; if (identifier == null) { - throw 'Dont know what to open'; + throw Exception('Dont know what to open'); } final keyToOpen = keyIdFromType(identifier) ?? identifier; if (keyToOpen == null) { - throw 'No key found to open'; + throw Exception('No key found to open'); } final key = getKey(keyToOpen); if (key == null) { - throw 'Unknown key to open'; + throw Exception('Unknown key to open'); } return OpenSSSS(ssss: this, keyId: keyToOpen, keyData: key); } @@ -600,19 +593,10 @@ class _DerivedKeys { _DerivedKeys({this.aesKey, this.hmacKey}); } -class _PassphraseInfo { - final String algorithm; - final String salt; - final int iterations; - final int bits; - - _PassphraseInfo({this.algorithm, this.salt, this.iterations, this.bits}); -} - class OpenSSSS { final SSSS ssss; final String keyId; - final BasicEvent keyData; + final SecretStorageKeyContent keyData; OpenSSSS({this.ssss, this.keyId, this.keyData}); Uint8List privateKey; @@ -632,28 +616,23 @@ class OpenSSSS { _keyFromPassphrase, _KeyFromPassphraseArgs( passphrase: passphrase, - info: _PassphraseInfo( - algorithm: keyData.content['passphrase']['algorithm'], - salt: keyData.content['passphrase']['salt'], - iterations: keyData.content['passphrase']['iterations'], - bits: keyData.content['passphrase']['bits'], - ), + info: keyData.passphrase, )); } else if (recoveryKey != null) { privateKey = SSSS.decodeRecoveryKey(recoveryKey); } else { - throw 'Nothing specified'; + throw Exception('Nothing specified'); } // verify the validity of the key if (!ssss.checkKey(privateKey, keyData)) { privateKey = null; - throw 'Inalid key'; + throw Exception('Inalid key'); } } void setPrivateKey(Uint8List key) { if (!ssss.checkKey(key, keyData)) { - throw 'Invalid key'; + throw Exception('Invalid key'); } privateKey = key; } @@ -677,7 +656,7 @@ class OpenSSSS { class _KeyFromPassphraseArgs { final String passphrase; - final _PassphraseInfo info; + final PassphraseInfo info; _KeyFromPassphraseArgs({this.passphrase, this.info}); } diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart index c3e95a54..af5a7e91 100644 --- a/lib/encryption/utils/bootstrap.dart +++ b/lib/encryption/utils/bootstrap.dart @@ -24,7 +24,6 @@ import 'package:olm/olm.dart' as olm; import '../encryption.dart'; import '../ssss.dart'; -import '../cross_signing.dart'; import '../key_manager.dart'; import '../../famedlysdk.dart'; import '../../matrix_api/utils/logs.dart'; @@ -45,6 +44,7 @@ enum BootstrapState { done, // done } +/// Bootstrapping SSSS and cross-signing class Bootstrap { final Encryption encryption; Client get client => encryption.client; @@ -162,7 +162,7 @@ class Bootstrap { void wipeSsss(bool wipe) { if (state != BootstrapState.askWipeSsss) { - throw Exception('Wrong State'); + throw BootstrapBadStateException('Wrong State'); } if (wipe) { state = BootstrapState.askNewSsss; @@ -178,7 +178,7 @@ class Bootstrap { void useExistingSsss(bool use) { if (state != BootstrapState.askUseExistingSsss) { - throw Exception('Wrong State'); + throw BootstrapBadStateException('Wrong State'); } if (use) { newSsssKey = encryption.ssss.open(encryption.ssss.defaultKeyId); @@ -192,7 +192,7 @@ class Bootstrap { void ignoreBadSecrets(bool ignore) { if (state != BootstrapState.askBadSsss) { - throw Exception('Wrong State'); + throw BootstrapBadStateException('Wrong State'); } if (ignore) { migrateOldSsss(); @@ -221,14 +221,14 @@ class Bootstrap { void unlockedSsss() { if (state != BootstrapState.askUnlockSsss) { - throw Exception('Wrong State'); + throw BootstrapBadStateException('Wrong State'); } state = BootstrapState.askNewSsss; } Future newSsss([String passphrase]) async { if (state != BootstrapState.askNewSsss) { - throw Exception('Wrong State'); + throw BootstrapBadStateException('Wrong State'); } state = BootstrapState.loading; try { @@ -285,10 +285,10 @@ class Bootstrap { void openExistingSsss() { if (state != BootstrapState.openExistingSsss) { - throw 'Bad State'; + throw BootstrapBadStateException(); } if (!newSsssKey.isUnlocked) { - throw 'Key not unlocked'; + throw BootstrapBadStateException('Key not unlocked'); } checkCrossSigning(); } @@ -306,7 +306,7 @@ class Bootstrap { void wipeCrossSigning(bool wipe) { if (state != BootstrapState.askWipeCrossSigning) { - throw 'Bad State'; + throw BootstrapBadStateException(); } if (wipe) { state = BootstrapState.askSetupCrossSigning; @@ -320,7 +320,7 @@ class Bootstrap { bool setupSelfSigningKey = false, bool setupUserSigningKey = false}) async { if (state != BootstrapState.askSetupCrossSigning) { - throw 'Bad State'; + throw BootstrapBadStateException(); } if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) { checkOnlineKeyBackup(); @@ -345,16 +345,17 @@ class Bootstrap { }, }; masterKey = MatrixCrossSigningKey.fromJson(json); - secretsToStore[MASTER_KEY] = base64.encode(masterSigningKey); + secretsToStore[EventTypes.CrossSigningMasterKey] = + base64.encode(masterSigningKey); } finally { master.free(); } } else { - masterSigningKey = - base64.decode(await newSsssKey.getStored(MASTER_KEY) ?? ''); + masterSigningKey = base64.decode( + await newSsssKey.getStored(EventTypes.CrossSigningMasterKey) ?? ''); if (masterSigningKey == null || masterSigningKey.isEmpty) { // no master signing key :( - throw 'No master key'; + throw BootstrapBadStateException('No master key'); } final master = olm.PkSigning(); try { @@ -391,7 +392,8 @@ class Bootstrap { }, }; selfSigningKey = MatrixCrossSigningKey.fromJson(json); - secretsToStore[SELF_SIGNING_KEY] = base64.encode(selfSigningPriv); + secretsToStore[EventTypes.CrossSigningSelfSigning] = + base64.encode(selfSigningPriv); } finally { selfSigning.free(); } @@ -415,7 +417,8 @@ class Bootstrap { }, }; userSigningKey = MatrixCrossSigningKey.fromJson(json); - secretsToStore[USER_SIGNING_KEY] = base64.encode(userSigningPriv); + secretsToStore[EventTypes.CrossSigningUserSigning] = + base64.encode(userSigningPriv); } finally { userSigning.free(); } @@ -451,7 +454,8 @@ class Bootstrap { if (masterKey != null) { if (client.userDeviceKeys[client.userID].masterKey.ed25519Key != masterKey.publicKey) { - throw 'ERROR: New master key does not match up!'; + throw BootstrapBadStateException( + 'ERROR: New master key does not match up!'); } await client.userDeviceKeys[client.userID].masterKey .setVerified(true, false); @@ -484,7 +488,7 @@ class Bootstrap { void wipeOnlineKeyBackup(bool wipe) { if (state != BootstrapState.askWipeOnlineKeyBackup) { - throw 'Bad State'; + throw BootstrapBadStateException(); } if (wipe) { state = BootstrapState.askSetupOnlineKeyBackup; @@ -495,7 +499,7 @@ class Bootstrap { Future askSetupOnlineKeyBackup(bool setup) async { if (state != BootstrapState.askSetupOnlineKeyBackup) { - throw 'Bad State'; + throw BootstrapBadStateException(); } if (!setup) { state = BootstrapState.done; @@ -527,6 +531,7 @@ class Bootstrap { '[Bootstrapping] Error setting up online key backup: ' + e.toString(), s); state = BootstrapState.error; + encryption.onError.add(SdkError(exception: e, stackTrace: s)); return; } state = BootstrapState.done; @@ -541,3 +546,11 @@ class Bootstrap { } } } + +class BootstrapBadStateException implements Exception { + String cause; + BootstrapBadStateException([this.cause = 'Bad state']); + + @override + String toString() => 'BootstrapBadStateException: $cause'; +} diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 846caff6..78fbaae1 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -114,7 +114,7 @@ _KeyVerificationMethod _makeVerificationMethod( if (type == 'm.sas.v1') { return _KeyVerificationMethodSas(request: request); } - throw 'Unkown method type'; + throw Exception('Unkown method type'); } class KeyVerification { @@ -883,7 +883,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { : theirInfo + ourInfo) + request.transactionId; } else { - throw 'Unknown key agreement protocol'; + throw Exception('Unknown key agreement protocol'); } return sas.generate_bytes(sasInfo, bytes); } @@ -965,14 +965,14 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { olmutil.free(); return ret; } - throw 'Unknown hash method'; + throw Exception('Unknown hash method'); } String _calculateMac(String input, String info) { if (messageAuthenticationCode == 'hkdf-hmac-sha256') { return sas.calculate_mac(input, info); } else { - throw 'Unknown message authentification code'; + throw Exception('Unknown message authentification code'); } } } diff --git a/lib/matrix_api.dart b/lib/matrix_api.dart index cf24e429..f4fe3005 100644 --- a/lib/matrix_api.dart +++ b/lib/matrix_api.dart @@ -66,4 +66,6 @@ export 'matrix_api/model/upload_key_signatures_response.dart'; export 'matrix_api/model/user_search_result.dart'; export 'matrix_api/model/well_known_informations.dart'; export 'matrix_api/model/who_is_info.dart'; +export 'matrix_api/model/events/secret_storage_default_key_content.dart'; +export 'matrix_api/model/events/secret_storage_key_content.dart'; export 'matrix_api/model/events/tombstone_content.dart'; diff --git a/lib/matrix_api/model/event_types.dart b/lib/matrix_api/model/event_types.dart index c1b9c356..cbd4cd00 100644 --- a/lib/matrix_api/model/event_types.dart +++ b/lib/matrix_api/model/event_types.dart @@ -44,6 +44,7 @@ abstract class EventTypes { static const String CrossSigningSelfSigning = 'm.cross_signing.self_signing'; static const String CrossSigningUserSigning = 'm.cross_signing.user_signing'; + static const String CrossSigningMasterKey = 'm.cross_signing.master'; static const String MegolmBackup = 'm.megolm_backup.v1'; static const String SecretStorageDefaultKey = 'm.secret_storage.default_key'; static String secretStorageKey(String keyId) => 'm.secret_storage.key.$keyId'; diff --git a/lib/matrix_api/model/events/secret_storage_default_key_content.dart b/lib/matrix_api/model/events/secret_storage_default_key_content.dart new file mode 100644 index 00000000..b7c561f6 --- /dev/null +++ b/lib/matrix_api/model/events/secret_storage_default_key_content.dart @@ -0,0 +1,22 @@ +import 'package:famedlysdk/matrix_api/model/basic_event.dart'; +import '../../utils/try_get_map_extension.dart'; + +extension SecretStorageDefaultKeyContentBasicEventExtension on BasicEvent { + SecretStorageDefaultKeyContent get parsedSecretStorageDefaultKeyContent => + SecretStorageDefaultKeyContent.fromJson(content); +} + +class SecretStorageDefaultKeyContent { + String key; + + SecretStorageDefaultKeyContent(); + + SecretStorageDefaultKeyContent.fromJson(Map json) + : key = json.tryGet('key'); + + Map toJson() { + final data = {}; + if (key != null) data['key'] = key; + return data; + } +} diff --git a/lib/matrix_api/model/events/secret_storage_key_content.dart b/lib/matrix_api/model/events/secret_storage_key_content.dart new file mode 100644 index 00000000..0af9532f --- /dev/null +++ b/lib/matrix_api/model/events/secret_storage_key_content.dart @@ -0,0 +1,57 @@ +import 'package:famedlysdk/matrix_api/model/basic_event.dart'; +import '../../utils/try_get_map_extension.dart'; + +extension SecretStorageKeyContentBasicEventExtension on BasicEvent { + SecretStorageKeyContent get parsedSecretStorageKeyContent => + SecretStorageKeyContent.fromJson(content); +} + +class SecretStorageKeyContent { + PassphraseInfo passphrase; + String iv; + String mac; + String algorithm; + + SecretStorageKeyContent(); + + SecretStorageKeyContent.fromJson(Map json) + : passphrase = json['passphrase'] is Map + ? PassphraseInfo.fromJson(json['passphrase']) + : null, + iv = json.tryGet('iv'), + mac = json.tryGet('mac'), + algorithm = json.tryGet('algorithm'); + + Map toJson() { + final data = {}; + if (passphrase != null) data['passphrase'] = passphrase.toJson(); + if (iv != null) data['iv'] = iv; + if (mac != null) data['mac'] = mac; + if (algorithm != null) data['algorithm'] = algorithm; + return data; + } +} + +class PassphraseInfo { + String algorithm; + String salt; + int iterations; + int bits; + + PassphraseInfo(); + + PassphraseInfo.fromJson(Map json) + : algorithm = json.tryGet('algorithm'), + salt = json.tryGet('salt'), + iterations = json.tryGet('iterations'), + bits = json.tryGet('bits'); + + Map toJson() { + final data = {}; + if (algorithm != null) data['algorithm'] = algorithm; + if (salt != null) data['salt'] = salt; + if (iterations != null) data['iterations'] = iterations; + if (bits != null) data['bits'] = bits; + return data; + } +} diff --git a/lib/src/client.dart b/lib/src/client.dart index 5a1c0065..8bd4292d 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -325,7 +325,7 @@ class Client extends MatrixApi { if (response.accessToken == null || response.deviceId == null || response.userId == null) { - throw 'Registered but token, device ID or user ID is null.'; + throw Exception('Registered but token, device ID or user ID is null.'); } await init( newToken: response.accessToken, @@ -410,6 +410,7 @@ class Client extends MatrixApi { } } + /// Run any request and react on user interactive authentication flows here. Future uiaRequestBackground( Future Function(Map auth) request) { final completer = Completer(); @@ -573,6 +574,7 @@ class Client extends MatrixApi { final StreamController onSyncError = StreamController.broadcast(); /// Synchronization erros are coming here. + @Deprecated('Please use encryption.onToDeviceEventDecryptionError instead') final StreamController onOlmError = StreamController.broadcast(); @@ -948,22 +950,7 @@ class Client extends MatrixApi { for (var i = 0; i < events.length; i++) { var toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson()); if (toDeviceEvent.type == EventTypes.Encrypted && encryptionEnabled) { - try { - toDeviceEvent = await encryption.decryptToDeviceEvent(toDeviceEvent); - } catch (e, s) { - Logs.error( - '[LibOlm] Could not decrypt to device event from ${toDeviceEvent.sender} with content: ${toDeviceEvent.content}\n${e.toString()}', - s); - - onOlmError.add( - ToDeviceEventDecryptionError( - exception: e is Exception ? e : Exception(e), - stackTrace: s, - toDeviceEvent: toDeviceEvent, - ), - ); - toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson()); - } + toDeviceEvent = await encryption.decryptToDeviceEvent(toDeviceEvent); } if (encryptionEnabled) { await encryption.handleToDeviceEvent(toDeviceEvent); diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index b4d3693a..93ab0d49 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -59,7 +59,7 @@ class DeviceKeysList { final roomId = await User(userId, room: Room(client: client)).startDirectChat(); if (roomId == null) { - throw 'Unable to start new room'; + throw Exception('Unable to start new room'); } final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client); diff --git a/lib/src/utils/uia_request.dart b/lib/src/utils/uia_request.dart index 82fb3975..9a7011f3 100644 --- a/lib/src/utils/uia_request.dart +++ b/lib/src/utils/uia_request.dart @@ -18,6 +18,7 @@ import '../../famedlysdk.dart'; +/// Wrapper to handle User interactive authentication requests class UiaRequest { void Function() onUpdate; void Function() onDone; diff --git a/test/uia_test.dart b/test/uia_test.dart index 59f7bc72..ab3b7b74 100644 --- a/test/uia_test.dart +++ b/test/uia_test.dart @@ -79,7 +79,7 @@ void main() { var finished = false; final request = UiaRequest( request: (auth) async { - throw 'nope'; + throw Exception('nope'); }, onUpdate: () => updated = true, onDone: () => finished = true,