Skip to content

Commit d70e9f9

Browse files
6.4.18-b1 (#1554)
IOS_ONLY - Fixed broken video calls by fixing bug in Mozilla's webrtc-sdp library - Reworked complete OMEMO trust management - Fixed spurious "new OMEMO device found" notifications and status messages - Introduced some new OMEMO expert settings - Improved UI responsiveness in some rare cases - Made sure to periodically advance the OMEMO DH-ratchet even on devices only used for receiving messages, not sending - Made sure to remove old OMEMO devices not seen for more than 90 days from own devicelist
2 parents 4b7f392 + 6c5ec7d commit d70e9f9

23 files changed

+821
-681
lines changed

Monal/Classes/ActiveChatsViewController.m

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ -(void) viewDidLoad
313313
self.chatListTable.emptyDataSetSource = self;
314314
self.chatListTable.emptyDataSetDelegate = self;
315315

316+
[self refresh];
317+
316318
//has to be done here to not always prepend intro screens onto our view queue
317319
//once a fullscreen view is dismissed (or the app is switched to foreground)
318320
[self segueToIntroScreensIfNeeded];
@@ -355,13 +357,15 @@ -(void) refreshDisplay
355357
}
356358
};
357359

358-
dispatch_async(dispatch_get_main_queue(), ^{
360+
[HelperTools dispatchAsync:YES reentrantOnQueue:dispatch_get_main_queue() withBlock:^{
359361
size_t unpinnedConCntBefore = self.unpinnedContacts.count;
360362
size_t pinnedConCntBefore = self.pinnedContacts.count;
361363
NSMutableArray<MLContact*>* newUnpinnedContacts = [[DataLayer sharedInstance] activeContactsWithPinned:NO];
362364
NSMutableArray<MLContact*>* newPinnedContacts = [[DataLayer sharedInstance] activeContactsWithPinned:YES];
363-
if(!newUnpinnedContacts || ! newPinnedContacts)
364-
return;
365+
if(!newUnpinnedContacts)
366+
newUnpinnedContacts = [NSMutableArray new];
367+
if(!newPinnedContacts)
368+
newPinnedContacts = [NSMutableArray new];
365369

366370
int unpinnedCntDiff = (int)unpinnedConCntBefore - (int)newUnpinnedContacts.count;
367371
int pinnedCntDiff = (int)pinnedConCntBefore - (int)newPinnedContacts.count;
@@ -380,8 +384,8 @@ -(void) refreshDisplay
380384
[self presentChatWithContact:nil];
381385
}
382386

383-
if(self.chatListTable.hasUncommittedUpdates)
384-
return;
387+
// if(self.chatListTable.hasUncommittedUpdates)
388+
// return;
385389
[CATransaction begin];
386390
[UIView performWithoutAnimation:^{
387391
[self.chatListTable beginUpdates];
@@ -398,7 +402,7 @@ -(void) refreshDisplay
398402

399403
MonalAppDelegate* appDelegate = (MonalAppDelegate*)[UIApplication sharedApplication].delegate;
400404
[appDelegate updateUnread];
401-
});
405+
}];
402406
}
403407

404408
-(void) refreshContact:(NSNotification*) notification
@@ -566,9 +570,7 @@ -(void) insertOrMoveContact:(MLContact*) contact completion:(void (^ _Nullable)(
566570
[self.chatListTable insertRowsAtIndexPaths:@[insertAtPath] withRowAnimation:UITableViewRowAnimationRight];
567571
//make sure to fully refresh to remove the empty dataset (yes this will trigger on first chat pinning, too, but that does no harm)
568572
if(oldCount == 0)
569-
dispatch_async(dispatch_get_main_queue(), ^{
570-
[self refreshDisplay];
571-
});
573+
[self refreshDisplay];
572574
}
573575
} completion:^(BOOL finished) {
574576
if(completion) completion(finished);
@@ -594,7 +596,6 @@ -(void) viewDidAppear:(BOOL) animated
594596
{
595597
DDLogDebug(@"active chats view did appear");
596598
[super viewDidAppear:animated];
597-
598599
[self refresh];
599600
}
600601

@@ -605,10 +606,10 @@ -(void) sheetDismissed
605606

606607
-(void) refresh
607608
{
608-
dispatch_async(dispatch_get_main_queue(), ^{
609+
[HelperTools dispatchAsync:YES reentrantOnQueue:dispatch_get_main_queue() withBlock:^{
609610
[self refreshDisplay]; // load contacts
610611
[self processViewQueue];
611-
});
612+
}];
612613
}
613614

614615
-(void) didReceiveMemoryWarning

Monal/Classes/AddContactMenu.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ struct AddContactMenu: View {
156156
self.newContact = MLContact.createContact(fromJid: jid, andAccountNo: account.accountNo)
157157
successAlert(title: Text("Success!"), message: Text("Successfully joined group/channel \(jid)!"))
158158
}.catch { error in
159-
errorAlert(title: Text("Error entering group/channel!"), message: Text("\(String(describing:error))"))
159+
errorAlert(title: Text("Error entering group/channel!"), message: Text(error.localizedDescription))
160160
}
161161
}
162162
}.catch { error in

Monal/Classes/ContactDetails.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ struct ContactDetails: View {
582582
}
583583
successAlert(title: Text("Success"), message: contact.mucType == "group" ? Text("Successfully destroyed group.") : Text("Successfully destroyed channel."))
584584
}.catch { error in
585-
errorAlert(title: Text("Error destroying group!"), message: Text("\(String(describing:error))"))
585+
errorAlert(title: Text("Error destroying group!"), message: Text(error.localizedDescription))
586586
}
587587
}
588588
)

Monal/Classes/DataLayerMigrations.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,11 @@ FOREIGN KEY('account_id') REFERENCES 'account'('account_id') ON DELETE CASCADE \
10911091
[db executeNonQuery:@"ALTER TABLE account DROP COLUMN 'supports_sasl2';"];
10921092
}];
10931093

1094+
//make sure all omemo devices have a "last used" timestamp
1095+
[self updateDB:db withDataLayer:dataLayer toVersion:6.407 withBlock:^{
1096+
[db executeNonQuery:@"UPDATE signalContactIdentity SET lastReceivedMsg=CURRENT_TIMESTAMP WHERE lastReceivedMsg IS NULL;"];
1097+
}];
1098+
10941099

10951100
//check if device id changed and invalidate state, if so
10961101
//but do so only for non-sandbox (e.g. non-development) installs

Monal/Classes/GeneralSettings.swift

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ class GeneralSettingsDefaultsDB: ObservableObject {
5959
@defaultsDB("OMEMODefaultOn")
6060
var omemoDefaultOn:Bool
6161

62+
@defaultsDB("allowMixedTrustInGroups")
63+
var allowMixedTrustInGroups: Bool
64+
65+
@defaultsDB("allowUnverifiedCalls")
66+
var allowUnverifiedCalls: Bool
67+
6268
@defaultsDB("AutodeleteInterval")
6369
var AutodeleteInterval: Int
6470

@@ -287,9 +293,43 @@ struct SecuritySettings: View {
287293
Section(header: Text("Encryption")) {
288294
SettingsToggle(isOn: $generalSettingsDefaultsDB.omemoDefaultOn) {
289295
Text("Enable encryption by default for new chats")
290-
Text("Every new contact will have encryption enabled, but already known contacts will preserve their encryption settings.")
296+
Text(
297+
"""
298+
Every new contact will have encryption enabled, but already known contacts will preserve their encryption settings.
299+
Generally this should never be turned off by members of high risk groups like journalists, activists etc.
300+
"""
301+
)
291302
}
292303

304+
SettingsToggle(isOn: $generalSettingsDefaultsDB.allowUnverifiedCalls) {
305+
Text("Allow unverified calls in encrypted chats")
306+
Text(
307+
"""
308+
Allow calls not being OMEMO-verified in OMEMO encrypted chats.
309+
If you turn this off, you won't be able to place or receive calls to/from contacts not using a client implementing [http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification](https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd).
310+
This should be turned off by members of high risk groups like journalists, activists etc.
311+
"""
312+
)
313+
}
314+
315+
SettingsToggle(isOn: $generalSettingsDefaultsDB.allowMixedTrustInGroups) {
316+
Text("Allow BTBV-only participants in verified encrypted groups")
317+
Text(
318+
"""
319+
If this is off, you won't send encrypted messages to members of a group not having any explicitly trusted device once you explicitly trusted/distrusted at least one single device of another member of this group.
320+
Use with care, because this means you'll have to explicitly trust at least one device of *all* members of a group once you started manually changing the trust of one device of a member of this group (even if you did this in an 1:1 chat with one of the members)!
321+
Generally this should only be turned off by members of high risk groups like journalists, activists etc.
322+
"""
323+
)
324+
}
325+
}
326+
327+
Section(header: Text("Networking")) {
328+
SettingsToggle(isOn: $generalSettingsDefaultsDB.webrtcAllowP2P) {
329+
Text("Calls: Allow P2P sessions")
330+
Text("Allow your device to establish a direct network connection to the remote party. This might leak your IP address to the caller/callee.")
331+
}
332+
293333
if #available(iOS 16.0, macCatalyst 16.0, *) {
294334
SettingsToggle(isOn: $generalSettingsDefaultsDB.useDnssecForAllConnections) {
295335
Text("Use DNSSEC validation for all connections")
@@ -303,11 +343,6 @@ like hotel wifi, ugly mobile carriers etc.
303343
)
304344
}
305345
}
306-
307-
SettingsToggle(isOn: $generalSettingsDefaultsDB.webrtcAllowP2P) {
308-
Text("Calls: Allow P2P sessions")
309-
Text("Allow your device to establish a direct network connection to the remote party. This might leak your IP address to the caller/callee.")
310-
}
311346
}
312347

313348
Section(header: Text("On this device")) {

Monal/Classes/MLCall.m

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -223,17 +223,10 @@ -(BOOL) muted
223223
-(void) setSpeaker:(BOOL) speaker
224224
{
225225
@synchronized(self) {
226-
DDLogError(@"*** setSpeaker:%@ called...", bool2str(speaker));
227226
if(self.webRTCClient == nil || self.audioSession == nil)
228-
{
229-
DDLogError(@"*** setSpeaker: not ready: %@, %@", self.webRTCClient, self.audioSession);
230227
return;
231-
}
232228
if(_speaker == speaker)
233-
{
234-
DDLogError(@"*** setSpeaker: called but identical...");
235229
return;
236-
}
237230
_speaker = speaker;
238231
if(_speaker)
239232
[self.webRTCClient speakerOn];
@@ -484,7 +477,7 @@ -(void) didActivateAudioSession:(AVAudioSession*) audioSession
484477
[[RTCAudioSession sharedInstance] unlockForConfiguration];
485478
if(self.callType == MLCallTypeVideo)
486479
{
487-
DDLogError(@"*** Activating speaker...");
480+
DDLogInfo(@"*** Video call detected, activating speaker...");
488481
self.speaker = YES;
489482
}
490483
}
@@ -883,7 +876,7 @@ -(void) offerSDP
883876
DDLogDebug(@"WebRTC reported local SDP '%@', sending to '%@': %@", [RTCSessionDescription stringForType:sdp.type], self.fullRemoteJid, sdp.sdp);
884877

885878
NSArray<MLXMLNode*>* children = [HelperTools sdp2xml:sdp.sdp withInitiator:YES];
886-
if(children.count == 0)
879+
if(children == nil || children.count == 0)
887880
{
888881
DDLogError(@"Could not serialize local SDP to XML!");
889882
[self handleEndCallActionWithReason:MLCallFinishReasonError];
@@ -1568,6 +1561,14 @@ -(void) processIncomingSDP:(NSNotification*) notification
15681561
return;
15691562
}
15701563
}
1564+
1565+
//all calls in encrypted chats eventually reach this point (either as session-initiate or as session-accept)
1566+
//--> check if we allow unverified calls and abort if the call is unverified and this isn't allowed
1567+
if(![[HelperTools defaultsDB] boolForKey:@"allowUnverifiedCalls"] && self.encryptionState == MLCallEncryptionStateClear)
1568+
{
1569+
[self handleEndCallActionWithReason:MLCallFinishReasonSecurityError];
1570+
return;
1571+
}
15711572
}
15721573
else
15731574
self.encryptionState = MLCallEncryptionStateClear;
@@ -1765,15 +1766,22 @@ -(void) handleAudioRouteChangeNotification:(NSNotification*) notification
17651766

17661767
-(MLCallEncryptionState) encryptionTypeForDeviceid:(NSNumber* _Nonnull) deviceid
17671768
{
1769+
//problem is: an attacking server could simply strip out the omemo deviceid from the <proceed> to downgrade us to a non-verified call
1770+
//dropping calls from a device not trusted but known, would not improve security because the attacker using that device could simply
1771+
//strip off the omemo deviceid from the <proceed>
1772+
//--> simply report those calls as unverified (MLCallEncryptionStateClear)
1773+
//if configured to not allow unverified calls, we drop all MLCallEncryptionStateClear calls in processIncomingSDP
1774+
17681775
NSNumber* trustLevel = [self.account.omemo getTrustLevelForJid:self.contact.contactJid andDeviceId:deviceid];
1776+
DDLogVerbose(@"Trust level: %@", trustLevel);
1777+
17691778
if(trustLevel == nil)
17701779
return MLCallEncryptionStateClear;
1771-
switch(trustLevel.intValue)
1772-
{
1773-
case MLOmemoTrusted: return MLCallEncryptionStateTrusted;
1774-
case MLOmemoToFU: return MLCallEncryptionStateToFU;
1775-
default: return MLCallEncryptionStateClear;
1776-
}
1780+
else if([MLSignalStore acceptedTrustLevel:trustLevel.intValue withTofu:NO andOutgoing:(self.direction == MLCallDirectionOutgoing)])
1781+
return MLCallEncryptionStateTrusted;
1782+
else if([MLSignalStore acceptedTrustLevel:trustLevel.intValue withTofu:YES andOutgoing:(self.direction == MLCallDirectionOutgoing)])
1783+
return MLCallEncryptionStateToFU;
1784+
return MLCallEncryptionStateClear;
17771785
}
17781786

17791787
-(BOOL) encryptFingerprintsInChildren:(NSArray<MLXMLNode*>*) children
@@ -1820,7 +1828,7 @@ -(BOOL) decryptFingerprintsInIqNode:(XMPPIQ*) iqNode
18201828
DDLogWarn(@"More than one OMEMO envelope found!");
18211829
return NO;
18221830
}
1823-
NSString* decryptedFingerprint = [self.account.omemo decryptOmemoEnvelope:[fingerprintNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"] forSenderJid:self.contact.contactJid andReturnErrorString:NO];
1831+
NSString* decryptedFingerprint = [self.account.omemo decryptOmemoEnvelope:[fingerprintNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"] forSenderJid:self.contact.contactJid inMuc:nil andReturnErrorString:NO];
18241832
if(decryptedFingerprint == nil)
18251833
{
18261834
DDLogWarn(@"Could not decrypt OMEMO encrypted fingerprint!");

Monal/Classes/MLIQProcessor.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ +(void) processErrorIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
204204
}
205205
return;
206206
}
207+
//we need to check for the rsm/last element because of a weird ejabberd bug sending complete=false, but no rsm last element
207208
if(![[iqNode findFirst:@"{urn:xmpp:mam:2}fin@complete|bool"] boolValue] && [iqNode check:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"])
208209
{
209210
DDLogVerbose(@"Paging through mam catchup results at %@ with after: %@", account.connectionProperties.identity.jid, [iqNode findFirst:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"]);
@@ -212,7 +213,7 @@ +(void) processErrorIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account
212213
[pageQuery setMAMQueryAfter:[iqNode findFirst:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/last#"]];
213214
[account sendIq:pageQuery withHandler:$newHandler(self, handleCatchup, $BOOL(secondTry, NO))];
214215
}
215-
else if([[iqNode findFirst:@"{urn:xmpp:mam:2}fin@complete|bool"] boolValue])
216+
else
216217
{
217218
DDLogVerbose(@"Mam catchup finished for %@", account.connectionProperties.identity.jid);
218219
[account mamFinishedFor:account.connectionProperties.identity.jid];

Monal/Classes/MLOMEMO.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
3232
*/
3333
-(MLXMLNode* _Nullable) encryptString:(NSString* _Nullable) message toDeviceids:(NSDictionary<NSString*, NSSet<NSNumber*>*>*) contactDeviceMap;
3434
-(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullable) message toContact:(NSString*) toContact;
35-
-(NSString* _Nullable) decryptOmemoEnvelope:(MLXMLNode*) envelope forSenderJid:(NSString*) senderJid andReturnErrorString:(BOOL) returnErrorString;
35+
-(NSString* _Nullable) decryptOmemoEnvelope:(MLXMLNode*) envelope forSenderJid:(NSString*) senderJid inMuc:(NSString* _Nullable) mucJid andReturnErrorString:(BOOL) returnErrorString;
3636
-(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticipantJid:(NSString* _Nullable) mucParticipantJid;
3737

3838
-(NSSet<NSNumber*>*) knownDevicesForAddressName:(NSString*) addressName;

0 commit comments

Comments
 (0)