Merge branch 'soru/decrypt-more-events' into 'master'

Allow requesting and updating of session keys with lower index and lower forwarded chain

Closes #76

See merge request famedly/famedlysdk!341
This commit is contained in:
Christian Pauly 2020-06-10 08:55:43 +00:00
commit d53003c0bd
7 changed files with 175 additions and 16 deletions

View file

@ -87,6 +87,7 @@ class Encryption {
if (event.type != EventTypes.Encrypted || if (event.type != EventTypes.Encrypted ||
event.content['ciphertext'] == null) return event; event.content['ciphertext'] == null) return event;
Map<String, dynamic> decryptedPayload; Map<String, dynamic> decryptedPayload;
var canRequestSession = false;
try { try {
if (event.content['algorithm'] != 'm.megolm.v1.aes-sha2') { if (event.content['algorithm'] != 'm.megolm.v1.aes-sha2') {
throw (DecryptError.UNKNOWN_ALGORITHM); throw (DecryptError.UNKNOWN_ALGORITHM);
@ -96,6 +97,7 @@ class Encryption {
final inboundGroupSession = final inboundGroupSession =
keyManager.getInboundGroupSession(roomId, sessionId, senderKey); keyManager.getInboundGroupSession(roomId, sessionId, senderKey);
if (inboundGroupSession == null) { if (inboundGroupSession == null) {
canRequestSession = true;
throw (DecryptError.UNKNOWN_SESSION); throw (DecryptError.UNKNOWN_SESSION);
} }
final decryptResult = inboundGroupSession.inboundGroupSession final decryptResult = inboundGroupSession.inboundGroupSession
@ -122,6 +124,8 @@ class Encryption {
roomId, roomId,
sessionId); sessionId);
} }
// decrypt errors here may mean we have a bad session key - others might have a better one
canRequestSession = true;
decryptedPayload = json.decode(decryptResult.plaintext); decryptedPayload = json.decode(decryptResult.plaintext);
} catch (exception) { } catch (exception) {
// alright, if this was actually by our own outbound group session, we might as well clear it // alright, if this was actually by our own outbound group session, we might as well clear it
@ -134,13 +138,14 @@ class Encryption {
event.content['session_id']) { event.content['session_id']) {
keyManager.clearOutboundGroupSession(roomId, wipe: true); keyManager.clearOutboundGroupSession(roomId, wipe: true);
} }
if (exception.toString() == DecryptError.UNKNOWN_SESSION) { if (canRequestSession) {
decryptedPayload = { decryptedPayload = {
'content': event.content, 'content': event.content,
'type': EventTypes.Encrypted, 'type': EventTypes.Encrypted,
}; };
decryptedPayload['content']['body'] = exception.toString(); decryptedPayload['content']['body'] = exception.toString();
decryptedPayload['content']['msgtype'] = 'm.bad.encrypted'; decryptedPayload['content']['msgtype'] = 'm.bad.encrypted';
decryptedPayload['content']['can_request_session'] = true;
} else { } else {
decryptedPayload = { decryptedPayload = {
'content': <String, dynamic>{ 'content': <String, dynamic>{

View file

@ -49,9 +49,6 @@ class KeyManager {
{bool forwarded = false}) { {bool forwarded = false}) {
final oldSession = final oldSession =
getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false); getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false);
if (oldSession != null) {
return;
}
if (content['algorithm'] != 'm.megolm.v1.aes-sha2') { if (content['algorithm'] != 'm.megolm.v1.aes-sha2') {
return; return;
} }
@ -69,15 +66,31 @@ class KeyManager {
'[LibOlm] Could not create new InboundGroupSession: ' + e.toString()); '[LibOlm] Could not create new InboundGroupSession: ' + e.toString());
return; return;
} }
if (!_inboundGroupSessions.containsKey(roomId)) { final newSession = SessionKey(
_inboundGroupSessions[roomId] = <String, SessionKey>{};
}
_inboundGroupSessions[roomId][sessionId] = SessionKey(
content: content, content: content,
inboundGroupSession: inboundGroupSession, inboundGroupSession: inboundGroupSession,
indexes: {}, indexes: {},
key: client.userID, key: client.userID,
); );
final oldFirstIndex =
oldSession?.inboundGroupSession?.first_known_index() ?? 0;
final newFirstIndex = newSession.inboundGroupSession.first_known_index();
if (oldSession == null ||
newFirstIndex < oldFirstIndex ||
(oldFirstIndex == newFirstIndex &&
newSession.forwardingCurve25519KeyChain.length <
oldSession.forwardingCurve25519KeyChain.length)) {
// use new session
oldSession?.dispose();
} else {
// we are gonna keep our old session
newSession.dispose();
return;
}
if (!_inboundGroupSessions.containsKey(roomId)) {
_inboundGroupSessions[roomId] = <String, SessionKey>{};
}
_inboundGroupSessions[roomId][sessionId] = newSession;
client.database?.storeInboundGroupSession( client.database?.storeInboundGroupSession(
client.id, client.id,
roomId, roomId,

View file

@ -341,8 +341,8 @@ class Event extends MatrixEvent {
Future<void> requestKey() async { Future<void> requestKey() async {
if (type != EventTypes.Encrypted || if (type != EventTypes.Encrypted ||
messageType != MessageTypes.BadEncrypted || messageType != MessageTypes.BadEncrypted ||
content['body'] != DecryptError.UNKNOWN_SESSION) { content['can_request_session'] != true) {
throw ('Session key not unknown'); throw ('Session key not requestable');
} }
await room.requestSessionKey(content['session_id'], content['sender_key']); await room.requestSessionKey(content['session_id'], content['sender_key']);
return; return;

View file

@ -19,7 +19,6 @@
import 'dart:async'; import 'dart:async';
import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/client.dart';
import 'package:famedlysdk/src/event.dart'; import 'package:famedlysdk/src/event.dart';
@ -996,7 +995,7 @@ class Room {
await client.database.transaction(() async { await client.database.transaction(() async {
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
if (events[i].type == EventTypes.Encrypted && if (events[i].type == EventTypes.Encrypted &&
events[i].content['body'] == DecryptError.UNKNOWN_SESSION) { events[i].content['can_request_session'] == true) {
events[i] = await client.encryption events[i] = await client.encryption
.decryptRoomEvent(id, events[i], store: true); .decryptRoomEvent(id, events[i], store: true);
} }

View file

@ -19,7 +19,6 @@
import 'dart:async'; import 'dart:async';
import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/matrix_api.dart';
import 'package:famedlysdk/encryption.dart';
import 'event.dart'; import 'event.dart';
import 'room.dart'; import 'room.dart';
@ -104,7 +103,7 @@ class Timeline {
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
if (events[i].type == EventTypes.Encrypted && if (events[i].type == EventTypes.Encrypted &&
events[i].messageType == MessageTypes.BadEncrypted && events[i].messageType == MessageTypes.BadEncrypted &&
events[i].content['body'] == DecryptError.UNKNOWN_SESSION && events[i].content['can_request_session'] == true &&
events[i].content['session_id'] == sessionId) { events[i].content['session_id'] == sessionId) {
events[i] = await room.client.encryption events[i] = await room.client.encryption
.decryptRoomEvent(room.id, events[i], store: true); .decryptRoomEvent(room.id, events[i], store: true);

View file

@ -216,6 +216,148 @@ void main() {
true); true);
}); });
test('setInboundGroupSession', () async {
final session = olm.OutboundGroupSession();
session.create();
final inbound = olm.InboundGroupSession();
inbound.create(session.session_key());
final senderKey = client.identityKey;
final roomId = '!someroom:example.org';
final sessionId = inbound.session_id();
// set a payload...
var sessionPayload = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId,
'session_key': inbound.export_session(1),
'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey,
};
client.encryption.keyManager.setInboundGroupSession(
roomId, sessionId, senderKey, sessionPayload,
forwarded: true);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.inboundGroupSession
.first_known_index(),
1);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.forwardingCurve25519KeyChain
.length,
1);
// not set one with a higher first known index
sessionPayload = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId,
'session_key': inbound.export_session(2),
'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey,
};
client.encryption.keyManager.setInboundGroupSession(
roomId, sessionId, senderKey, sessionPayload,
forwarded: true);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.inboundGroupSession
.first_known_index(),
1);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.forwardingCurve25519KeyChain
.length,
1);
// set one with a lower first known index
sessionPayload = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId,
'session_key': inbound.export_session(0),
'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey,
};
client.encryption.keyManager.setInboundGroupSession(
roomId, sessionId, senderKey, sessionPayload,
forwarded: true);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.inboundGroupSession
.first_known_index(),
0);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.forwardingCurve25519KeyChain
.length,
1);
// not set one with a longer forwarding chain
sessionPayload = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey, 'beep'],
'session_id': sessionId,
'session_key': inbound.export_session(0),
'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey,
};
client.encryption.keyManager.setInboundGroupSession(
roomId, sessionId, senderKey, sessionPayload,
forwarded: true);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.inboundGroupSession
.first_known_index(),
0);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.forwardingCurve25519KeyChain
.length,
1);
// set one with a shorter forwarding chain
sessionPayload = <String, dynamic>{
'algorithm': 'm.megolm.v1.aes-sha2',
'room_id': roomId,
'forwarding_curve25519_key_chain': [],
'session_id': sessionId,
'session_key': inbound.export_session(0),
'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey,
};
client.encryption.keyManager.setInboundGroupSession(
roomId, sessionId, senderKey, sessionPayload,
forwarded: true);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.inboundGroupSession
.first_known_index(),
0);
expect(
client.encryption.keyManager
.getInboundGroupSession(roomId, sessionId, senderKey)
.forwardingCurve25519KeyChain
.length,
0);
inbound.free();
session.free();
});
test('dispose client', () async { test('dispose client', () async {
await client.dispose(closeDatabase: true); await client.dispose(closeDatabase: true);
}); });

View file

@ -237,9 +237,9 @@ void main() {
try { try {
await event.requestKey(); await event.requestKey();
} catch (e) { } catch (e) {
exception = e; exception = e.toString();
} }
expect(exception, 'Session key not unknown'); expect(exception, 'Session key not requestable');
var event2 = Event.fromJson({ var event2 = Event.fromJson({
'event_id': id, 'event_id': id,
@ -251,6 +251,7 @@ void main() {
'content': json.encode({ 'content': json.encode({
'msgtype': 'm.bad.encrypted', 'msgtype': 'm.bad.encrypted',
'body': DecryptError.UNKNOWN_SESSION, 'body': DecryptError.UNKNOWN_SESSION,
'can_request_session': true,
'algorithm': 'm.megolm.v1.aes-sha2', 'algorithm': 'm.megolm.v1.aes-sha2',
'ciphertext': 'AwgAEnACgAkLmt6qF84IK++J7UDH2Za1YVchHyprqTqsg...', 'ciphertext': 'AwgAEnACgAkLmt6qF84IK++J7UDH2Za1YVchHyprqTqsg...',
'device_id': 'RJYKSTBOIE', 'device_id': 'RJYKSTBOIE',