From 8131f2b240a5c9b0963e149032d192d3326d30d6 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 24 Sep 2023 20:38:51 +0200 Subject: [PATCH 01/29] --- 863 -- 6.0b14 From cea1380f0b9a6f719512b80bb8e499d023c660c4 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 24 Sep 2023 21:59:04 +0200 Subject: [PATCH 02/29] Log crashreport logfile copy result --- Monal/Classes/HelperTools.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 9e3cf7e49d..6b61bbaf24 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -168,8 +168,9 @@ static void addFilePathWithSize(const KSCrashReportWriter* writer, char* name, c static void crash_callback(const KSCrashReportWriter* writer) { - asyncSafeCopyFile(_origLogfilePath, _logfilePath); + int copyRetval = asyncSafeCopyFile(_origLogfilePath, _logfilePath); writer->addStringElement(writer, "logfileCopied", "YES"); + writer->addIntegerElement(writer, "logfileCopyResult", copyRetval); addFilePathWithSize(writer, "logfileCopy", _logfilePath); //this comes last to make sure we see size differences if the logfile got written during crash data collection (could be other processes) addFilePathWithSize(writer, "currentLogfile", _origLogfilePath); From f15b221ee160d9b3c181e8e0b02a7bc53cc25b05 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 24 Sep 2023 22:35:28 +0200 Subject: [PATCH 03/29] Reduce rawlog size to 128MiB This reduces memory consumption when sending crashreports and thus prevents crashes when doing crash reports. --- Monal/Classes/HelperTools.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 6b61bbaf24..b31f9bb6c4 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -1502,7 +1502,7 @@ +(void) configureLogging self.fileLogger = [[MLFileLogger alloc] initWithLogFileManager:logFileManager]; self.fileLogger.doNotReuseLogFiles = NO; self.fileLogger.rollingFrequency = 60 * 60 * 48; // 48 hour rolling - self.fileLogger.maximumFileSize = 256 * 1024 * 1024; + self.fileLogger.maximumFileSize = 128 * 1024 * 1024; self.fileLogger.logFormatter = formatter; self.fileLogger.archiveAllowed = YES; //everything is configured now, engage logfile archiving [DDLog addLogger:self.fileLogger]; From 36b205e5ccb21262979751576730890fed925b42 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 25 Sep 2023 19:11:53 +0200 Subject: [PATCH 04/29] Ask for mic permission when starting a call --- Monal/Classes/AVCallUI.swift | 15 +++++++++++++++ Monal/Monal-Info.plist | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/AVCallUI.swift b/Monal/Classes/AVCallUI.swift index a28a860b68..88995eac2c 100644 --- a/Monal/Classes/AVCallUI.swift +++ b/Monal/Classes/AVCallUI.swift @@ -29,6 +29,7 @@ struct VideoView: UIViewRepresentable { struct AVCallUI: View { @StateObject private var call: ObservableKVOWrapper @StateObject private var contact: ObservableKVOWrapper + @State private var showMicAlert = false private var ringingPlayer: AVAudioPlayer! private var busyPlayer: AVAudioPlayer! private var errorPlayer: AVAudioPlayer! @@ -365,6 +366,13 @@ struct AVCallUI: View { Spacer().frame(height: 32) } } + .alert(isPresented: $showMicAlert) { + Alert( + title: Text("Missing permission"), + message: Text("You need to grant microphone access in iOS Settings-> Privacy-> Microphone, if you want that others can hear you."), + dismissButton: .default(Text("OK")) + ) + } .onAppear { //force portrait mode and lock ui there UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") @@ -372,6 +380,13 @@ struct AVCallUI: View { self.ringingPlayer.numberOfLoops = -1 self.busyPlayer.numberOfLoops = -1 self.errorPlayer.numberOfLoops = -1 + + //ask for mic permissions + AVAudioSession.sharedInstance().requestRecordPermission { granted in + if !granted { + showMicAlert = true + } + } } .onDisappear { //allow all orientations again diff --git a/Monal/Monal-Info.plist b/Monal/Monal-Info.plist index cdb24471ef..4bda9713d4 100644 --- a/Monal/Monal-Info.plist +++ b/Monal/Monal-Info.plist @@ -83,7 +83,7 @@ NSLocationWhenInUseUsageDescription Monal uses your location when you send a location message in a conversation. NSMicrophoneUsageDescription - Monal uses the microphone to transmit your voice in audio calls. + Monal uses the microphone to transmit your voice in audio messages or calls. NSPhotoLibraryAddUsageDescription Monal allows users to save photos received in conversations. NSPhotoLibraryUsageDescription From 2e95bdc05c26405e0d723511f0afb0a74c8825b1 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 24 Sep 2023 19:35:56 +0200 Subject: [PATCH 05/29] Configure AVAudioSession to allow bluetooth --- Monal/Classes/MLCall.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index 49ac4cc9f7..cf7b412585 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -392,6 +392,15 @@ -(void) didActivateAudioSession:(AVAudioSession*) audioSession { DDLogInfo(@"Activating audio session now: %@", audioSession); [[RTCAudioSession sharedInstance] lockForConfiguration]; + NSUInteger options = 0; + options |= AVAudioSessionCategoryOptionAllowBluetooth; + options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; + options |= AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers; + options |= AVAudioSessionCategoryOptionAllowAirPlay; + NSError* error = nil; + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:options error:&error]; + if(error != nil) + DDLogError(@"Failed to configure AVAudioSession: %@", error); [[RTCAudioSession sharedInstance] audioSessionDidActivate:audioSession]; [[RTCAudioSession sharedInstance] setIsAudioEnabled:YES]; [[RTCAudioSession sharedInstance] unlockForConfiguration]; From fb291325d6fb63911ab0d6ebac2de5d5159ff0a9 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 25 Sep 2023 22:05:11 +0200 Subject: [PATCH 06/29] Fix leaking of MLCall instances --- Monal/Classes/MLCall.m | 6 +++--- Monal/Classes/SwiftHelpers.swift | 5 ++++- Monal/Classes/WebRTCClient.swift | 4 ++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index cf7b412585..e7fbde30ac 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -119,9 +119,9 @@ -(instancetype) initWithUUID:(NSUUID*) uuid jmiid:(NSString*) jmiid contact:(MLC return self; } --(void) deinit +-(void) dealloc { - DDLogInfo(@"Call deinit: %@", self); + DDLogInfo(@"Called dealloc: %@", self); [self.callDurationTimer invalidate]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } @@ -1011,7 +1011,7 @@ -(void) webRTCClient:(WebRTCClient*) webRTCClient didDiscoverLocalCandidate:(RTC -(void) webRTCClient:(WebRTCClient*) webRTCClient didChangeConnectionState:(RTCIceConnectionState) state { @synchronized(self) { - if(webRTCClient != self.webRTCClient) + if(webRTCClient != self.webRTCClient && !self.isFinished) { DDLogInfo(@"Ignoring new RTCIceConnectionState %ld for webRTCClient: %@ (call migrated)", (long)state, webRTCClient); return; diff --git a/Monal/Classes/SwiftHelpers.swift b/Monal/Classes/SwiftHelpers.swift index 20ef19bce3..d99a0763e7 100644 --- a/Monal/Classes/SwiftHelpers.swift +++ b/Monal/Classes/SwiftHelpers.swift @@ -81,7 +81,10 @@ public class ObservableKVOWrapper: ObservableObject { private func addObserverForMember(_ member: String){ if(!self.observedMembers.contains(member)) { DDLogDebug("Adding observer for member '\(member)'...") - self.observers.append(KVOObserver(obj:self.obj, keyPath:member, objectWillChange: { + self.observers.append(KVOObserver(obj:self.obj, keyPath:member, objectWillChange: { [weak self] in + guard let self = self else { + return + } DDLogDebug("Observer said '\(member)' has changed...") DispatchQueue.main.async { DDLogDebug("Calling self.objectWillChange.send()...") diff --git a/Monal/Classes/WebRTCClient.swift b/Monal/Classes/WebRTCClient.swift index b7b4162709..e56ec780ee 100644 --- a/Monal/Classes/WebRTCClient.swift +++ b/Monal/Classes/WebRTCClient.swift @@ -45,6 +45,10 @@ final class WebRTCClient: NSObject { unreachable("WebRTCClient:init is unavailable") } + deinit { + DDLogDebug("Deinit of webrtc client for delegate: \(String(describing:self.delegate))") + } + @objc static func createPeerConnection(iceServers: [RTCIceServer], forceRelay: Bool) -> RTCPeerConnection? { let config = RTCConfiguration() From b6a64ae152ad19b36996160a67f74a04c280e4bc Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 25 Sep 2023 22:05:59 +0200 Subject: [PATCH 07/29] Make rust xml parser more forgiving --- rust/sdp-to-jingle/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/sdp-to-jingle/Cargo.toml b/rust/sdp-to-jingle/Cargo.toml index 323a2cca7d..0281a0cc6f 100644 --- a/rust/sdp-to-jingle/Cargo.toml +++ b/rust/sdp-to-jingle/Cargo.toml @@ -11,6 +11,6 @@ crate-type = ["staticlib", "lib"] [dependencies] serde = {version = "1.0"} serde_derive = {version = "1.0"} -quick-xml = { version = "0.30.0", features = ["serialize"] } +quick-xml = { version = "0.30.0", features = ["serialize", "overlapped-lists"] } webrtc-sdp = {version = "0.3.10", features = ["serialize"] } \ No newline at end of file From 576910ebe662df64f5648f38f0db47f9c899bc4d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 26 Sep 2023 20:28:09 +0200 Subject: [PATCH 08/29] Add default for opus maxplaybackrate which is 48000 not 0 --- rust/sdp-to-jingle/src/xep_0167.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rust/sdp-to-jingle/src/xep_0167.rs b/rust/sdp-to-jingle/src/xep_0167.rs index a5fa565398..111094fddd 100644 --- a/rust/sdp-to-jingle/src/xep_0167.rs +++ b/rust/sdp-to-jingle/src/xep_0167.rs @@ -339,7 +339,14 @@ impl JingleRtpSessionsPayloadType { max_br: self.get_fmtp_param(&mut known_param_names, "max_br"), max_mbps: self.get_fmtp_param(&mut known_param_names, "max_mbps"), max_fr: self.get_fmtp_param(&mut known_param_names, "max_fr"), - maxplaybackrate: self.get_fmtp_param(&mut known_param_names, "maxplaybackrate"), + // this has a default of 48000 which is different to the datatype-default of 0 + maxplaybackrate: match self + .get_fmtp_param_vec::(&mut known_param_names, "maxplaybackrate") + .is_empty() + { + true => 48000, + false => self.get_fmtp_param(&mut known_param_names, "maxplaybackrate"), + }, maxaveragebitrate: self.get_fmtp_param(&mut known_param_names, "maxaveragebitrate"), usedtx: self.get_fmtp_param(&mut known_param_names, "usedtx"), stereo: self.get_fmtp_param(&mut known_param_names, "stereo"), From 64996527f02c9ff8cfcc1c7533eb99301ea1d1b3 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 26 Sep 2023 20:49:51 +0200 Subject: [PATCH 09/29] Add default for opus profile_level_id which is 0x420010 not 0 --- rust/sdp-to-jingle/src/xep_0167.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rust/sdp-to-jingle/src/xep_0167.rs b/rust/sdp-to-jingle/src/xep_0167.rs index 111094fddd..497bdfdf04 100644 --- a/rust/sdp-to-jingle/src/xep_0167.rs +++ b/rust/sdp-to-jingle/src/xep_0167.rs @@ -172,6 +172,11 @@ impl JingleRtpSessionsPayloadType { add_nondefault_parameter!(self, params, level_asymmetry_allowed); add_nondefault_parameter!(self, params, profile_level_id); + // this has a default of 0x420010 which is different to the datatype-default of 0 + // see: https://stackoverflow.com/questions/20634476/is-sprop-parameter-sets-or-profile-level-id-the-sdp-parameter-required-to-decode + if params.profile_level_id != 0x420010 && params.profile_level_id != 0 { + self.add_parameter("profile_level_id", params.profile_level_id.to_string()); + } add_nondefault_parameter!(self, params, max_fs); add_nondefault_parameter!(self, params, max_cpb); add_nondefault_parameter!(self, params, max_dpb); @@ -332,7 +337,15 @@ impl JingleRtpSessionsPayloadType { .get_fmtp_param(&mut known_param_names, "packetization_mode"), level_asymmetry_allowed: self .get_fmtp_param(&mut known_param_names, "level_asymmetry_allowed"), - profile_level_id: self.get_fmtp_param(&mut known_param_names, "profile_level_id"), + // this has a default of 0x420010 which is different to the datatype-default of 0 + // see: https://stackoverflow.com/questions/20634476/is-sprop-parameter-sets-or-profile-level-id-the-sdp-parameter-required-to-decode + profile_level_id: match self + .get_fmtp_param_vec::(&mut known_param_names, "profile_level_id") + .is_empty() + { + true => 0x420010, + false => self.get_fmtp_param(&mut known_param_names, "profile_level_id"), + }, max_fs: self.get_fmtp_param(&mut known_param_names, "max_fs"), max_cpb: self.get_fmtp_param(&mut known_param_names, "max_cpb"), max_dpb: self.get_fmtp_param(&mut known_param_names, "max_dpb"), From 7e477f6fd075d04369cce2f78c7b9ffcbdce113e Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 26 Sep 2023 21:55:52 +0200 Subject: [PATCH 10/29] Update doap to include many more keys --- monal.doap | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/monal.doap b/monal.doap index 622a90c9d3..2eba3e18fd 100644 --- a/monal.doap +++ b/monal.doap @@ -14,16 +14,38 @@ - + + + + - + - + + + + + + + + Objective C iOS MacOS + + + Thilo Molitor + + + + + + Friedrich Altheide + + + From 0d8505d37b5c61c8851c3cdeab1d350294b0b744 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 26 Sep 2023 21:59:43 +0200 Subject: [PATCH 11/29] Don't update xmpp.org timestamp on stable releases --- .github/workflows/stable.build-push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stable.build-push.yml b/.github/workflows/stable.build-push.yml index 8960e61d57..0a2e31c9e4 100644 --- a/.github/workflows/stable.build-push.yml +++ b/.github/workflows/stable.build-push.yml @@ -71,8 +71,8 @@ jobs: run: ./scripts/uploadNonAlpha.sh stable - name: Publish catalyst to appstore connect run: xcrun altool --upload-app --file ./Monal/build/app/Monal.pkg --type macos --asc-provider S8D843U34Y -u "$(cat /Users/ci/apple_connect_upload_mail.txt)" -p "$(cat /Users/ci/apple_connect_upload_secret.txt)" --primary-bundle-id maccatalyst.G7YU7X7KRJ.SworIM - - name: Update xmpp.org client list with new timestamp - run: ./scripts/push_xmpp.org.sh + # - name: Update xmpp.org client list with new timestamp + # run: ./scripts/push_xmpp.org.sh - uses: actions/upload-artifact@v2 with: name: monal-catalyst-pkg From 67c17db112b3c3ebae1da8a746cafee530ffd1e7 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 27 Sep 2023 01:37:53 +0200 Subject: [PATCH 12/29] Add XEP-0215 to server details --- Monal/Classes/MLServerDetails.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Monal/Classes/MLServerDetails.m b/Monal/Classes/MLServerDetails.m index a0724e96cf..1b4dea7f9d 100644 --- a/Monal/Classes/MLServerDetails.m +++ b/Monal/Classes/MLServerDetails.m @@ -81,6 +81,13 @@ -(void) checkServerCaps:(MLXMPPConnection*) connection @"Color": connection.supportsPing ? @"Green" : @"Red" }]; + // supportsExternalServiceDiscovery + [self.serverCaps addObject:@{ + @"Title":NSLocalizedString(@"XEP-0215: External Service Discovery", @""), + @"Description":NSLocalizedString(@"XMPP protocol extension for discovering services external to the XMPP network, like STUN or TURN servers needed for A/V calls.", @""), + @"Color": connection.supportsPing ? @"Green" : @"Red" + }]; + // supportsRosterVersion [self.serverCaps addObject:@{ @"Title":NSLocalizedString(@"XEP-0237: Roster Versioning", @""), From 51218ee3ef2e7994dc65b23d00ee5fefb33a5097 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 27 Sep 2023 02:43:07 +0200 Subject: [PATCH 13/29] Allow XML attributes to be of any class that can be stringified --- Monal/Classes/MLXMLNode.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLXMLNode.m b/Monal/Classes/MLXMLNode.m index b4d487bd8d..39546f7fae 100644 --- a/Monal/Classes/MLXMLNode.m +++ b/Monal/Classes/MLXMLNode.m @@ -819,9 +819,9 @@ -(NSString*) XMLString for(NSString* key in [_attributes allKeys]) { //handle xmlns inheritance (don't add namespace to childs if it should be the same like the parent's one) - if([key isEqualToString:@"xmlns"] && parent && [_attributes[@"xmlns"] isEqualToString:parent.attributes[@"xmlns"]]) + if([key isEqualToString:@"xmlns"] && parent && [[NSString stringWithFormat:@"%@", _attributes[@"xmlns"]] isEqualToString:[NSString stringWithFormat:@"%@", parent.attributes[@"xmlns"]]]) continue; - [outputString appendString:[NSString stringWithFormat:@" %@='%@'", key, [MLXMLNode escapeForXMPP:(NSString*)_attributes[key]]]]; + [outputString appendString:[NSString stringWithFormat:@" %@='%@'", key, [MLXMLNode escapeForXMPP:[NSString stringWithFormat:@"%@", _attributes[key]]]]]; } if([_children count] || (_data && ![_data isEqualToString:@""])) From 943145f0e1680bde7e1d436d143c0d2ba1b42513 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 27 Sep 2023 20:15:53 +0200 Subject: [PATCH 14/29] Don't respond with error iq, if ICE candidate could not be parsed --- Monal/Classes/MLCall.m | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index e7fbde30ac..3769335670 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -1175,16 +1175,19 @@ -(void) processRemoteICECandidate:(XMPPIQ*) iqNode } if(incomingCandidate == nil) { - DDLogError(@"incomingCandidate is unexpectedly nil!"); - XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; - [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ - [[MLXMLNode alloc] initWithElement:@"bad-request" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], - ] andData:nil]]; - [self.account send:errorIq]; - - //don't be too harsh and not end the call here - //[self handleEndCallActionWithReason:MLCallFinishReasonError]; + DDLogError(@"incomingCandidate is unexpectedly nil, ignoring!"); + [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; return; + +// XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; +// [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ +// [[MLXMLNode alloc] initWithElement:@"bad-request" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], +// ] andData:nil]]; +// [self.account send:errorIq]; +// +// //don't be too harsh and not end the call here +// //[self handleEndCallActionWithReason:MLCallFinishReasonError]; +// return; } DDLogInfo(@"%@: Got remote ICE candidate for call: %@", self, incomingCandidate); NSString* remoteUfrag = [self.remoteSDP findFirst:@"{urn:xmpp:jingle:1}jingle/content/{urn:xmpp:jingle:transports:ice-udp:1}transport@ufrag", incomingCandidate.sdpMid]; From fbe8da4598f3a39807d494a1ae759c8db82712a4 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 27 Sep 2023 20:46:25 +0200 Subject: [PATCH 15/29] Don't send TCP ICE candidates nor handle them when incoming These are not specced in XEP-0176, so ignore them. --- rust/sdp-to-jingle/src/lib.rs | 18 ++++++++++++------ rust/sdp-to-jingle/src/xep_0167.rs | 6 +++--- rust/sdp-to-jingle/src/xep_0176.rs | 18 +++++++++++++----- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/rust/sdp-to-jingle/src/lib.rs b/rust/sdp-to-jingle/src/lib.rs index 09fdda846c..fdac899fcd 100644 --- a/rust/sdp-to-jingle/src/lib.rs +++ b/rust/sdp-to-jingle/src/lib.rs @@ -14,21 +14,27 @@ mod xep_0339; pub fn sdp_str_to_jingle_str(sdp_str: &str, initiator: bool) -> Option { let sdp_session = match parse_sdp(sdp_str, true) { Err(e) => { - eprintln!("Could not parse sdp: {}", e); + eprintln!("Could not parse sdpstring: {}", e); return None; } Ok(sdp) => sdp, }; for warning in &sdp_session.warnings { - eprintln!("sdp parser warning: {}", warning); + eprintln!("mozilla sdp parser warning: {}", warning); } - let jingle = JingleRtpSessions::from_sdp(&sdp_session, initiator); + let jingle = match JingleRtpSessions::from_sdp(&sdp_session, initiator) { + Err(e) => { + eprintln!("Could not convert sdp to jingle: {}", e); + return None; + } + Ok(jingle) => jingle, + }; match quick_xml::se::to_string(&jingle) { Err(e) => { - eprintln!("Could not serialize jingle to xml: {}", e); + eprintln!("Could not serialize jingle to xmlstring: {}", e); None } Ok(jingle_xml) => Some(jingle_xml), @@ -38,7 +44,7 @@ pub fn sdp_str_to_jingle_str(sdp_str: &str, initiator: bool) -> Option { pub fn jingle_str_to_sdp_str(jingle_str: &str, initiator: bool) -> Option { let jingle: Root = match quick_xml::de::from_str(jingle_str) { Err(e) => { - eprintln!("Error parsing xml: {}", e); + eprintln!("Could not parse xmlstring: {}", e); return None; } Ok(j) => j, @@ -46,7 +52,7 @@ pub fn jingle_str_to_sdp_str(jingle_str: &str, initiator: bool) -> Option { - eprintln!("Error converting parsed xml into sdp: {}", e); + eprintln!("Could not convert jingle to sdp: {}", e); return None; } Ok(j) => j, diff --git a/rust/sdp-to-jingle/src/xep_0167.rs b/rust/sdp-to-jingle/src/xep_0167.rs index 497bdfdf04..6bac546bac 100644 --- a/rust/sdp-to-jingle/src/xep_0167.rs +++ b/rust/sdp-to-jingle/src/xep_0167.rs @@ -566,7 +566,7 @@ impl JingleRtpSessions { .push(JingleRtpSessionsValue::RtcpFbTrrInt(rtcp_fb_trr_int)); } - pub fn from_sdp(sdp: &SdpSession, initiator: bool) -> Root { + pub fn from_sdp(sdp: &SdpSession, initiator: bool) -> Result { let mut root = Root::default(); let mut has_global_extmap_allow_mixed: bool = false; //translate global ExtmapAllowMixed to media-local ExtmapAllowMixed values @@ -608,7 +608,7 @@ impl JingleRtpSessions { SdpAttribute::BundleOnly => {} SdpAttribute::Candidate(candidate) => { media_transport - .add_candidate(JingleTransportCandidate::new_from_sdp(candidate)); + .add_candidate(JingleTransportCandidate::new_from_sdp(candidate)?); //use ufrag from candidate if not (yet) set by dedicated ufrag attribute //(may be be overwritten if we encounter a dedicated ufrag attribute later on) if media_transport.get_ufrag().is_none() { @@ -743,7 +743,7 @@ impl JingleRtpSessions { content.childs.push(JingleDes::Description(jingle)); root.push(RootEnum::Content(content)); } - root + Ok(root) } pub fn to_sdp(root: &Root, initiator: bool) -> Result { diff --git a/rust/sdp-to-jingle/src/xep_0176.rs b/rust/sdp-to-jingle/src/xep_0176.rs index 12e7f02859..d4d48e738c 100644 --- a/rust/sdp-to-jingle/src/xep_0176.rs +++ b/rust/sdp-to-jingle/src/xep_0176.rs @@ -102,12 +102,12 @@ pub struct JingleTransportCandidate { } impl JingleTransportCandidate { - pub fn new_from_sdp(candidate: &SdpAttributeCandidate) -> Self { + pub fn new_from_sdp(candidate: &SdpAttributeCandidate) -> Result { let id = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .subsec_nanos(); - Self { + Ok(Self { xmlns: "urn:xmpp:jingle:transports:ice-udp:1".to_string(), id: Some(format!("{}", id)), component: candidate.component, @@ -125,12 +125,18 @@ impl JingleTransportCandidate { priority: candidate.priority, protocol: match candidate.transport { SdpAttributeCandidateTransport::Udp => "udp".to_string(), - SdpAttributeCandidateTransport::Tcp => "tcp".to_string(), //not specced in any xep + //SdpAttributeCandidateTransport::Tcp => "tcp".to_string(), //not specced in xep-0176 + _ => { + return Err(SdpParserInternalError::Generic( + "Encountered some candidate transport (like tcp) not specced in XEP-0176!" + .to_string(), + )); + } }, raddr: candidate.raddr.as_ref().map(|addr| format!("{}", addr)), rport: candidate.rport, c_type: JingleTransportCandidateType::new_from_sdp(&candidate.c_type), - } + }) } pub fn to_sdp( @@ -142,8 +148,10 @@ impl JingleTransportCandidate { component: self.component, transport: match self.protocol.as_str() { "udp" => Ok(SdpAttributeCandidateTransport::Udp), + //"tcp" => Ok(SdpAttributeCandidateTransport::Tcp), _ => Err(SdpParserInternalError::Generic( - "TCP or any other candidate transports not specced in an XEP-0176!".to_string(), + "Encountered some candidate transport (like tcp) not specced in XEP-0176!" + .to_string(), )), }?, priority: self.priority, From 2890a650729df1b481bf446978faccabbc79a0e0 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Fri, 29 Sep 2023 02:04:01 +0200 Subject: [PATCH 16/29] Fix call migration feature This was not updated when jingle sdp was implemented. --- Monal/Classes/MLCall.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index 3769335670..184f397666 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -451,6 +451,9 @@ -(void) migrateTo:(MLCall*) otherCall self.jmiProceed = nil; [self.callDurationTimer invalidate]; self.callDurationTimer = nil; + self.localSDP = otherCall.localSDP; //should be nil + self.remoteSDP = otherCall.remoteSDP; //should be nil + self.candidateQueue = otherCall.candidateQueue; //should be empty otherCall = nil; DDLogDebug(@"%@: Stopping all running timers...", [self short]); From b3e0b07ac9b792513490ad2b7c11daa82d7e38cb Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 27 Sep 2023 02:44:09 +0200 Subject: [PATCH 17/29] Implement OMEMO verification for A/V calls --- Monal/Classes/AVCallUI.swift | 9 ++ Monal/Classes/MLCall.h | 1 + Monal/Classes/MLCall.m | 225 +++++++++++++++++++++++++++++------ Monal/Classes/MLOMEMO.h | 3 + Monal/Classes/MLOMEMO.m | 199 +++++++++++++++++-------------- 5 files changed, 310 insertions(+), 127 deletions(-) diff --git a/Monal/Classes/AVCallUI.swift b/Monal/Classes/AVCallUI.swift index 88995eac2c..51ad78c9aa 100644 --- a/Monal/Classes/AVCallUI.swift +++ b/Monal/Classes/AVCallUI.swift @@ -161,6 +161,10 @@ struct AVCallUI: View { Text("Call ended: connection failed") .bold() .foregroundColor(.primary) + case .securityError: + Text("Call ended: could establish call encryption") + .bold() + .foregroundColor(.primary) case .unanswered: Text("Call was not answered") .bold() @@ -423,6 +427,11 @@ struct AVCallUI: View { ringingPlayer.stop() busyPlayer.stop() errorPlayer.play() + case .securityError: + DDLogDebug("state: finished: securityError") + ringingPlayer.stop() + busyPlayer.stop() + errorPlayer.play() case .unanswered: DDLogDebug("state: finished: unanswered") ringingPlayer.stop() diff --git a/Monal/Classes/MLCall.h b/Monal/Classes/MLCall.h index 3b5505b110..2eae2926bc 100644 --- a/Monal/Classes/MLCall.h +++ b/Monal/Classes/MLCall.h @@ -44,6 +44,7 @@ typedef NS_ENUM(NSUInteger, MLCallFinishReason) { MLCallFinishReasonUnknown, //dummy default value MLCallFinishReasonNormal, //used for a call answered and finished locally (call direction etc. don't matter here) MLCallFinishReasonConnectivityError, //used for a call accepted but not connected (call direction etc. don't matter here) + MLCallFinishReasonSecurityError, //used for a call that could not be encrypted using OMEMO MLCallFinishReasonUnanswered, //used for a call retracted remotely (always remote party) MLCallFinishReasonAnsweredElsewhere, //used for a call answered and finished remotely (own account OR remote party) MLCallFinishReasonRetracted, //used for a call retracted locally (always own acount) diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index 184f397666..477b8c7289 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -17,6 +17,7 @@ #import "MLVoIPProcessor.h" #import "MLCall.h" #import "MonalAppDelegate.h" +#import "MLOMEMO.h" @import CallKit; @import WebRTC; @@ -59,6 +60,7 @@ @interface MLCall() @property (nonatomic, strong) monal_void_block_t _Nullable cancelWaitUntilIceRestart; @property (nonatomic, strong) MLXMLNode* localSDP; @property (nonatomic, strong) MLXMLNode* remoteSDP; +@property (nonatomic, strong) NSNumber* remoteOmemoDeviceId; @property (nonatomic, strong) NSMutableArray* candidateQueue; @property (nonatomic, readonly) xmpp* account; @@ -103,6 +105,7 @@ -(instancetype) initWithUUID:(NSUUID*) uuid jmiid:(NSString*) jmiid contact:(MLC self.cancelConnectingTimeout = nil; self.localSDP = nil; self.remoteSDP = nil; + self.remoteOmemoDeviceId = nil; self.candidateQueue = [NSMutableArray new]; [HelperTools dispatchAsync:NO reentrantOnQueue:dispatch_get_main_queue() withBlock:^{ @@ -290,6 +293,12 @@ -(void) setJmiProceed:(MLXMLNode*) jmiProceed { @synchronized(self) { _jmiProceed = jmiProceed; + //try the style implemented in conversations + self.remoteOmemoDeviceId = [jmiProceed findFirst:@"{urn:xmpp:jingle-message:0}proceed/{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}device@id|uint"]; + //try the style documented by daniel at https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd + if(self.remoteOmemoDeviceId == nil) + self.remoteOmemoDeviceId = [jmiProceed findFirst:@"{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}device@id|uint"]; + DDLogInfo(@"Proceed set remote omemo deviceid to: %@", self.remoteOmemoDeviceId); if(self.direction == MLCallDirectionOutgoing && self.webRTCClient != nil) [self establishOutgoingConnection]; } @@ -453,7 +462,8 @@ -(void) migrateTo:(MLCall*) otherCall self.callDurationTimer = nil; self.localSDP = otherCall.localSDP; //should be nil self.remoteSDP = otherCall.remoteSDP; //should be nil - self.candidateQueue = otherCall.candidateQueue; //should be empty + self.candidateQueue = otherCall.candidateQueue; //should be empty + self.remoteOmemoDeviceId = otherCall.remoteOmemoDeviceId; //depends on jmiPropose otherCall = nil; DDLogDebug(@"%@: Stopping all running timers...", [self short]); @@ -579,6 +589,11 @@ -(void) internalUpdateCallKitState [self sendJmiFinishWithReason:@"connectivity-error"]; [self.voipProcessor.cxProvider reportCallWithUUID:self.uuid endedAtDate:nil reason:CXCallEndedReasonFailed]; } + else if(self.finishReason == MLCallFinishReasonSecurityError) + { + [self sendJmiFinishWithReason:@"security-error"]; + [self.voipProcessor.cxProvider reportCallWithUUID:self.uuid endedAtDate:nil reason:CXCallEndedReasonFailed]; + } else if(self.finishReason == MLCallFinishReasonError) [self.voipProcessor.cxProvider reportCallWithUUID:self.uuid endedAtDate:nil reason:CXCallEndedReasonFailed]; else @@ -620,6 +635,11 @@ -(void) internalUpdateCallKitState [self sendJmiFinishWithReason:@"connectivity-error"]; [self.voipProcessor.cxProvider reportCallWithUUID:self.uuid endedAtDate:nil reason:CXCallEndedReasonFailed]; } + else if(self.finishReason == MLCallFinishReasonSecurityError) + { + [self sendJmiFinishWithReason:@"security-error"]; + [self.voipProcessor.cxProvider reportCallWithUUID:self.uuid endedAtDate:nil reason:CXCallEndedReasonFailed]; + } else if(self.finishReason == MLCallFinishReasonError) [self.voipProcessor.cxProvider reportCallWithUUID:self.uuid endedAtDate:nil reason:CXCallEndedReasonFailed]; else @@ -752,16 +772,22 @@ -(void) handleConnectivityChange:(NSNotification*) notification -(void) offerSDP { + //see https://webrtc.googlesource.com/src/+/refs/heads/main/sdk/objc/api/peerconnection/RTCSessionDescription.h [self.webRTCClient offerWithCompletion:^(RTCSessionDescription* sdp) { - DDLogDebug(@"WebRTC reported local SDP '%@' offer, sending to '%@': %@", [RTCSessionDescription stringForType:sdp.type], self.fullRemoteJid, sdp.sdp); + DDLogDebug(@"WebRTC reported local SDP '%@', sending to '%@': %@", [RTCSessionDescription stringForType:sdp.type], self.fullRemoteJid, sdp.sdp); - //see https://webrtc.googlesource.com/src/+/refs/heads/main/sdk/objc/api/peerconnection/RTCSessionDescription.h + NSArray* children = [HelperTools sdp2xml:sdp.sdp withInitiator:YES]; + if(![self encryptFingerprintsInChildren:children]) + { + DDLogError(@"Could not encrypt local SDP offer fingerprint with OMEMO!"); + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; + return; + } XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ @"action": @"session-initiate", @"sid": self.jmiid, - } andChildren:[HelperTools sdp2xml:sdp.sdp withInitiator:YES] - andData:nil]]; + } andChildren:children andData:nil]]; /* TODO: implement raw sdp alongside jingle and write xep [[MLXMLNode alloc] initWithElement:@"sdp" andNamespace:@"urn:tmp:monal:webrtc:sdp:0" withAttributes:@{ @"id": self.jmiid, @@ -848,6 +874,10 @@ -(void) sendJmiProceed [jmiNode addChildNode:[[MLXMLNode alloc] initWithElement:@"proceed" andNamespace:@"urn:xmpp:jingle-message:0" withAttributes:@{ @"id": self.jmiid, } andChildren:@[] andData:nil]]; + if(self.contact.isEncrypted) + [jmiNode addChildNode:[[MLXMLNode alloc] initWithElement:@"device" andNamespace:@"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification" withAttributes:@{ + @"id": [self.account.omemo getDeviceId], + } andChildren:@[] andData:nil]]; [jmiNode setStoreHint]; self.jmiProceed = jmiNode; [self.account send:jmiNode]; @@ -924,27 +954,34 @@ -(NSString*) description case MLCallStateUnknown: state = @"unknown"; break; default: state = @"undefined"; break; } - return [NSString stringWithFormat:@"%@Call:%@", self.direction == MLCallDirectionIncoming ? @"Incoming" : @"Outgoing", @{ - @"uuid": self.uuid, - @"jmiid": self.jmiid, - @"state": state, - @"finishReason": @(self.finishReason), - @"durationTime": @(self.durationTime), - @"contact": nilWrapper(self.contact), - @"fullRemoteJid": nilWrapper(self.fullRemoteJid), - @"jmiPropose": nilWrapper(self.jmiPropose), - @"jmiProceed": nilWrapper(self.jmiProceed), - @"webRTCClient": nilWrapper(self.webRTCClient), - @"providerAnswerAction": nilWrapper(self.providerAnswerAction), - @"wasConnectedOnce": bool2str(self.wasConnectedOnce), - @"isConnected": bool2str(self.isConnected), - @"isReconnecting": bool2str(self.isReconnecting), - }]; + return [NSString stringWithFormat:@"%@%@Call:%@", + self.direction == MLCallDirectionIncoming ? @"Incoming" : @"Outgoing", + self.contact.isEncrypted ? @"Encrypted" : @"Unencrypted", + @{ + @"uuid": self.uuid, + @"jmiid": self.jmiid, + @"state": state, + @"finishReason": @(self.finishReason), + @"durationTime": @(self.durationTime), + @"contact": nilWrapper(self.contact), + @"fullRemoteJid": nilWrapper(self.fullRemoteJid), + @"jmiPropose": nilWrapper(self.jmiPropose), + @"jmiProceed": nilWrapper(self.jmiProceed), + @"webRTCClient": nilWrapper(self.webRTCClient), + @"providerAnswerAction": nilWrapper(self.providerAnswerAction), + @"wasConnectedOnce": bool2str(self.wasConnectedOnce), + @"isConnected": bool2str(self.isConnected), + @"isReconnecting": bool2str(self.isReconnecting), + @"hasLocalSDP": bool2str(self.localSDP != nil), + @"hasRemoteSDP": bool2str(self.remoteSDP != nil), + @"remoteOmemoDeviceId": nilWrapper(self.remoteOmemoDeviceId), + } + ]; } -(NSString*) short { - return [NSString stringWithFormat:@"%@Call:%@{%@}", self.direction == MLCallDirectionIncoming ? @"Incoming" : @"Outgoing", self.uuid, self.jmiid]; + return [NSString stringWithFormat:@"%@%@Call:%@{%@}", self.direction == MLCallDirectionIncoming ? @"Incoming" : @"Outgoing", self.contact.isEncrypted ? @"Encrypted" : @"Unencrypted", self.uuid, self.jmiid]; } -(BOOL) isEqualToContact:(MLContact*) contact @@ -1136,8 +1173,6 @@ -(void) processIncomingICECandidate:(NSNotification*) notification -(void) processRemoteICECandidate:(XMPPIQ*) iqNode { - //TODO: all code in this method only allows for one single candidate per jingle transport-info iq, but the xep allows multiple candidates! - RTCIceCandidate* incomingCandidate = nil; if([iqNode check:@"{urn:xmpp:jingle:1}jingle/{urn:tmp:monal:webrtc:candidate:0}candidate"]) { @@ -1256,6 +1291,18 @@ -(void) processIncomingSDP:(NSNotification*) notification return; } + //make sure we don't handle incoming sdp twice + if(self.remoteSDP != nil && [iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) + { + DDLogWarn(@"Got new remote sdp but we already got one, ignoring! MITM/DDOS??"); + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"cancel"} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"conflict" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], + ] andData:nil]]; + [self.account send:errorIq]; + return; + } + NSString* rawSDP; NSString* type; //raw sdp alongside jingle mode @@ -1266,14 +1313,18 @@ -(void) processIncomingSDP:(NSNotification*) notification } else { - //handle candidates in initial sdp (our webrtc lib does not like them --> fake transport-info iqs for these) - //(candidates in initial jingle are allowed by xep!) - if([iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"] || [iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) + //save omemo deviceid if we got a session-initiate for this incoming call + if(self.direction == MLCallDirectionIncoming && [iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) + self.remoteOmemoDeviceId = [iqNode findFirst:@"{urn:xmpp:jingle:1}jingle/{urn:xmpp:jingle:1}content/{urn:xmpp:jingle:transports:ice-udp:1}transport/{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}fingerprint/{eu.siacs.conversations.axolotl}encrypted/header@sid|uint"]; + + if([iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) { - // don't change iqNode directly to not influence code outside of this method - MLXMLNode* copyWithoutCandidates = [iqNode copy]; + //don't change iqNode directly to not influence code outside of this method + iqNode = [iqNode copy]; + //handle candidates in initial sdp (our webrtc lib does not like them --> fake transport-info iqs for these) + //(candidates in initial jingle are allowed by xep!) @synchronized(self.candidateQueue) { - for(MLXMLNode* content in [copyWithoutCandidates find:@"{urn:xmpp:jingle:1}jingle/content"]) + for(MLXMLNode* content in [iqNode find:@"{urn:xmpp:jingle:1}jingle/content"]) { MLXMLNode* transport = [content findFirst:@"{urn:xmpp:jingle:transports:ice-udp:1}transport"]; for(MLXMLNode* candidate in [transport find:@"{urn:xmpp:jingle:transports:ice-udp:1}candidate"]) @@ -1294,9 +1345,23 @@ -(void) processIncomingSDP:(NSNotification*) notification } } } - // don't change iqNode directly to not influence code outside of this method - iqNode = (XMPPIQ*)copyWithoutCandidates; + //decrypt fingerprint, if needed (use iqNode copy created above to not influence code outside of this method) + if(![self decryptFingerprintsInIqNode:iqNode]) + { + DDLogError(@"Could not decrypt remote SDP offer/response fingerprint with OMEMO!"); + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not decrypt call with OMEMO!"], + ] andData:nil]]; + [self.account send:errorIq]; + + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; + return; + } } + + //now handle the jingle offer/response or terminate nodes and convert jingle xml to sdp if([iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) { if(self.direction != MLCallDirectionOutgoing) @@ -1347,6 +1412,8 @@ -(void) processIncomingSDP:(NSNotification*) notification [self handleEndCallActionWithReason:MLCallFinishReasonError]; return; } + + //convert raw sdp string to RTCSessionDescription object RTCSessionDescription* resultSDP = [[RTCSessionDescription alloc] initWithType:[RTCSessionDescription typeForString:type] sdp:rawSDP]; if(resultSDP == nil) { @@ -1361,6 +1428,9 @@ -(void) processIncomingSDP:(NSNotification*) notification return; } DDLogInfo(@"%@: Got remote SDP for call: %@", self, resultSDP); + @synchronized(self.candidateQueue) { + self.remoteSDP = iqNode; + } //this is blocking (e.g. no need for an inner @synchronized) weakify(self); @@ -1376,14 +1446,11 @@ -(void) processIncomingSDP:(NSNotification*) notification [self.account send:errorIq]; [self handleEndCallActionWithReason:MLCallFinishReasonError]; + return; } else { DDLogDebug(@"Successfully passed SDP to webRTCClient..."); - @synchronized(self.candidateQueue) { - self.remoteSDP = iqNode; - } - [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; //only send a "session-accept" if the remote is the initiator (e.g. this is an incoming call) if(self.direction == MLCallDirectionIncoming) { @@ -1391,12 +1458,27 @@ -(void) processIncomingSDP:(NSNotification*) notification [self.webRTCClient offerWithCompletion:^(RTCSessionDescription* _) { [self.webRTCClient answerWithCompletion:^(RTCSessionDescription* localSdp) { DDLogDebug(@"Sending SDP answer back..."); + NSArray* children = [HelperTools sdp2xml:localSdp.sdp withInitiator:NO]; + if(![self encryptFingerprintsInChildren:children]) + { + DDLogError(@"Could not encrypt local SDP response fingerprint with OMEMO!"); + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not encrypt call with OMEMO!"], + ] andData:nil]]; + [self.account send:errorIq]; + + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; + return; + } + [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; + XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ @"action": @"session-accept", @"sid": self.jmiid, - } andChildren:[HelperTools sdp2xml:localSdp.sdp withInitiator:NO] - andData:nil]]; + } andChildren:children andData:nil]]; [self.account send:sdpIQ]; @synchronized(self.candidateQueue) { @@ -1411,6 +1493,7 @@ -(void) processIncomingSDP:(NSNotification*) notification } else { + [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; @synchronized(self.candidateQueue) { DDLogDebug(@"Now handling queued candidate iqs: %lu", (unsigned long)self.candidateQueue.count); for(XMPPIQ* candidateIq in self.candidateQueue) @@ -1437,4 +1520,70 @@ -(void) handleAudioRouteChangeNotification:(NSNotification*) notification self.speaker = NO; } +-(BOOL) encryptFingerprintsInChildren:(NSArray*) children +{ + if(!self.contact.isEncrypted) + return YES; + if(self.remoteOmemoDeviceId == nil) + { + DDLogWarn(@"No remoteOmemoDeviceId given, but trying to encrypt fingerprint!"); + return NO; + } + + //see https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd + BOOL retval = NO; + for(MLXMLNode* child in children) + for(MLXMLNode* fingerprint in [child find:@"/{urn:xmpp:jingle:1}content/{urn:xmpp:jingle:transports:ice-udp:1}transport/{urn:xmpp:jingle:apps:dtls:0}fingerprint"]) + { + MLXMLNode* envelope = [self.account.omemo encryptString:fingerprint.data toDeviceids:@{ + self.contact.contactJid: [NSSet setWithArray:@[self.remoteOmemoDeviceId]], + }]; + if(envelope == nil) + { + DDLogWarn(@"Could not encrypt fingerprint with OMEMO!"); + return NO; + } + [fingerprint addChildNode:envelope]; + [fingerprint setXMLNS:@"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"]; + fingerprint.data = nil; + retval = YES; + } + return retval; //this is only true if at least one fingerprint could be found and encrypted +} + +-(BOOL) decryptFingerprintsInIqNode:(XMPPIQ*) iqNode +{ + if(!self.contact.isEncrypted) + return YES; + if(self.remoteOmemoDeviceId == nil) + { + DDLogWarn(@"No remoteOmemoDeviceId given, but trying to decrypt fingerprint!"); + return NO; + } + + //see https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd + BOOL retval = NO; + for(MLXMLNode* fingerprintNode in [iqNode find:@"{urn:xmpp:jingle:1}jingle/content/{urn:xmpp:jingle:transports:ice-udp:1}transport/{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}fingerprint"]) + { + //more than one omemo envelope means we are under attack + if([[fingerprintNode find:@"{eu.siacs.conversations.axolotl}encrypted"] count] > 1) + { + DDLogWarn(@"More than one OMEMO envelope found!"); + return NO; + } + NSString* decryptedFingerprint = [self.account.omemo decryptOmemoEnvelope:[fingerprintNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"] forSenderJid:iqNode.fromUser andReturnErrorString:NO]; + if(decryptedFingerprint == nil) + { + DDLogWarn(@"Could not decrypt OMEMO encrypted fingerprint!"); + return NO; + } + //remove omemo envelope, correct xmlns and add our decrypted fingerprint back in as text content + [fingerprintNode removeChildNode:[fingerprintNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"]]; + [fingerprintNode setXMLNS:@"urn:xmpp:jingle:apps:dtls:0"]; + fingerprintNode.data = decryptedFingerprint; + retval = YES; + } + return retval; //this is only true if at least one fingerprint could be found and deencrypted +} + @end diff --git a/Monal/Classes/MLOMEMO.h b/Monal/Classes/MLOMEMO.h index 538814e3b7..aeb4a2bdcd 100644 --- a/Monal/Classes/MLOMEMO.h +++ b/Monal/Classes/MLOMEMO.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @class xmpp; @class XMPPMessage; @class XMPPIQ; +@class MLXMLNode; @interface MLOMEMO : NSObject @property (nonatomic, strong) OmemoState* state; @@ -28,7 +29,9 @@ NS_ASSUME_NONNULL_BEGIN /* * encrypting / decrypting messages */ +-(MLXMLNode* _Nullable) encryptString:(NSString* _Nullable) message toDeviceids:(NSDictionary*>*) contactDeviceMap; -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullable) message toContact:(NSString*) toContact; +-(NSString* _Nullable) decryptOmemoEnvelope:(MLXMLNode*) envelope forSenderJid:(NSString*) senderJid andReturnErrorString:(BOOL) returnErrorString; -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticipantJid:(NSString* _Nullable) mucParticipantJid; -(NSSet*) knownDevicesForAddressName:(NSString*) addressName; diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 45c0b27769..63e4cce23c 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -843,6 +843,59 @@ -(BOOL) generateNewKeysIfNeeded return NO; } +-(MLXMLNode* _Nullable) encryptString:(NSString* _Nullable) message toDeviceids:(NSDictionary*>*) contactDeviceMap +{ + + MLXMLNode* encrypted = [[MLXMLNode alloc] initWithElement:@"encrypted" andNamespace:@"eu.siacs.conversations.axolotl"]; + + MLEncryptedPayload* encryptedPayload; + if(message) + { + // Encrypt message + encryptedPayload = [AESGcm encrypt:[message dataUsingEncoding:NSUTF8StringEncoding] keySize:KEY_SIZE]; + if(encryptedPayload == nil) + { + showErrorOnAlpha(self.account, @"Could not encrypt normal message: AESGcm error"); + return nil; + } + [encrypted addChildNode:[[MLXMLNode alloc] initWithElement:@"payload" andData:[HelperTools encodeBase64WithData:encryptedPayload.body]]]; + } + else + { + //there is no message that can be encrypted -> create new session keys (e.g. this is a key transport message) + NSData* newKey = [AESGcm genKey:KEY_SIZE]; + NSData* newIv = [AESGcm genIV]; + if(newKey == nil || newIv == nil) + { + showErrorOnAlpha(self.account, @"Could not create key or iv"); + return nil; + } + encryptedPayload = [[MLEncryptedPayload alloc] initWithKey:newKey iv:newIv]; + if(encryptedPayload == nil) + { + showErrorOnAlpha(self.account, @"Could not encrypt transport message: AESGcm error"); + return nil; + } + } + + //add crypto header with our own deviceid + MLXMLNode* header = [[MLXMLNode alloc] initWithElement:@"header" withAttributes:@{ + @"sid": [NSString stringWithFormat:@"%u", self.monalSignalStore.deviceid], + } andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"iv" andData:[HelperTools encodeBase64WithData:encryptedPayload.iv]], + ] andData:nil]; + + //add encryption for all given contacts' devices + for(NSString* recipient in contactDeviceMap) + { + DDLogVerbose(@"Adding encryption for devices of %@: %@", recipient, contactDeviceMap[recipient]); + [self addEncryptionKeyForAllDevices:contactDeviceMap[recipient] encryptForJid:recipient withEncryptedPayload:encryptedPayload withXMLHeader:header]; + } + + [encrypted addChildNode:header]; + return encrypted; +} + -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullable) message toContact:(NSString*) toContact { MLAssert(self.signalContext != nil, @"signalContext should be initiated."); @@ -884,63 +937,23 @@ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullab if(recipientDevices && recipientDevices.count > 0) contactDeviceMap[recipient] = recipientDevices; } - NSSet* myDevices = [NSSet setWithArray:[self.monalSignalStore knownDevicesForAddressName:self.account.connectionProperties.identity.jid]]; //check if we found omemo keys of at least one of the recipients or more than 1 own device, otherwise don't encrypt anything + NSSet* myDevices = [NSSet setWithArray:[self.monalSignalStore knownDevicesForAddressName:self.account.connectionProperties.identity.jid]]; if(contactDeviceMap.count > 0 || myDevices.count > 1) { - MLXMLNode* encrypted = [[MLXMLNode alloc] initWithElement:@"encrypted" andNamespace:@"eu.siacs.conversations.axolotl"]; - - MLEncryptedPayload* encryptedPayload; - if(message) - { - // Encrypt message - encryptedPayload = [AESGcm encrypt:[message dataUsingEncoding:NSUTF8StringEncoding] keySize:KEY_SIZE]; - if(encryptedPayload == nil) - { - showErrorOnAlpha(self.account, @"Could not encrypt message: AESGcm error"); - return; - } - [encrypted addChildNode:[[MLXMLNode alloc] initWithElement:@"payload" andData:[HelperTools encodeBase64WithData:encryptedPayload.body]]]; - } - else - { - //there is no message that can be encrypted -> create new session keys (e.g. this is a key transport message) - NSData* newKey = [AESGcm genKey:KEY_SIZE]; - NSData* newIv = [AESGcm genIV]; - if(newKey == nil || newIv == nil) - { - showErrorOnAlpha(self.account, @"Could not create key or iv"); - return; - } - encryptedPayload = [[MLEncryptedPayload alloc] initWithKey:newKey iv:newIv]; - if(encryptedPayload == nil) - { - showErrorOnAlpha(self.account, @"Could not encrypt message: AESGcm error"); - return; - } - } - - //add crypto header with our own deviceid - MLXMLNode* header = [[MLXMLNode alloc] initWithElement:@"header" withAttributes:@{ - @"sid": [NSString stringWithFormat:@"%u", self.monalSignalStore.deviceid], - } andChildren:@[ - [[MLXMLNode alloc] initWithElement:@"iv" andData:[HelperTools encodeBase64WithData:encryptedPayload.iv]], - ] andData:nil]; - - //add encryption for all of our recipients' devices - for(NSString* recipient in contactDeviceMap) + //add encryption for all of our own devices to contactDeviceMap + DDLogVerbose(@"Adding encryption for OWN (%@) devices to contactDeviceMap: %@", self.account.connectionProperties.identity.jid, myDevices); + contactDeviceMap[self.account.connectionProperties.identity.jid] = myDevices; + + //now encrypt everything to all collected deviceids + MLXMLNode* envelope = [self encryptString:message toDeviceids:contactDeviceMap]; + if(envelope == nil) { - DDLogVerbose(@"Adding encryption for devices of %@: %@", recipient, contactDeviceMap[recipient]); - [self addEncryptionKeyForAllDevices:contactDeviceMap[recipient] encryptForJid:recipient withEncryptedPayload:encryptedPayload withXMLHeader:header]; + DDLogError(@"Got nil envelope!"); + return; } - - //add encryption for all of our own devices - DDLogVerbose(@"Adding encryption for OWN (%@) devices: %@", self.account.connectionProperties.identity.jid, myDevices); - [self addEncryptionKeyForAllDevices:myDevices encryptForJid:self.account.connectionProperties.identity.jid withEncryptedPayload:encryptedPayload withXMLHeader:header]; - - [encrypted addChildNode:header]; - [messageNode addChildNode:encrypted]; + [messageNode addChildNode:envelope]; } } @@ -996,48 +1009,33 @@ -(void) addEncryptionKeyForAllDevices:(NSSet*) devices encryptForJid: [self removeQueuedKeyTransportElementsFor:encryptForJid andDevices:usedRids]; } --(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticipantJid:(NSString* _Nullable) mucParticipantJid +-(NSString* _Nullable) decryptOmemoEnvelope:(MLXMLNode*) envelope forSenderJid:(NSString*) senderJid andReturnErrorString:(BOOL) returnErrorString { - if(![messageNode check:@"{eu.siacs.conversations.axolotl}encrypted/header"]) + DDLogVerbose(@"OMEMO envelope: %@", envelope); + + if(![envelope check:@"header"]) { - showErrorOnAlpha(self.account, @"DecryptMessage called but the message has no encryption header"); + showErrorOnAlpha(self.account, @"decryptOmemoEnvelope called but the envelope has no encryption header"); return nil; } - BOOL isKeyTransportElement = ![messageNode check:@"{eu.siacs.conversations.axolotl}encrypted/payload"]; - - NSNumber* sid = [messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/header@sid|uint"]; - NSString* senderJid = nil; - if([messageNode check:@"/"]) - { - if(mucParticipantJid == nil) - { - DDLogError(@"Could not get muc participant jid and corresponding signal address of muc participant '%@': %@", messageNode.from, mucParticipantJid); -#ifdef IS_ALPHA - return [NSString stringWithFormat:@"Could not get muc participant jid and corresponding signal address of muc participant '%@': %@", messageNode.from, mucParticipantJid]; -#else - return nil; -#endif - } - else - senderJid = mucParticipantJid; - } - else - senderJid = messageNode.fromUser; + + BOOL isKeyTransportElement = ![envelope check:@"payload"]; + NSNumber* sid = [envelope findFirst:@"header@sid|uint"]; SignalAddress* address = [[SignalAddress alloc] initWithName:senderJid deviceId:(uint32_t)sid.unsignedIntValue]; if(!self.signalContext) { showErrorOnAlpha(self.account, @"Missing signal context in decrypt!"); - return NSLocalizedString(@"Error decrypting message", @""); + return !returnErrorString ? nil : NSLocalizedString(@"Error decrypting message", @""); } //don't try to decrypt our own messages (could be mirrored by MUC etc.) if([senderJid isEqualToString:self.account.connectionProperties.identity.jid] && sid.unsignedIntValue == self.monalSignalStore.deviceid) return nil; - NSData* messageKey = [messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/header/key#|base64", self.monalSignalStore.deviceid]; - BOOL devicePreKey = [[messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/header/key@prekey|bool", self.monalSignalStore.deviceid] boolValue]; + NSData* messageKey = [envelope findFirst:@"header/key#|base64", self.monalSignalStore.deviceid]; + BOOL devicePreKey = [[envelope findFirst:@"header/key@prekey|bool", self.monalSignalStore.deviceid] boolValue]; DDLogVerbose(@"Decrypting using:\nrid=%u --> messageKey=%@\nrid=%u --> isPreKey=%@", self.monalSignalStore.deviceid, messageKey, self.monalSignalStore.deviceid, bool2str(devicePreKey)); @@ -1050,7 +1048,7 @@ -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticip { DDLogError(@"Message was not encrypted for this device: %u", self.monalSignalStore.deviceid); [self rebuildSessionWithJid:senderJid forRid:sid]; - return [NSString stringWithFormat:NSLocalizedString(@"Message was not encrypted for this device. Please make sure the sender trusts deviceid %u.", @""), self.monalSignalStore.deviceid]; + return !returnErrorString ? nil : [NSString stringWithFormat:NSLocalizedString(@"Message was not encrypted for this device. Please make sure the sender trusts deviceid %u.", @""), self.monalSignalStore.deviceid]; } else { @@ -1080,10 +1078,10 @@ -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticip [self rebuildSessionWithJid:senderJid forRid:sid]; #ifdef IS_ALPHA if(isKeyTransportElement) - return [NSString stringWithFormat:@"There was an error decrypting this encrypted KEY TRANSPORT message (Signal error). To resolve this, try sending an encrypted message to this person. (%@)", error]; + return !returnErrorString ? nil : [NSString stringWithFormat:@"There was an error decrypting this encrypted KEY TRANSPORT message (Signal error). To resolve this, try sending an encrypted message to this person. (%@)", error]; #endif if(!isKeyTransportElement) - return [NSString stringWithFormat:NSLocalizedString(@"There was an error decrypting this encrypted message (Signal error). To resolve this, try sending an encrypted message to this person. (%@)", @""), error]; + return !returnErrorString ? nil : [NSString stringWithFormat:NSLocalizedString(@"There was an error decrypting this encrypted message (Signal error). To resolve this, try sending an encrypted message to this person. (%@)", @""), error]; return nil; } NSData* key; @@ -1095,10 +1093,10 @@ -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticip [self rebuildSessionWithJid:senderJid forRid:sid]; #ifdef IS_ALPHA if(isKeyTransportElement) - return @"There was an error decrypting this encrypted KEY TRANSPORT message (Signal error). To resolve this, try sending an encrypted message to this person."; + return !returnErrorString ? nil : @"There was an error decrypting this encrypted KEY TRANSPORT message (Signal error). To resolve this, try sending an encrypted message to this person."; #endif if(!isKeyTransportElement) - return NSLocalizedString(@"There was an error decrypting this encrypted message (Signal error). To resolve this, try sending an encrypted message to this person.", @""); + return !returnErrorString ? nil : NSLocalizedString(@"There was an error decrypting this encrypted message (Signal error). To resolve this, try sending an encrypted message to this person.", @""); return nil; } else @@ -1124,7 +1122,7 @@ -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticip { DDLogInfo(@"KeyTransportElement received from jid: %@ device: %@", senderJid, sid); #ifdef IS_ALPHA - return [NSString stringWithFormat:@"ALPHA_DEBUG_MESSAGE: KeyTransportElement received from jid: %@ device: %@", senderJid, sid]; + return !returnErrorString ? nil : [NSString stringWithFormat:@"ALPHA_DEBUG_MESSAGE: KeyTransportElement received from jid: %@ device: %@", senderJid, sid]; #else return nil; #endif @@ -1141,23 +1139,23 @@ -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticip if(key != nil) { - NSData* iv = [messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/header/iv#|base64"]; - NSData* decodedPayload = [messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/payload#|base64"]; + NSData* iv = [envelope findFirst:@"header/iv#|base64"]; + NSData* decodedPayload = [envelope findFirst:@"payload#|base64"]; if(iv == nil || iv.length != 12) { showErrorOnAlpha(self.account, @"Could not decrypt message: iv length: %lu", (unsigned long)iv.length); - return NSLocalizedString(@"Error while decrypting: iv.length != 12", @""); + return !returnErrorString ? nil : NSLocalizedString(@"Error while decrypting: iv.length != 12", @""); } if(decodedPayload == nil) { - return NSLocalizedString(@"Error: Received OMEMO message is empty", @""); + return !returnErrorString ? nil : NSLocalizedString(@"Error: Received OMEMO message is empty", @""); } NSData* decData = [AESGcm decrypt:decodedPayload withKey:key andIv:iv withAuth:auth]; if(decData == nil) { showErrorOnAlpha(self.account, @"Could not decrypt message with key that was decrypted. (GCM error)"); - return NSLocalizedString(@"Encrypted message was sent in an older format Monal can't decrypt. Please ask them to update their client. (GCM error)", @""); + return !returnErrorString ? nil : NSLocalizedString(@"Encrypted message was sent in an older format Monal can't decrypt. Please ask them to update their client. (GCM error)", @""); } else DDLogInfo(@"Successfully decrypted message, passing back cleartext string..."); @@ -1166,12 +1164,35 @@ -(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticip else { showErrorOnAlpha(self.account, @"Could not get omemo decryption key"); - return NSLocalizedString(@"Could not decrypt message", @""); + return !returnErrorString ? nil : NSLocalizedString(@"Could not decrypt message", @""); } } } } +-(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode withMucParticipantJid:(NSString* _Nullable) mucParticipantJid +{ + NSString* senderJid = nil; + if([messageNode check:@"/"]) + { + if(mucParticipantJid == nil) + { + DDLogError(@"Could not get muc participant jid and corresponding signal address of muc participant '%@': %@", messageNode.from, mucParticipantJid); +#ifdef IS_ALPHA + return [NSString stringWithFormat:@"Could not get muc participant jid and corresponding signal address of muc participant '%@': %@", messageNode.from, mucParticipantJid]; +#else + return nil; +#endif + } + else + senderJid = mucParticipantJid; + } + else + senderJid = messageNode.fromUser; + + return [self decryptOmemoEnvelope:[messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"] forSenderJid:senderJid andReturnErrorString:YES]; +} + $$instance_handler(handleDevicelistUnsubscribe, account.omemo, $$ID(xmpp*, account), $$ID(NSString*, jid), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason)) if(success == NO) { From b3ba87b61ac162c7584c5fe33c8334ff987f0584 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 28 Sep 2023 01:23:40 +0200 Subject: [PATCH 18/29] Enable WebRTC logging --- Monal/Classes/WebRTCClient.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Monal/Classes/WebRTCClient.swift b/Monal/Classes/WebRTCClient.swift index e56ec780ee..2252ffe61d 100644 --- a/Monal/Classes/WebRTCClient.swift +++ b/Monal/Classes/WebRTCClient.swift @@ -90,6 +90,8 @@ final class WebRTCClient: NSObject { @objc required init(iceServers: [RTCIceServer], audioOnly: Bool, forceRelay: Bool) { + RTCSetMinDebugLogLevel(.info) + var peerConnection = WebRTCClient.createPeerConnection(iceServers: iceServers, forceRelay: forceRelay) if peerConnection == nil { // try again with empty ice server list From 1752fc02692be1f6ba6051cc4d64b62b8329e22a Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Fri, 29 Sep 2023 02:01:36 +0200 Subject: [PATCH 19/29] Implement UI for omemo call encryption and handle ToFU This will show a yellow checkmark shield if the call was verified using omemo, but ToFU was used, a green checkmark, if the remote omemo fingerprint was manually verified and a red xmark if the call is not verified by omemo at all. --- Monal/Classes/AVCallUI.swift | 128 ++++++++++++++++++----- Monal/Classes/MLCall.h | 8 ++ Monal/Classes/MLCall.m | 192 ++++++++++++++++++++++------------- Monal/Classes/MLOMEMO.h | 2 + Monal/Classes/MLOMEMO.m | 12 +++ 5 files changed, 243 insertions(+), 99 deletions(-) diff --git a/Monal/Classes/AVCallUI.swift b/Monal/Classes/AVCallUI.swift index 51ad78c9aa..fc3b459683 100644 --- a/Monal/Classes/AVCallUI.swift +++ b/Monal/Classes/AVCallUI.swift @@ -30,6 +30,7 @@ struct AVCallUI: View { @StateObject private var call: ObservableKVOWrapper @StateObject private var contact: ObservableKVOWrapper @State private var showMicAlert = false + @State private var showSecurityHelpAlert: MLCallEncryptionState? = nil private var ringingPlayer: AVAudioPlayer! private var busyPlayer: AVAudioPlayer! private var errorPlayer: AVAudioPlayer! @@ -85,20 +86,56 @@ struct AVCallUI: View { Group { Spacer().frame(height: 24) - HStack { - switch MLCallDirection(rawValue:call.direction) { - case .incoming: - Image(systemName: "phone.arrow.down.left") - .resizable() - .frame(width: 20.0, height: 20.0) - .foregroundColor(.primary) - case .outgoing: - Image(systemName: "phone.arrow.up.right") - .resizable() - .frame(width: 20.0, height: 20.0) - .foregroundColor(.primary) - default: //should never be reached - Text("") + HStack(alignment: .top) { + VStack { + Spacer().frame(height: 8) + Button(action: { + //show dialog explaining different encryption states + self.showSecurityHelpAlert = MLCallEncryptionState(rawValue:call.encryptionState) + }, label: { + switch MLCallEncryptionState(rawValue:call.encryptionState) { + case .unknown: + Text("") + case .clear: + Image(systemName: "xmark.shield.fill") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.red) + Spacer().frame(width: 10) + case .toFU: + Image(systemName: "checkmark.shield.fill") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.yellow) + Spacer().frame(width: 10) + case .trusted: + Image(systemName: "checkmark.shield.fill") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.green) + Spacer().frame(width: 10) + default: //should never be reached + Text("") + } + }) + } + + VStack { + Spacer().frame(height: 8) + switch MLCallDirection(rawValue:call.direction) { + case .incoming: + Image(systemName: "phone.arrow.down.left") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.primary) + case .outgoing: + Image(systemName: "phone.arrow.up.right") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.primary) + default: //should never be reached + Text("") + } } Spacer().frame(width: 20) @@ -109,17 +146,20 @@ struct AVCallUI: View { Spacer().frame(width: 20) - Button(action: { - self.delegate.dismissWithoutAnimation() - if let activeChats = self.appDelegate.activeChats { - activeChats.presentChat(with:self.contact.obj) - } - }, label: { - Image(systemName: "text.bubble") - .resizable() - .frame(width: 28.0, height: 28.0) - .foregroundColor(.primary) - }) + VStack { + Spacer().frame(height: 8) + Button(action: { + self.delegate.dismissWithoutAnimation() + if let activeChats = self.appDelegate.activeChats { + activeChats.presentChat(with:self.contact.obj) + } + }, label: { + Image(systemName: "text.bubble") + .resizable() + .frame(width: 28.0, height: 28.0) + .foregroundColor(.primary) + }) + } } Spacer().frame(height: 16) @@ -162,7 +202,7 @@ struct AVCallUI: View { .bold() .foregroundColor(.primary) case .securityError: - Text("Call ended: could establish call encryption") + Text("Call ended: couldn't establish encryption") .bold() .foregroundColor(.primary) case .unanswered: @@ -377,6 +417,42 @@ struct AVCallUI: View { dismissButton: .default(Text("OK")) ) } + .richAlert(isPresented:$showSecurityHelpAlert, title:Text("Call security help").foregroundColor(.black)) { + VStack(alignment: .leading) { + HStack { + Image(systemName: "xmark.shield.fill") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.red) + Spacer().frame(width: 10) + Text("Red x-mark shield:") + }.font(Font.body.weight(showSecurityHelpAlert == .clear ? .heavy : .medium)) + Text("This means your call is encrypted, but the remote party could not be verified using OMEMO encryption.\nYour or the callee's XMPP server could possibly Man-In-The-Middle you.") + Spacer().frame(height: 20) + + HStack { + Image(systemName: "checkmark.shield.fill") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.yellow) + Spacer().frame(width: 10) + Text("Yellow checkmark shield:") + }.font(Font.body.weight(showSecurityHelpAlert == .toFU ? .heavy : .medium)) + Text("This means your call is encrypted and the remote party was verified using OMEMO encryption.\nBut since you did not manually verify the callee's OMEMO fingerprints, your or the callee's XMPP server could possibly have inserted their own OMEMO keys to Man-In-The-Middle you.") + Spacer().frame(height: 20) + + HStack { + Image(systemName: "checkmark.shield.fill") + .resizable() + .frame(width: 20.0, height: 20.0) + .foregroundColor(.green) + Spacer().frame(width: 10) + Text("Green checkmark shield:") + }.font(Font.body.weight(showSecurityHelpAlert == .trusted ? .heavy : .medium)) + Text("This means your call is encrypted and the remote party was verified using OMEMO encryption.\nYou manually verified the used OMEMO keys and no Man-In-The-Middle can take place.") + Spacer().frame(height: 20) + }.foregroundColor(.black) + } .onAppear { //force portrait mode and lock ui there UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") diff --git a/Monal/Classes/MLCall.h b/Monal/Classes/MLCall.h index 2eae2926bc..69b5d0c4b6 100644 --- a/Monal/Classes/MLCall.h +++ b/Monal/Classes/MLCall.h @@ -53,6 +53,13 @@ typedef NS_ENUM(NSUInteger, MLCallFinishReason) { MLCallFinishReasonError, //used for a call error }; +typedef NS_ENUM(NSUInteger, MLCallEncryptionState) { + MLCallEncryptionStateUnknown, + MLCallEncryptionStateClear, + MLCallEncryptionStateToFU, + MLCallEncryptionStateTrusted, +}; + @interface MLCall : NSObject @property (strong, readonly) NSString* description; @@ -61,6 +68,7 @@ typedef NS_ENUM(NSUInteger, MLCallFinishReason) { @property (nonatomic, strong, readonly) MLContact* contact; @property (nonatomic, readonly) MLCallType callType; @property (nonatomic, readonly) MLCallDirection direction; +@property (nonatomic, readonly) MLCallEncryptionState encryptionState; @property (nonatomic, readonly) MLCallState state; @property (nonatomic, readonly) MLCallFinishReason finishReason; @property (nonatomic, readonly) uint32_t durationTime; diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index 477b8c7289..39682cb6c1 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -39,6 +39,7 @@ @interface MLCall() @property (nonatomic, strong) MLContact* contact; @property (nonatomic) MLCallType callType; @property (nonatomic) MLCallDirection direction; +@property (nonatomic) MLCallEncryptionState encryptionState; @property (nonatomic, strong) MLXMLNode* _Nullable jmiPropose; @property (nonatomic, strong) MLXMLNode* _Nullable jmiProceed; @@ -62,6 +63,8 @@ @interface MLCall() @property (nonatomic, strong) MLXMLNode* remoteSDP; @property (nonatomic, strong) NSNumber* remoteOmemoDeviceId; @property (nonatomic, strong) NSMutableArray* candidateQueue; +@property (nonatomic, assign) BOOL isEncrypted; + @property (nonatomic, readonly) xmpp* account; @property (nonatomic, strong) MLVoIPProcessor* voipProcessor; @@ -94,6 +97,7 @@ -(instancetype) initWithUUID:(NSUUID*) uuid jmiid:(NSString*) jmiid contact:(MLC self.contact = contact; self.callType = callType; self.direction = direction; + self.encryptionState = MLCallEncryptionStateUnknown; self.isConnected = NO; self.wasConnectedOnce = NO; self.isReconnecting = NO; @@ -293,12 +297,12 @@ -(void) setJmiProceed:(MLXMLNode*) jmiProceed { @synchronized(self) { _jmiProceed = jmiProceed; - //try the style implemented in conversations - self.remoteOmemoDeviceId = [jmiProceed findFirst:@"{urn:xmpp:jingle-message:0}proceed/{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}device@id|uint"]; - //try the style documented by daniel at https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd - if(self.remoteOmemoDeviceId == nil) - self.remoteOmemoDeviceId = [jmiProceed findFirst:@"{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}device@id|uint"]; - DDLogInfo(@"Proceed set remote omemo deviceid to: %@", self.remoteOmemoDeviceId); + if(self.direction == MLCallDirectionOutgoing) + { + //see https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd + self.remoteOmemoDeviceId = [jmiProceed findFirst:@"{urn:xmpp:jingle-message:0}proceed/{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}device@id|uint"]; + DDLogInfo(@"Proceed set remote omemo deviceid to: %@", self.remoteOmemoDeviceId); + } if(self.direction == MLCallDirectionOutgoing && self.webRTCClient != nil) [self establishOutgoingConnection]; } @@ -463,7 +467,8 @@ -(void) migrateTo:(MLCall*) otherCall self.localSDP = otherCall.localSDP; //should be nil self.remoteSDP = otherCall.remoteSDP; //should be nil self.candidateQueue = otherCall.candidateQueue; //should be empty - self.remoteOmemoDeviceId = otherCall.remoteOmemoDeviceId; //depends on jmiPropose + self.remoteOmemoDeviceId = otherCall.remoteOmemoDeviceId; //depends on jmiProceed and should be empty + self.encryptionState = MLCallEncryptionStateUnknown; //depends on callstate >= connecting otherCall = nil; DDLogDebug(@"%@: Stopping all running timers...", [self short]); @@ -777,12 +782,15 @@ -(void) offerSDP DDLogDebug(@"WebRTC reported local SDP '%@', sending to '%@': %@", [RTCSessionDescription stringForType:sdp.type], self.fullRemoteJid, sdp.sdp); NSArray* children = [HelperTools sdp2xml:sdp.sdp withInitiator:YES]; - if(![self encryptFingerprintsInChildren:children]) + //we don't encrypt anything if the remote did not send us their deviceid + //we try to encrypt using omemo even if this contact is unencrypted (and continue without omemo, if it did not work out) + if(/*self.contact.isEncrypted &&*/ self.remoteOmemoDeviceId != nil && [self encryptFingerprintsInChildren:children]) { - DDLogError(@"Could not encrypt local SDP offer fingerprint with OMEMO!"); - [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; - return; + //we are encrypted now (if the remote can't decrypt this or answers with a cleartext fingerprint, we throw a security error later on) + self.encryptionState = [self encryptionTypeForDeviceid:self.remoteOmemoDeviceId]; } + else + self.encryptionState = MLCallEncryptionStateClear; XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ @"action": @"session-initiate", @@ -871,13 +879,18 @@ -(void) sendJmiProceed DDLogDebug(@"Accepting via JMI: %@", self); //xep 0353 mandates bare jid, but daniel will update it to mandate full jid XMPPMessage* jmiNode = [[XMPPMessage alloc] initWithType:kMessageChatType to:self.fullRemoteJid]; - [jmiNode addChildNode:[[MLXMLNode alloc] initWithElement:@"proceed" andNamespace:@"urn:xmpp:jingle-message:0" withAttributes:@{ + MLXMLNode* proceedElement = [[MLXMLNode alloc] initWithElement:@"proceed" andNamespace:@"urn:xmpp:jingle-message:0" withAttributes:@{ @"id": self.jmiid, - } andChildren:@[] andData:nil]]; + } andChildren:@[] andData:nil]; + //only offer omemo deviceid for encryption if encryption is enabled for this contact if(self.contact.isEncrypted) - [jmiNode addChildNode:[[MLXMLNode alloc] initWithElement:@"device" andNamespace:@"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification" withAttributes:@{ + { + //see https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd + [proceedElement addChildNode:[[MLXMLNode alloc] initWithElement:@"device" andNamespace:@"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification" withAttributes:@{ @"id": [self.account.omemo getDeviceId], } andChildren:@[] andData:nil]]; + } + [jmiNode addChildNode:proceedElement]; [jmiNode setStoreHint]; self.jmiProceed = jmiNode; [self.account send:jmiNode]; @@ -975,6 +988,7 @@ -(NSString*) description @"hasLocalSDP": bool2str(self.localSDP != nil), @"hasRemoteSDP": bool2str(self.remoteSDP != nil), @"remoteOmemoDeviceId": nilWrapper(self.remoteOmemoDeviceId), + @"encryptionState": @(self.encryptionState), } ]; } @@ -1317,7 +1331,7 @@ -(void) processIncomingSDP:(NSNotification*) notification if(self.direction == MLCallDirectionIncoming && [iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) self.remoteOmemoDeviceId = [iqNode findFirst:@"{urn:xmpp:jingle:1}jingle/{urn:xmpp:jingle:1}content/{urn:xmpp:jingle:transports:ice-udp:1}transport/{http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification}fingerprint/{eu.siacs.conversations.axolotl}encrypted/header@sid|uint"]; - if([iqNode findFirst:@"{urn:xmpp:jingle:1}jingle"]) + if([iqNode check:@"{urn:xmpp:jingle:1}jingle"]) { //don't change iqNode directly to not influence code outside of this method iqNode = [iqNode copy]; @@ -1346,18 +1360,39 @@ -(void) processIncomingSDP:(NSNotification*) notification } } //decrypt fingerprint, if needed (use iqNode copy created above to not influence code outside of this method) - if(![self decryptFingerprintsInIqNode:iqNode]) + //only encrypt if encryption is enabled for this contact + if(self.contact.isEncrypted) { - DDLogError(@"Could not decrypt remote SDP offer/response fingerprint with OMEMO!"); - XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; - [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ - [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], - [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not decrypt call with OMEMO!"], - ] andData:nil]]; - [self.account send:errorIq]; + //if this is a session-initiate and we can decrypt the fingerprint, this call is encrypted now + //(if we can NOT decrypt anything we are simply unencrypted, but still continue) + if([iqNode check:@"{urn:xmpp:jingle:1}jingle"]) + { + if(self.remoteOmemoDeviceId != nil && [self decryptFingerprintsInIqNode:iqNode]) + self.encryptionState = [self encryptionTypeForDeviceid:self.remoteOmemoDeviceId]; + else + self.encryptionState = MLCallEncryptionStateClear; + } - [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; - return; + //if this is a session-accept after sending an encrypted session-initiate and we can NOT decrypt the fingerprint, + //this call is a security error (if we can decrypt it, everything is fine and the call is secured) + if([iqNode check:@"{urn:xmpp:jingle:1}jingle"]) + { + //we don't need to check self.remoteOmemoDeviceId, because self.encryptionState will only be different to + //MLCallEncryptionStateClear if the deviceid is not nil + if(self.encryptionState != MLCallEncryptionStateClear && ![self decryptFingerprintsInIqNode:iqNode]) + { + DDLogError(@"Could not decrypt remote SDP offer/response fingerprint with OMEMO!"); + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not decrypt call with OMEMO!"], + ] andData:nil]]; + [self.account send:errorIq]; + + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; + return; + } + } } } @@ -1454,41 +1489,44 @@ -(void) processIncomingSDP:(NSNotification*) notification //only send a "session-accept" if the remote is the initiator (e.g. this is an incoming call) if(self.direction == MLCallDirectionIncoming) { - //it seems we have to create an offer and ignore it before we can create the desired answer - [self.webRTCClient offerWithCompletion:^(RTCSessionDescription* _) { - [self.webRTCClient answerWithCompletion:^(RTCSessionDescription* localSdp) { - DDLogDebug(@"Sending SDP answer back..."); - NSArray* children = [HelperTools sdp2xml:localSdp.sdp withInitiator:NO]; - if(![self encryptFingerprintsInChildren:children]) - { - DDLogError(@"Could not encrypt local SDP response fingerprint with OMEMO!"); - XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; - [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ - [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], - [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not encrypt call with OMEMO!"], - ] andData:nil]]; - [self.account send:errorIq]; - - [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; - return; - } - [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; + [self.webRTCClient answerWithCompletion:^(RTCSessionDescription* localSdp) { + DDLogDebug(@"Sending SDP answer back..."); + NSArray* children = [HelperTools sdp2xml:localSdp.sdp withInitiator:NO]; + //we got a session-initiate jingle iq + //--> self.encryptionState will NOT be MLCallEncryptionStateClear, if that iq contained an encrypted fingerprint, + //--> self.encryptionState WILL be MLCallEncryptionStateClear, if it did not contain such an encrypted fingerprint + //(in this case we just don't try to decrypt anything, the call will simply be unencrypted but continue) + //we don't need to check self.remoteOmemoDeviceId, because self.encryptionState will only be different to + //MLCallEncryptionStateClear if the deviceid is not nil + if(self.encryptionState != MLCallEncryptionStateClear && ![self encryptFingerprintsInChildren:children]) + { + DDLogError(@"Could not encrypt local SDP response fingerprint with OMEMO!"); + XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode]; + [errorIq addChildNode:[[MLXMLNode alloc] initWithElement:@"error" withAttributes:@{@"type": @"modify"} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"not-acceptable" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas"], + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-stanzas" andData:@"Could not encrypt call with OMEMO!"], + ] andData:nil]]; + [self.account send:errorIq]; - XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; - [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ - @"action": @"session-accept", - @"sid": self.jmiid, - } andChildren:children andData:nil]]; - [self.account send:sdpIQ]; + [self handleEndCallActionWithReason:MLCallFinishReasonSecurityError]; + return; + } + [self.account send:[[XMPPIQ alloc] initAsResponseTo:iqNode]]; + + XMPPIQ* sdpIQ = [[XMPPIQ alloc] initWithType:kiqSetType to:self.fullRemoteJid]; + [sdpIQ addChildNode:[[MLXMLNode alloc] initWithElement:@"jingle" andNamespace:@"urn:xmpp:jingle:1" withAttributes:@{ + @"action": @"session-accept", + @"sid": self.jmiid, + } andChildren:children andData:nil]]; + [self.account send:sdpIQ]; + + @synchronized(self.candidateQueue) { + self.localSDP = sdpIQ; - @synchronized(self.candidateQueue) { - self.localSDP = sdpIQ; - - DDLogDebug(@"Now handling queued candidate iqs: %lu", (unsigned long)self.candidateQueue.count); - for(XMPPIQ* candidateIq in self.candidateQueue) - [self processRemoteICECandidate:candidateIq]; - } - }]; + DDLogDebug(@"Now handling queued candidate iqs: %lu", (unsigned long)self.candidateQueue.count); + for(XMPPIQ* candidateIq in self.candidateQueue) + [self processRemoteICECandidate:candidateIq]; + } }]; } else @@ -1520,15 +1558,24 @@ -(void) handleAudioRouteChangeNotification:(NSNotification*) notification self.speaker = NO; } --(BOOL) encryptFingerprintsInChildren:(NSArray*) children +-(MLCallEncryptionState) encryptionTypeForDeviceid:(NSNumber* _Nonnull) deviceid { - if(!self.contact.isEncrypted) - return YES; - if(self.remoteOmemoDeviceId == nil) + NSNumber* trustLevel = [self.account.omemo getTrustLevelForJid:self.contact.contactJid andDeviceId:deviceid]; + if(trustLevel == nil) + return MLCallEncryptionStateClear; + switch(trustLevel.intValue) { - DDLogWarn(@"No remoteOmemoDeviceId given, but trying to encrypt fingerprint!"); - return NO; + case MLOmemoTrusted: return MLCallEncryptionStateTrusted; + case MLOmemoToFU: return MLCallEncryptionStateToFU; + default: return MLCallEncryptionStateClear; } +} + +-(BOOL) encryptFingerprintsInChildren:(NSArray*) children +{ + //don't try to encrypt if the remote deviceid is not trusted + if([self encryptionTypeForDeviceid:self.remoteOmemoDeviceId] == MLCallEncryptionStateClear) + return NO; //see https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd BOOL retval = NO; @@ -1548,18 +1595,15 @@ -(BOOL) encryptFingerprintsInChildren:(NSArray*) children fingerprint.data = nil; retval = YES; } - return retval; //this is only true if at least one fingerprint could be found and encrypted + //this is only true if at least one fingerprint could be found and encrypted (this is normally true) + return retval; } -(BOOL) decryptFingerprintsInIqNode:(XMPPIQ*) iqNode { - if(!self.contact.isEncrypted) - return YES; - if(self.remoteOmemoDeviceId == nil) - { - DDLogWarn(@"No remoteOmemoDeviceId given, but trying to decrypt fingerprint!"); + //don't try to decrypt if the remote deviceid is not trusted + if([self encryptionTypeForDeviceid:self.remoteOmemoDeviceId] == MLCallEncryptionStateClear) return NO; - } //see https://gist.github.com/iNPUTmice/aa4fc0aeea6ce5fb0e0fe04baca842cd BOOL retval = NO; @@ -1571,7 +1615,7 @@ -(BOOL) decryptFingerprintsInIqNode:(XMPPIQ*) iqNode DDLogWarn(@"More than one OMEMO envelope found!"); return NO; } - NSString* decryptedFingerprint = [self.account.omemo decryptOmemoEnvelope:[fingerprintNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"] forSenderJid:iqNode.fromUser andReturnErrorString:NO]; + NSString* decryptedFingerprint = [self.account.omemo decryptOmemoEnvelope:[fingerprintNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted"] forSenderJid:self.contact.contactJid andReturnErrorString:NO]; if(decryptedFingerprint == nil) { DDLogWarn(@"Could not decrypt OMEMO encrypted fingerprint!"); @@ -1583,7 +1627,9 @@ -(BOOL) decryptFingerprintsInIqNode:(XMPPIQ*) iqNode fingerprintNode.data = decryptedFingerprint; retval = YES; } - return retval; //this is only true if at least one fingerprint could be found and deencrypted + //this is only true if at least one fingerprint could be found and decrypted + //(that could be false, if the remote did something weird or a MITM changed something) + return retval; } @end diff --git a/Monal/Classes/MLOMEMO.h b/Monal/Classes/MLOMEMO.h index aeb4a2bdcd..69e5a2f500 100644 --- a/Monal/Classes/MLOMEMO.h +++ b/Monal/Classes/MLOMEMO.h @@ -8,6 +8,7 @@ #import #import "OmemoState.h" +#import "MLSignalStore.h" NS_ASSUME_NONNULL_BEGIN @@ -39,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN -(void) addIdentityManually:(SignalAddress*) address identityKey:(NSData* _Nonnull) identityKey; -(void) updateTrust:(BOOL) trust forAddress:(SignalAddress*)address; -(NSNumber*) getTrustLevel:(SignalAddress*)address identityKey:(NSData*)identityKey; +-(NSNumber* _Nullable) getTrustLevelForJid:(NSString*) jid andDeviceId:(NSNumber*) deviceid; -(NSData*) getIdentityForAddress:(SignalAddress*) address; -(BOOL) isSessionBrokenForJid:(NSString*) jid andDeviceId:(NSNumber*) rid; -(void) deleteDeviceForSource:(NSString*) source andRid:(NSNumber*) rid; diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 63e4cce23c..5b4bbad08c 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -957,6 +957,18 @@ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullab } } +-(NSNumber* _Nullable) getTrustLevelForJid:(NSString*) jid andDeviceId:(NSNumber*) deviceid +{ + SignalAddress* address = [[SignalAddress alloc] initWithName:jid deviceId:(uint32_t)deviceid.unsignedIntValue]; + NSData* identity = [self.monalSignalStore getIdentityForAddress:address]; + if(!identity) + { + showErrorOnAlpha(self.account, @"Could not get Identity for: %@ device id %@", jid, deviceid); + return nil; + } + return [self getTrustLevel:address identityKey:identity]; +} + -(void) addEncryptionKeyForAllDevices:(NSSet*) devices encryptForJid:(NSString*) encryptForJid withEncryptedPayload:(MLEncryptedPayload*) encryptedPayload withXMLHeader:(MLXMLNode*) xmlHeader { NSMutableSet* usedRids = [NSMutableSet new]; From 29a5d5c321c7d8e41527a49ee3a6d27d9e030789 Mon Sep 17 00:00:00 2001 From: lissine Date: Sat, 30 Sep 2023 08:40:33 +0100 Subject: [PATCH 20/29] Launch Screen: switch the copyright notice to 2023 --- Monal/Monal-iOS/Launch Screen.storyboard | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Monal/Monal-iOS/Launch Screen.storyboard b/Monal/Monal-iOS/Launch Screen.storyboard index 2fdd2351fd..0398ef4ad6 100644 --- a/Monal/Monal-iOS/Launch Screen.storyboard +++ b/Monal/Monal-iOS/Launch Screen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -19,7 +19,7 @@ -