From a48327f6f6a475b71b71b5ca36811a8473d6b204 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 26 Sep 2023 17:35:19 +1000 Subject: [PATCH] [WIP] Updated to the latest libSession and started plugging in Updated the updated group messages encryption/decryption logic to work with the latest libSession Disabled manual PN triggering for updated group messages --- LibSession-Util | 2 +- .../Messages/Message+Destination.swift | 3 ++ .../Sending & Receiving/MessageReceiver.swift | 14 +++++-- .../Sending & Receiving/MessageSender.swift | 17 +++++--- .../SessionUtil+SharedGroup.swift | 12 +++++- .../Utilities/MessageWrapper.swift | 15 +++++-- .../LibSessionUtil/LibSessionSpec.swift | 39 +++++++++++++++++++ 7 files changed, 86 insertions(+), 16 deletions(-) diff --git a/LibSession-Util b/LibSession-Util index e21302b59..916a47fcd 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit e21302b598b5dde44fd72566d8b89d4d41cbb9ce +Subproject commit 916a47fcd347332256b744457362639c405aebef diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 1fd865a53..aaf700550 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -21,6 +21,9 @@ public extension Message { public var defaultNamespace: SnodeAPI.Namespace? { switch self { case .contact: return .`default` + case .closedGroup(let key) where SessionId.Prefix(from: key) == .group: + return .groupMessages + case .closedGroup: return .legacyClosedGroup default: return nil } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index c4b72ce49..007a6955a 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -36,7 +36,7 @@ public enum MessageReceiver { ) case (_, .community(let openGroupId, let messageSender, let timestamp, let messageServerId)): - plaintext = data + plaintext = data.removePadding() // Remove the padding sender = messageSender sentTimestamp = UInt64(floor(timestamp * 1000)) // Convert to ms for database consistency openGroupServerMessageId = UInt64(messageServerId) @@ -62,6 +62,7 @@ public enum MessageReceiver { using: dependencies ) + plaintext = plaintext.removePadding() // Remove the padding sentTimestamp = UInt64(floor(timestamp * 1000)) // Convert to ms for database consistency openGroupServerMessageId = UInt64(messageServerId) threadVariant = .contact @@ -85,6 +86,7 @@ public enum MessageReceiver { ciphertext: ciphertext, using: userX25519KeyPair ) + plaintext = plaintext.removePadding() // Remove the padding sentTimestamp = envelope.timestamp openGroupServerMessageId = nil threadVariant = .contact @@ -105,13 +107,16 @@ public enum MessageReceiver { ) guard - let envelope: SNProtoEnvelope = try? MessageWrapper.unwrap(data: plaintextEnvelope), + let envelope: SNProtoEnvelope = try? MessageWrapper.unwrap( + data: plaintextEnvelope, + includesWebSocketMessage: false + ), let envelopeContent: Data = envelope.content else { SNLog("Failed to unwrap data for message from 'default' namespace.") throw MessageReceiverError.invalidMessage } - plaintext = envelopeContent + plaintext = envelopeContent // Padding already removed for updated groups sentTimestamp = envelope.timestamp openGroupServerMessageId = nil threadVariant = .group @@ -158,6 +163,7 @@ public enum MessageReceiver { } (plaintext, sender) = try decrypt(keyPairs: encryptionKeyPairs) + plaintext = plaintext.removePadding() // Remove the padding sentTimestamp = envelope.timestamp openGroupServerMessageId = nil threadVariant = .legacyGroup @@ -175,7 +181,7 @@ public enum MessageReceiver { } } - let proto: SNProtoContent = try Result(SNProtoContent.parseData(plaintext.removePadding())) + let proto: SNProtoContent = try Result(SNProtoContent.parseData(plaintext)) .onFailure { SNLog("Couldn't parse proto due to error: \($0).") } .successOrThrow() let message: Message = try Message.createMessageFrom(proto, sender: sender) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 984a0d54f..8fdaec2a2 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -179,11 +179,6 @@ public final class MessageSender { .successOrThrow() .base64EncodedString() - // Config messages should be sent directly rather than via this method - case (.contact, _): throw MessageSenderError.invalidConfigMessageHandling - case (.closedGroup(let groupPublicKey), _) where SessionId.Prefix(from: groupPublicKey) == .group: - throw MessageSenderError.invalidConfigMessageHandling - // Updated group messages should be wrapped _before_ encrypting case (.closedGroup(let groupPublicKey), .groupMessages) where SessionId.Prefix(from: groupPublicKey) == .group: return try SessionUtil @@ -204,6 +199,11 @@ public final class MessageSender { ) .base64EncodedString() + // Config messages should be sent directly rather than via this method + case (.contact, _): throw MessageSenderError.invalidConfigMessageHandling + case (.closedGroup(let groupPublicKey), _) where SessionId.Prefix(from: groupPublicKey) == .group: + throw MessageSenderError.invalidConfigMessageHandling + // Legacy groups used a `05` prefix case (.closedGroup(let groupPublicKey), _): guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else { @@ -262,6 +262,13 @@ public final class MessageSender { details: NotifyPushServerJob.Details(message: snodeMessage) ) let shouldNotify: Bool = { + // New groups only run via the updated push server so don't notify + switch destination { + case .closedGroup(let key) where SessionId.Prefix(from: key) == .group: + return false + default: break + } + switch updatedMessage { case is VisibleMessage, is UnsendRequest: return !isSyncMessage case let callMessage as CallMessage: diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift index 3473a4e0c..3a1ec36ca 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift @@ -327,23 +327,31 @@ internal extension SessionUtil { guard case .groupKeys(let conf, _, _) = config else { throw SessionUtilError.invalidConfigObject } var ciphertext: [UInt8] = Array(ciphertext) + var cSessionId: [CChar] = [CChar](repeating: 0, count: 67) var maybePlaintext: UnsafeMutablePointer? = nil var plaintextLen: Int = 0 - groups_keys_decrypt_message( + let didDecrypt: Bool = groups_keys_decrypt_message( conf, &ciphertext, ciphertext.count, + &cSessionId, &maybePlaintext, &plaintextLen ) + // If we got a reported failure then just stop here + guard didDecrypt else { throw MessageReceiverError.decryptionFailed } + + // We need to manually free 'maybePlaintext' upon a successful decryption + defer { maybePlaintext?.deallocate() } + guard plaintextLen > 0, let plaintext: Data = maybePlaintext .map({ Data(bytes: $0, count: plaintextLen) }) else { throw MessageReceiverError.decryptionFailed } - return (plaintext, "") + return (plaintext, String(cString: cSessionId)) } ?? { throw MessageReceiverError.decryptionFailed }() } } diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift index 28896f4bd..fd9711c67 100644 --- a/SessionMessagingKit/Utilities/MessageWrapper.swift +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -80,11 +80,18 @@ public enum MessageWrapper { } /// - Note: `data` shouldn't be base 64 encoded. - public static func unwrap(data: Data) throws -> SNProtoEnvelope { + public static func unwrap( + data: Data, + includesWebSocketMessage: Bool = true + ) throws -> SNProtoEnvelope { do { - let webSocketMessage = try WebSocketProtoWebSocketMessage.parseData(data) - let envelope = webSocketMessage.request!.body! - return try SNProtoEnvelope.parseData(envelope) + let envelopeData: Data = try { + guard includesWebSocketMessage else { return data } + + let webSocketMessage = try WebSocketProtoWebSocketMessage.parseData(data) + return webSocketMessage.request!.body! + }() + return try SNProtoEnvelope.parseData(envelopeData) } catch let error { SNLog("Failed to unwrap data: \(error).") throw Error.failedToUnwrapData diff --git a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift index efd74f0ac..448c08997 100644 --- a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift @@ -1805,6 +1805,20 @@ fileprivate extension LibSessionSpec { // MARK: - GROUP_INFO fileprivate extension LibSessionSpec { + /// This function can be used to regenerate the hard-coded `keysDump` value if needed due to `libSession` changes + /// resulting in the dump changing + private static func generateKeysDump(for keysConf: UnsafeMutablePointer?) throws -> String { + var dumpResult: UnsafeMutablePointer? = nil + var dumpResultLen: Int = 0 + try CExceptionHelper.performSafely { + groups_keys_dump(keysConf, &dumpResult, &dumpResultLen) + } + + let dumpData: Data = Data(bytes: dumpResult!, count: dumpResultLen) + dumpResult?.deallocate() + return dumpData.toHexString() + } + class func groupInfoSpec() { context("GROUP_INFO") { // MARK: -- generates config correctly @@ -1814,8 +1828,24 @@ fileprivate extension LibSessionSpec { hex: "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210" ) + // Since we can't test the group without encryption keys and the C API doesn't have + // a way to manually provide encryption keys we needed to create a dump with valid + // key data and load that in so we can test the other cases, this dump contains a + // single admin member and a single encryption key + let keysDump: Data = Data(hex: "64363a6163746976656c65343a6b6579736c6565") + let cachedKeysDump: (data: UnsafePointer, length: Int)? = keysDump.withUnsafeBytes { unsafeBytes in + return unsafeBytes.baseAddress.map { + ( + $0.assumingMemoryBound(to: UInt8.self), + unsafeBytes.count + ) + } + } + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: userSeed) let keyPair: KeyPair = Crypto().generate(.ed25519KeyPair(seed: seed))! + var userEdSK: [UInt8] = identity.ed25519KeyPair.secretKey var edPK: [UInt8] = keyPair.publicKey var edSK: [UInt8] = keyPair.secretKey @@ -1828,9 +1858,18 @@ fileprivate extension LibSessionSpec { var conf: UnsafeMutablePointer? = nil expect(groups_info_init(&conf, &edPK, &edSK, nil, 0, &error)).to(equal(0)) + var membersConf: UnsafeMutablePointer? = nil + expect(groups_members_init(&membersConf, &edPK, &edSK, nil, 0, &error)).to(equal(0)) + + var keysConf1: UnsafeMutablePointer? = nil + expect(groups_keys_init(&keysConf1, &userEdSK, &edPK, &edSK, conf, membersConf, cachedKeysDump?.data, (cachedKeysDump?.length ?? 0), &error)).to(equal(0)) + var conf2: UnsafeMutablePointer? = nil expect(groups_info_init(&conf2, &edPK, &edSK, nil, 0, &error)).to(equal(0)) + var keysConf2: UnsafeMutablePointer? = nil + expect(groups_keys_init(&keysConf2, &userEdSK, &edPK, &edSK, conf2, membersConf, cachedKeysDump?.data, (cachedKeysDump?.length ?? 0), &error)).to(equal(0)) + expect(groups_info_set_name(conf, "GROUP Name")).to(equal(0)) expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue())