2020-11-09 00:58:47 +01:00
|
|
|
import SessionUtilitiesKit
|
2020-11-05 23:17:05 +01:00
|
|
|
|
2020-12-01 09:45:42 +01:00
|
|
|
public enum MessageReceiver {
|
2021-02-09 23:51:28 +01:00
|
|
|
private static var lastEncryptionKeyPairRequest: [String:Date] = [:]
|
2020-11-08 02:34:08 +01:00
|
|
|
|
2020-12-01 09:45:42 +01:00
|
|
|
public enum Error : LocalizedError {
|
2020-11-27 05:13:42 +01:00
|
|
|
case duplicateMessage
|
2020-11-06 06:31:56 +01:00
|
|
|
case invalidMessage
|
2020-11-07 23:00:10 +01:00
|
|
|
case unknownMessage
|
2020-11-09 00:33:26 +01:00
|
|
|
case unknownEnvelopeType
|
2020-12-10 06:12:22 +01:00
|
|
|
case noUserX25519KeyPair
|
|
|
|
case noUserED25519KeyPair
|
|
|
|
case invalidSignature
|
2020-11-09 00:33:26 +01:00
|
|
|
case noData
|
2020-11-17 06:23:13 +01:00
|
|
|
case senderBlocked
|
2020-11-18 05:53:45 +01:00
|
|
|
case noThread
|
2020-11-23 05:58:48 +01:00
|
|
|
case selfSend
|
2020-12-10 06:12:22 +01:00
|
|
|
case decryptionFailed
|
2020-11-06 09:32:09 +01:00
|
|
|
case invalidGroupPublicKey
|
2021-01-04 05:30:13 +01:00
|
|
|
case noGroupKeyPair
|
2020-11-06 06:31:56 +01:00
|
|
|
|
2020-12-01 09:45:42 +01:00
|
|
|
public var isRetryable: Bool {
|
2020-11-18 05:36:51 +01:00
|
|
|
switch self {
|
2021-05-28 01:09:56 +02:00
|
|
|
case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType,
|
|
|
|
.invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed: return false
|
2020-11-18 05:36:51 +01:00
|
|
|
default: return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-01 09:45:42 +01:00
|
|
|
public var errorDescription: String? {
|
2020-11-06 06:31:56 +01:00
|
|
|
switch self {
|
2020-11-27 05:13:42 +01:00
|
|
|
case .duplicateMessage: return "Duplicate message."
|
2020-11-06 06:31:56 +01:00
|
|
|
case .invalidMessage: return "Invalid message."
|
2020-11-07 23:00:10 +01:00
|
|
|
case .unknownMessage: return "Unknown message type."
|
2020-11-09 00:33:26 +01:00
|
|
|
case .unknownEnvelopeType: return "Unknown envelope type."
|
2020-12-10 06:12:22 +01:00
|
|
|
case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair."
|
|
|
|
case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair."
|
|
|
|
case .invalidSignature: return "Invalid message signature."
|
2020-11-09 00:33:26 +01:00
|
|
|
case .noData: return "Received an empty envelope."
|
2020-11-17 06:23:13 +01:00
|
|
|
case .senderBlocked: return "Received a message from a blocked user."
|
2020-11-18 05:53:45 +01:00
|
|
|
case .noThread: return "Couldn't find thread for message."
|
2020-12-10 06:12:22 +01:00
|
|
|
case .selfSend: return "Message addressed at self."
|
|
|
|
case .decryptionFailed: return "Decryption failed."
|
2020-11-06 09:32:09 +01:00
|
|
|
// Shared sender keys
|
|
|
|
case .invalidGroupPublicKey: return "Invalid group public key."
|
2021-01-04 05:30:13 +01:00
|
|
|
case .noGroupKeyPair: return "Missing group key pair."
|
2020-11-06 06:31:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 07:46:35 +01:00
|
|
|
public static func parse(
|
|
|
|
_ data: Data,
|
|
|
|
openGroupMessageServerID: UInt64?,
|
|
|
|
openGroupServerPublicKey: String? = nil,
|
|
|
|
isOutgoing: Bool? = nil,
|
|
|
|
otherBlindedPublicKey: String? = nil,
|
|
|
|
isRetry: Bool = false,
|
|
|
|
using transaction: Any
|
|
|
|
) throws -> (Message, SNProtoContent) {
|
2020-12-02 06:25:16 +01:00
|
|
|
let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey()
|
2020-11-30 04:20:36 +01:00
|
|
|
let isOpenGroupMessage = (openGroupMessageServerID != nil)
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2020-11-09 00:33:26 +01:00
|
|
|
// Parse the envelope
|
2020-11-17 06:23:13 +01:00
|
|
|
let envelope = try SNProtoEnvelope.parseData(data)
|
2020-12-02 06:25:16 +01:00
|
|
|
let storage = SNMessagingKitConfiguration.shared.storage
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2020-11-09 00:33:26 +01:00
|
|
|
// Decrypt the contents
|
2021-01-04 05:30:13 +01:00
|
|
|
guard let ciphertext = envelope.content else { throw Error.noData }
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2021-01-04 05:30:13 +01:00
|
|
|
var plaintext: Data!
|
|
|
|
var sender: String!
|
2020-11-18 05:53:45 +01:00
|
|
|
var groupPublicKey: String? = nil
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2020-11-30 01:00:28 +01:00
|
|
|
if isOpenGroupMessage {
|
|
|
|
(plaintext, sender) = (envelope.content!, envelope.source!)
|
2022-02-25 01:59:29 +01:00
|
|
|
}
|
|
|
|
else {
|
2020-11-30 01:00:28 +01:00
|
|
|
switch envelope.type {
|
2022-02-25 01:59:29 +01:00
|
|
|
case .sessionMessage:
|
|
|
|
// Default to 'standard' as the old code didn't seem to require an `envelope.source`
|
|
|
|
switch (SessionId.Prefix(from: envelope.source) ?? .standard) {
|
|
|
|
case .standard, .unblinded:
|
|
|
|
guard let userX25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else {
|
|
|
|
throw Error.noUserX25519KeyPair
|
|
|
|
}
|
|
|
|
|
|
|
|
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
|
|
|
|
|
|
|
|
case .blinded:
|
2022-03-03 07:46:35 +01:00
|
|
|
guard let otherBlindedPublicKey: String = otherBlindedPublicKey else { throw Error.noData }
|
2022-02-25 01:59:29 +01:00
|
|
|
guard let openGroupServerPublicKey: String = openGroupServerPublicKey else {
|
|
|
|
throw Error.invalidGroupPublicKey
|
|
|
|
}
|
|
|
|
guard let userEd25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserED25519KeyPair() else {
|
|
|
|
throw Error.noUserED25519KeyPair
|
|
|
|
}
|
|
|
|
|
|
|
|
(plaintext, sender) = try decryptWithSessionBlindingProtocol(
|
|
|
|
data: ciphertext,
|
2022-03-03 07:46:35 +01:00
|
|
|
isOutgoing: (isOutgoing == true),
|
|
|
|
otherBlindedPublicKey: otherBlindedPublicKey,
|
2022-02-25 01:59:29 +01:00
|
|
|
with: openGroupServerPublicKey,
|
|
|
|
userEd25519KeyPair: userEd25519KeyPair
|
|
|
|
)
|
2021-01-04 05:30:13 +01:00
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
|
|
|
|
case .closedGroupMessage:
|
|
|
|
guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else {
|
|
|
|
throw Error.invalidGroupPublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
var encryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: hexEncodedGroupPublicKey)
|
|
|
|
|
|
|
|
guard !encryptionKeyPairs.isEmpty else { throw Error.noGroupKeyPair }
|
|
|
|
|
|
|
|
// Loop through all known group key pairs in reverse order (i.e. try the latest key pair first (which'll more than
|
|
|
|
// likely be the one we want) but try older ones in case that didn't work)
|
|
|
|
var encryptionKeyPair = encryptionKeyPairs.removeLast()
|
|
|
|
|
|
|
|
func decrypt() throws {
|
|
|
|
do {
|
|
|
|
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: encryptionKeyPair)
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
if !encryptionKeyPairs.isEmpty {
|
|
|
|
encryptionKeyPair = encryptionKeyPairs.removeLast()
|
|
|
|
try decrypt()
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
throw error
|
|
|
|
}
|
2021-05-03 07:13:18 +02:00
|
|
|
}
|
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
groupPublicKey = envelope.source
|
|
|
|
try decrypt()
|
|
|
|
|
|
|
|
default: throw Error.unknownEnvelopeType
|
2020-11-30 01:00:28 +01:00
|
|
|
}
|
2020-11-09 00:33:26 +01:00
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2020-11-17 06:23:13 +01:00
|
|
|
// Don't process the envelope any further if the sender is blocked
|
2020-11-25 06:15:16 +01:00
|
|
|
guard !isBlocked(sender) else { throw Error.senderBlocked }
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2020-11-09 03:56:50 +01:00
|
|
|
// Parse the proto
|
2020-11-06 04:05:45 +01:00
|
|
|
let proto: SNProtoContent
|
|
|
|
do {
|
2020-11-09 00:33:26 +01:00
|
|
|
proto = try SNProtoContent.parseData((plaintext as NSData).removePadding())
|
2020-11-06 04:05:45 +01:00
|
|
|
} catch {
|
|
|
|
SNLog("Couldn't parse proto due to error: \(error).")
|
2020-11-07 23:00:10 +01:00
|
|
|
throw error
|
2020-11-06 04:05:45 +01:00
|
|
|
}
|
2020-11-09 03:56:50 +01:00
|
|
|
// Parse the message
|
2020-11-06 06:31:56 +01:00
|
|
|
let message: Message? = {
|
|
|
|
if let readReceipt = ReadReceipt.fromProto(proto) { return readReceipt }
|
|
|
|
if let typingIndicator = TypingIndicator.fromProto(proto) { return typingIndicator }
|
2021-01-22 00:28:26 +01:00
|
|
|
if let closedGroupControlMessage = ClosedGroupControlMessage.fromProto(proto) { return closedGroupControlMessage }
|
2021-03-02 04:25:21 +01:00
|
|
|
if let dataExtractionNotification = DataExtractionNotification.fromProto(proto) { return dataExtractionNotification }
|
2020-11-06 06:31:56 +01:00
|
|
|
if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate }
|
2021-01-13 03:38:07 +01:00
|
|
|
if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage }
|
2021-07-30 09:04:56 +02:00
|
|
|
if let unsendRequest = UnsendRequest.fromProto(proto) { return unsendRequest }
|
2022-02-02 06:59:56 +01:00
|
|
|
if let messageRequestResponse = MessageRequestResponse.fromProto(proto) { return messageRequestResponse }
|
2020-11-06 06:31:56 +01:00
|
|
|
if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage }
|
|
|
|
return nil
|
|
|
|
}()
|
|
|
|
if let message = message {
|
2021-01-13 03:38:07 +01:00
|
|
|
// Ignore self sends if needed
|
2021-01-14 00:40:58 +01:00
|
|
|
if !message.isSelfSendValid {
|
2021-01-13 03:38:07 +01:00
|
|
|
guard sender != userPublicKey else { throw Error.selfSend }
|
|
|
|
}
|
|
|
|
// Guard against control messages in open groups
|
2020-11-30 01:00:28 +01:00
|
|
|
if isOpenGroupMessage {
|
|
|
|
guard message is VisibleMessage else { throw Error.invalidMessage }
|
|
|
|
}
|
2021-01-13 03:38:07 +01:00
|
|
|
// Finish parsing
|
2020-11-17 06:23:13 +01:00
|
|
|
message.sender = sender
|
2020-11-23 05:58:48 +01:00
|
|
|
message.recipient = userPublicKey
|
2020-11-19 06:28:30 +01:00
|
|
|
message.sentTimestamp = envelope.timestamp
|
2021-04-14 03:39:04 +02:00
|
|
|
message.receivedTimestamp = NSDate.millisecondTimestamp()
|
2021-04-15 02:09:05 +02:00
|
|
|
if isOpenGroupMessage {
|
2021-04-14 03:39:04 +02:00
|
|
|
message.openGroupServerTimestamp = envelope.serverTimestamp
|
2021-04-12 08:56:53 +02:00
|
|
|
}
|
2020-11-18 05:53:45 +01:00
|
|
|
message.groupPublicKey = groupPublicKey
|
2020-11-30 01:00:28 +01:00
|
|
|
message.openGroupServerMessageID = openGroupMessageServerID
|
2021-01-13 03:38:07 +01:00
|
|
|
// Validate
|
2020-11-26 23:07:24 +01:00
|
|
|
var isValid = message.isValid
|
|
|
|
if message is VisibleMessage && !isValid && proto.dataMessage?.attachments.isEmpty == false {
|
|
|
|
isValid = true
|
|
|
|
}
|
2021-02-22 06:34:27 +01:00
|
|
|
guard isValid else {
|
|
|
|
throw Error.invalidMessage
|
|
|
|
}
|
2021-05-19 00:42:58 +02:00
|
|
|
// If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp
|
|
|
|
// will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround
|
|
|
|
// for this issue.
|
|
|
|
if let message = message as? ClosedGroupControlMessage, case .new = message.kind {
|
|
|
|
// Allow duplicates in this case to avoid the following situation:
|
|
|
|
// • The app performed a background poll or received a push notification
|
|
|
|
// • This method was invoked and the received message timestamps table was updated
|
|
|
|
// • Processing wasn't finished
|
|
|
|
// • The user doesn't see the new closed group
|
|
|
|
} else {
|
|
|
|
guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) || isRetry else { throw Error.duplicateMessage }
|
|
|
|
storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction)
|
|
|
|
}
|
2021-01-13 03:38:07 +01:00
|
|
|
// Return
|
2020-11-20 01:10:53 +01:00
|
|
|
return (message, proto)
|
2020-11-06 06:31:56 +01:00
|
|
|
} else {
|
2020-11-07 23:03:08 +01:00
|
|
|
throw Error.unknownMessage
|
2020-11-06 06:31:56 +01:00
|
|
|
}
|
2020-11-06 04:05:45 +01:00
|
|
|
}
|
2020-11-05 23:17:05 +01:00
|
|
|
}
|