/* * Ansible inventory script used at Famedly GmbH for managing many hosts * Copyright (C) 2019, 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:convert'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:test/test.dart'; import 'fake_matrix_api.dart'; import 'fake_database.dart'; Map jsonDecode(dynamic payload) { if (payload is String) { try { return json.decode(payload); } catch (e) { return {}; } } if (payload is Map) return payload; return {}; } void main() { /// All Tests related to device keys test('fromJson', () async { var rawJson = { 'content': { 'action': 'request', 'body': { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'sender_key': 'RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU', 'session_id': 'X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ' }, 'request_id': '1495474790150.19', 'requesting_device_id': 'JLAFKJWSCS' }, 'type': 'm.room_key_request', 'sender': '@alice:example.com' }; var toDeviceEvent = ToDeviceEvent.fromJson(rawJson); expect(toDeviceEvent.content, rawJson['content']); expect(toDeviceEvent.sender, rawJson['sender']); expect(toDeviceEvent.type, rawJson['type']); expect( ToDeviceEventDecryptionError( exception: Exception('test'), stackTrace: null, toDeviceEvent: toDeviceEvent) .sender, rawJson['sender'], ); var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); var room = matrix.getRoomById('!726s6s6q:example.com'); if (matrix.encryptionEnabled) { await room.createOutboundGroupSession(); rawJson['content']['body']['session_id'] = room.inboundGroupSessions.keys.first; var roomKeyRequest = RoomKeyRequest.fromToDeviceEvent( ToDeviceEvent.fromJson(rawJson), matrix.keyManager, KeyManagerKeyShareRequest( room: room, sessionId: rawJson['content']['body']['session_id'], senderKey: rawJson['content']['body']['sender_key'], devices: [ matrix.userDeviceKeys[rawJson['sender']] .deviceKeys[rawJson['content']['requesting_device_id']] ], )); await roomKeyRequest.forwardKey(); } await matrix.dispose(closeDatabase: true); }); test('Create Request', () async { var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); if (!matrix.encryptionEnabled) { await matrix.dispose(closeDatabase: true); return; } final requestRoom = matrix.getRoomById('!726s6s6q:example.com'); await matrix.keyManager.request(requestRoom, 'sessionId', 'senderKey'); var foundEvent = false; for (var entry in FakeMatrixApi.calledEndpoints.entries) { final payload = jsonDecode(entry.value.first); if (entry.key.startsWith('/client/r0/sendToDevice/m.room_key_request') && (payload['messages'] is Map) && (payload['messages']['@alice:example.com'] is Map) && (payload['messages']['@alice:example.com']['*'] is Map)) { final content = payload['messages']['@alice:example.com']['*']; if (content['action'] == 'request' && content['body']['room_id'] == '!726s6s6q:example.com' && content['body']['sender_key'] == 'senderKey' && content['body']['session_id'] == 'sessionId') { foundEvent = true; break; } } } expect(foundEvent, true); await matrix.dispose(closeDatabase: true); }); final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; test('Reply To Request', () async { var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); if (!matrix.encryptionEnabled) { await matrix.dispose(closeDatabase: true); return; } matrix.setUserId('@alice:example.com'); // we need to pretend to be alice FakeMatrixApi.calledEndpoints.clear(); await matrix.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] .setBlocked(false, matrix); await matrix.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] .setVerified(true, matrix); await matrix.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] .startVerification(matrix); // test a successful share var event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key_request', content: { 'action': 'request', 'body': { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'sender_key': 'senderKey', 'session_id': validSessionId, }, 'request_id': 'request_1', 'requesting_device_id': 'OTHERDEVICE', }); await matrix.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), true); // test various fail scenarios // no body FakeMatrixApi.calledEndpoints.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key_request', content: { 'action': 'request', 'request_id': 'request_2', 'requesting_device_id': 'OTHERDEVICE', }); await matrix.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), false); // request by ourself FakeMatrixApi.calledEndpoints.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key_request', content: { 'action': 'request', 'body': { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'sender_key': 'senderKey', 'session_id': validSessionId, }, 'request_id': 'request_3', 'requesting_device_id': 'JLAFKJWSCS', }); await matrix.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), false); // device not found FakeMatrixApi.calledEndpoints.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key_request', content: { 'action': 'request', 'body': { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'sender_key': 'senderKey', 'session_id': validSessionId, }, 'request_id': 'request_4', 'requesting_device_id': 'blubb', }); await matrix.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), false); // unknown room FakeMatrixApi.calledEndpoints.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key_request', content: { 'action': 'request', 'body': { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!invalid:example.com', 'sender_key': 'senderKey', 'session_id': validSessionId, }, 'request_id': 'request_5', 'requesting_device_id': 'OTHERDEVICE', }); await matrix.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), false); // unknwon session FakeMatrixApi.calledEndpoints.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key_request', content: { 'action': 'request', 'body': { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'sender_key': 'senderKey', 'session_id': 'invalid', }, 'request_id': 'request_6', 'requesting_device_id': 'OTHERDEVICE', }); await matrix.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), false); FakeMatrixApi.calledEndpoints.clear(); await matrix.dispose(closeDatabase: true); }); test('Receive shared keys', () async { var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); if (!matrix.encryptionEnabled) { await matrix.dispose(closeDatabase: true); return; } final requestRoom = matrix.getRoomById('!726s6s6q:example.com'); await matrix.keyManager.request(requestRoom, validSessionId, 'senderKey'); final session = requestRoom.inboundGroupSessions[validSessionId]; final sessionKey = session.inboundGroupSession .export_session(session.inboundGroupSession.first_known_index()); requestRoom.inboundGroupSessions.clear(); var event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', content: { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'session_id': validSessionId, 'session_key': sessionKey, 'sender_key': 'senderKey', 'forwarding_curve25519_key_chain': [], }, encryptedContent: { 'sender_key': '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI', }); await matrix.keyManager.handleToDeviceEvent(event); expect(requestRoom.inboundGroupSessions.containsKey(validSessionId), true); // now test a few invalid scenarios // request not found requestRoom.inboundGroupSessions.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', content: { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'session_id': validSessionId, 'session_key': sessionKey, 'sender_key': 'senderKey', 'forwarding_curve25519_key_chain': [], }, encryptedContent: { 'sender_key': '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI', }); await matrix.keyManager.handleToDeviceEvent(event); expect(requestRoom.inboundGroupSessions.containsKey(validSessionId), false); // unknown device await matrix.keyManager.request(requestRoom, validSessionId, 'senderKey'); requestRoom.inboundGroupSessions.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', content: { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'session_id': validSessionId, 'session_key': sessionKey, 'sender_key': 'senderKey', 'forwarding_curve25519_key_chain': [], }, encryptedContent: { 'sender_key': 'invalid', }); await matrix.keyManager.handleToDeviceEvent(event); expect(requestRoom.inboundGroupSessions.containsKey(validSessionId), false); // no encrypted content await matrix.keyManager.request(requestRoom, validSessionId, 'senderKey'); requestRoom.inboundGroupSessions.clear(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', content: { 'algorithm': 'm.megolm.v1.aes-sha2', 'room_id': '!726s6s6q:example.com', 'session_id': validSessionId, 'session_key': sessionKey, 'sender_key': 'senderKey', 'forwarding_curve25519_key_chain': [], }); await matrix.keyManager.handleToDeviceEvent(event); expect(requestRoom.inboundGroupSessions.containsKey(validSessionId), false); await matrix.dispose(closeDatabase: true); }); }