From 9971e7377e38dbdff466783ffa9824d8922e70e7 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Sat, 30 May 2020 13:55:09 +0200 Subject: [PATCH] configurable verification methods --- lib/src/client.dart | 17 +++++++++--- lib/src/cross_signing.dart | 6 +++-- lib/src/key_manager.dart | 40 +++++++++++++++++++--------- lib/src/ssss.dart | 11 ++++---- lib/src/utils/device_keys_list.dart | 4 +-- lib/src/utils/key_verification.dart | 41 ++++++++++++++++++++++------- pubspec.lock | 8 +++--- 7 files changed, 87 insertions(+), 40 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index d464121..5222f60 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -90,16 +90,25 @@ class Client { SSSS ssss; CrossSigning crossSigning; + Set verificationMethods; + /// Create a client /// clientName = unique identifier of this client /// debug: Print debug output? /// database: The database instance to use /// enableE2eeRecovery: Enable additional logic to try to recover from bad e2ee sessions + /// verificationMethods: A set of all the verification methods this client can handle. Includes: + /// KeyVerificationMethod.numbers: Compare numbers. Most basic, should be supported + /// KeyVerificationMethod.emoji: Compare emojis Client(this.clientName, - {this.debug = false, this.database, this.enableE2eeRecovery = false}) { + {this.debug = false, + this.database, + this.enableE2eeRecovery = false, + this.verificationMethods}) { ssss = SSSS(this); keyManager = KeyManager(this); crossSigning = CrossSigning(this); + verificationMethods ??= {}; onLoginStateChanged.stream.listen((loginState) { print('LoginState: ${loginState.toString()}'); }); @@ -1209,7 +1218,8 @@ class Client { } void _handleToDeviceKeyVerificationRequest(ToDeviceEvent toDeviceEvent) { - if (!toDeviceEvent.type.startsWith('m.key.verification.')) { + if (!toDeviceEvent.type.startsWith('m.key.verification.') || + verificationMethods.isEmpty) { return; } // we have key verification going on! @@ -1243,7 +1253,8 @@ class Client { final type = event['type'].startsWith('m.key.verification.') ? event['type'] : event['content']['msgtype']; - if (!type.startsWith('m.key.verification.')) { + if (!type.startsWith('m.key.verification.') || + verificationMethods.isEmpty) { return; } if (type == 'm.key.verification.request') { diff --git a/lib/src/cross_signing.dart b/lib/src/cross_signing.dart index a884a37..779e03e 100644 --- a/lib/src/cross_signing.dart +++ b/lib/src/cross_signing.dart @@ -16,7 +16,8 @@ class CrossSigning { client.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async { final keyObj = olm.PkSigning(); try { - return keyObj.init_with_seed(base64.decode(secret)) == client.userDeviceKeys[client.userID].selfSigningKey.ed25519Key; + return keyObj.init_with_seed(base64.decode(secret)) == + client.userDeviceKeys[client.userID].selfSigningKey.ed25519Key; } catch (_) { return false; } finally { @@ -26,7 +27,8 @@ class CrossSigning { client.ssss.setValidator(USER_SIGNING_KEY, (String secret) async { final keyObj = olm.PkSigning(); try { - return keyObj.init_with_seed(base64.decode(secret)) == client.userDeviceKeys[client.userID].userSigningKey.ed25519Key; + return keyObj.init_with_seed(base64.decode(secret)) == + client.userDeviceKeys[client.userID].userSigningKey.ed25519Key; } catch (_) { return false; } finally { diff --git a/lib/src/key_manager.dart b/lib/src/key_manager.dart index 5ac075d..ef24233 100644 --- a/lib/src/key_manager.dart +++ b/lib/src/key_manager.dart @@ -20,7 +20,8 @@ class KeyManager { final keyObj = olm.PkDecryption(); try { final info = await getRoomKeysInfo(); - return keyObj.init_with_private_key(base64.decode(secret)) == info['auth_data']['public_key']; + return keyObj.init_with_private_key(base64.decode(secret)) == + info['auth_data']['public_key']; } catch (_) { return false; } finally { @@ -59,13 +60,16 @@ class KeyManager { try { backupPubKey = decryption.init_with_private_key(privateKey); - if (backupPubKey == null || !info.containsKey('auth_data') || !(info['auth_data'] is Map) || info['auth_data']['public_key'] != backupPubKey) { - + if (backupPubKey == null || + !info.containsKey('auth_data') || + !(info['auth_data'] is Map) || + info['auth_data']['public_key'] != backupPubKey) { return; } for (final roomEntries in payload['rooms'].entries) { final roomId = roomEntries.key; - if (!(roomEntries.value is Map) || !(roomEntries.value['sessions'] is Map)) { + if (!(roomEntries.value is Map) || + !(roomEntries.value['sessions'] is Map)) { continue; } for (final sessionEntries in roomEntries.value['sessions'].entries) { @@ -74,24 +78,35 @@ class KeyManager { if (!(rawEncryptedSession is Map)) { continue; } - final firstMessageIndex = rawEncryptedSession['first_message_index'] is int ? rawEncryptedSession['first_message_index'] : null; - final forwardedCount = rawEncryptedSession['forwarded_count'] is int ? rawEncryptedSession['forwarded_count'] : null; - final isVerified = rawEncryptedSession['is_verified'] is bool ? rawEncryptedSession['is_verified'] : null; + final firstMessageIndex = + rawEncryptedSession['first_message_index'] is int + ? rawEncryptedSession['first_message_index'] + : null; + final forwardedCount = rawEncryptedSession['forwarded_count'] is int + ? rawEncryptedSession['forwarded_count'] + : null; + final isVerified = rawEncryptedSession['is_verified'] is bool + ? rawEncryptedSession['is_verified'] + : null; final sessionData = rawEncryptedSession['session_data']; - if (firstMessageIndex == null || forwardedCount == null || isVerified == null || !(sessionData is Map)) { + if (firstMessageIndex == null || + forwardedCount == null || + isVerified == null || + !(sessionData is Map)) { continue; } - final senderKey = sessionData['sender_key']; Map decrypted; try { - decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], sessionData['mac'], sessionData['ciphertext'])); + decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], + sessionData['mac'], sessionData['ciphertext'])); } catch (err) { print('[LibOlm] Error decrypting room key: ' + err.toString()); } if (decrypted != null) { decrypted['session_id'] = sessionId; decrypted['room_id'] = roomId; - final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client); + final room = + client.getRoomById(roomId) ?? Room(id: roomId, client: client); room.setInboundGroupSession(sessionId, decrypted, forwarded: true); } } @@ -105,7 +120,8 @@ class KeyManager { final info = await getRoomKeysInfo(); final ret = await client.jsonRequest( type: HTTPType.GET, - action: '/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}', + action: + '/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}', ); await loadFromResponse({ 'rooms': { diff --git a/lib/src/ssss.dart b/lib/src/ssss.dart index 5eca4a4..a151ab4 100644 --- a/lib/src/ssss.dart +++ b/lib/src/ssss.dart @@ -72,9 +72,7 @@ class SSSS { final keys = deriveKeys(key, name); final cipher = base64.decode(data.ciphertext); final hmac = base64 - .encode(Hmac(sha256, keys.hmacKey) - .convert(cipher) - .bytes) + .encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes) .replaceAll(RegExp(r'=+$'), ''); if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) { throw 'Bad MAC'; @@ -322,7 +320,8 @@ class SSSS { return; // the secret wasn't a string....wut? } // let's validate if the secret is, well, valid - if (_validators.containsKey(request.type) && !(await _validators[request.type](secret))) { + if (_validators.containsKey(request.type) && + !(await _validators[request.type](secret))) { print('[SSSS] The received secret was invalid'); return; // didn't pass the validator } @@ -335,8 +334,8 @@ class SSSS { if (client.database != null) { final keyId = keyIdFromType(request.type); if (keyId != null) { - await client.database.storeSSSSCache( - client.id, request.type, keyId, secret); + await client.database + .storeSSSSCache(client.id, request.type, keyId, secret); } } } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 79e4c63..18b3327 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -29,8 +29,8 @@ class DeviceKeysList { return null; } - CrossSigningKey getCrossSigningKey(String type) => - crossSigningKeys.values.firstWhere((k) => k.usage.contains(type), orElse: () => null); + CrossSigningKey getCrossSigningKey(String type) => crossSigningKeys.values + .firstWhere((k) => k.usage.contains(type), orElse: () => null); CrossSigningKey get masterKey => getCrossSigningKey('master'); CrossSigningKey get selfSigningKey => getCrossSigningKey('self_signing'); diff --git a/lib/src/utils/key_verification.dart b/lib/src/utils/key_verification.dart index 1088ff4..195a6a6 100644 --- a/lib/src/utils/key_verification.dart +++ b/lib/src/utils/key_verification.dart @@ -52,6 +52,8 @@ enum KeyVerificationState { error } +enum KeyVerificationMethod { emoji, numbers } + List _intersect(List a, List b) { final res = []; for (final v in a) { @@ -82,8 +84,6 @@ List _bytesToInt(Uint8List bytes, int totalBits) { return ret; } -final VERIFICATION_METHODS = ['m.sas.v1']; - _KeyVerificationMethod _makeVerificationMethod( String type, KeyVerification request) { if (type == 'm.sas.v1') { @@ -134,9 +134,18 @@ class KeyVerification { : null); } + List get knownVerificationMethods { + final methods = []; + if (client.verificationMethods.contains(KeyVerificationMethod.numbers) || + client.verificationMethods.contains(KeyVerificationMethod.emoji)) { + methods.add('m.sas.v1'); + } + return methods; + } + Future sendStart() async { await send('m.key.verification.request', { - 'methods': VERIFICATION_METHODS, + 'methods': knownVerificationMethods, if (room == null) 'timestamp': DateTime.now().millisecondsSinceEpoch, }); startedVerification = true; @@ -178,7 +187,7 @@ class KeyVerification { } // verify it has a method we can use possibleMethods = - _intersect(VERIFICATION_METHODS, payload['methods']); + _intersect(knownVerificationMethods, payload['methods']); if (possibleMethods.isEmpty) { // reject it outright await cancel('m.unknown_method'); @@ -189,7 +198,7 @@ class KeyVerification { case 'm.key.verification.ready': _deviceId ??= payload['from_device']; possibleMethods = - _intersect(VERIFICATION_METHODS, payload['methods']); + _intersect(knownVerificationMethods, payload['methods']); if (possibleMethods.isEmpty) { // reject it outright await cancel('m.unknown_method'); @@ -233,7 +242,7 @@ class KeyVerification { if (!(await verifyLastStep(['m.key.verification.request', null]))) { return; // abort } - if (!VERIFICATION_METHODS.contains(payload['method'])) { + if (!knownVerificationMethods.contains(payload['method'])) { await cancel('m.unknown_method'); return; } @@ -525,7 +534,6 @@ abstract class _KeyVerificationMethod { const KNOWN_KEY_AGREEMENT_PROTOCOLS = ['curve25519-hkdf-sha256', 'curve25519']; const KNOWN_HASHES = ['sha256']; const KNOWN_MESSAGE_AUTHENTIFICATION_CODES = ['hkdf-hmac-sha256']; -const KNOWN_AUTHENTICATION_TYPES = ['emoji', 'decimal']; class _KeyVerificationMethodSas extends _KeyVerificationMethod { _KeyVerificationMethodSas({KeyVerification request}) @@ -549,6 +557,19 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { sas?.free(); } + List get knownAuthentificationTypes { + final types = []; + if (request.client.verificationMethods + .contains(KeyVerificationMethod.emoji)) { + types.add('emoji'); + } + if (request.client.verificationMethods + .contains(KeyVerificationMethod.numbers)) { + types.add('decimal'); + } + return types; + } + @override Future handlePayload(String type, Map payload) async { try { @@ -630,7 +651,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { 'key_agreement_protocols': KNOWN_KEY_AGREEMENT_PROTOCOLS, 'hashes': KNOWN_HASHES, 'message_authentication_codes': KNOWN_MESSAGE_AUTHENTIFICATION_CODES, - 'short_authentication_string': KNOWN_AUTHENTICATION_TYPES, + 'short_authentication_string': knownAuthentificationTypes, }; request.makePayload(payload); // We just store the canonical json in here for later verification @@ -662,7 +683,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } messageAuthenticationCode = possibleMessageAuthenticationCodes.first; final possibleAuthenticationTypes = _intersect( - KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']); + knownAuthentificationTypes, payload['short_authentication_string']); if (possibleAuthenticationTypes.isEmpty) { return false; } @@ -700,7 +721,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } messageAuthenticationCode = payload['message_authentication_code']; final possibleAuthenticationTypes = _intersect( - KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']); + knownAuthentificationTypes, payload['short_authentication_string']); if (possibleAuthenticationTypes.isEmpty) { return false; } diff --git a/pubspec.lock b/pubspec.lock index 1d8863b..232c25e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -410,11 +410,9 @@ packages: olm: dependency: "direct main" description: - path: "." - ref: "0c612a525511652a7760126b058de8c924fe8900" - resolved-ref: "0c612a525511652a7760126b058de8c924fe8900" - url: "https://gitlab.com/famedly/libraries/dart-olm.git" - source: git + path: "/home/sorunome/repos/famedly/dart-olm" + relative: false + source: path version: "1.1.1" package_config: dependency: transitive