diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 531e1760e..3039232bf 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1614,7 +1614,7 @@ static NSTimeInterval launchStartedAt; - (void)createRSSFeedsIfNeeded { - NSArray *feeds = @[ self.lokiNewsFeed, self.lokiMessengerUpdatesFeed ]; + NSArray *feeds = @[ /*self.lokiNewsFeed,*/ self.lokiMessengerUpdatesFeed ]; NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; for (LKRSSFeed *feed in feeds) { NSString *userDefaultsKey = [@"isRSSFeedSetUp." stringByAppendingString:feed.id]; diff --git a/Signal/src/Models/MessageActions.swift b/Signal/src/Models/MessageActions.swift index 02170d8e5..5d99a6c91 100644 --- a/Signal/src/Models/MessageActions.swift +++ b/Signal/src/Models/MessageActions.swift @@ -131,6 +131,7 @@ class ConversationViewItemActions: NSObject { var actions: [MenuAction] = [] let isGroup = conversationViewItem.isGroupThread; + let isRSSFeed = conversationViewItem.isRSSFeed; if shouldAllowReply { let replyAction = MessageActionBuilder.reply(conversationViewItem: conversationViewItem, delegate: delegate) @@ -147,6 +148,11 @@ class ConversationViewItemActions: NSObject { actions.append(saveMediaAction) } } + + if isGroup && !isRSSFeed && conversationViewItem.interaction is TSIncomingMessage { + let copyPublicKeyAction = MessageActionBuilder.copyPublicKey(conversationViewItem: conversationViewItem, delegate: delegate) + actions.append(copyPublicKeyAction) + } if !isGroup || conversationViewItem.userCanDeleteGroupMessage { let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: conversationViewItem, delegate: delegate) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index a71a522e4..0cab0c1bc 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -1,7 +1,5 @@ import PromiseKit -extension String : Error { } - public extension LokiAPI { fileprivate static var failureCount: [LokiAPITarget:UInt] = [:] @@ -67,7 +65,7 @@ public extension LokiAPI { print("[Loki] Invoking get_n_service_nodes on \(target).") return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { intermediate in let rawResponse = intermediate.responseObject - guard let json = rawResponse as? JSON, let intermediate = json["result"] as? JSON, let rawTargets = intermediate["service_node_states"] as? [JSON] else { throw "Failed to update random snode pool from: \(rawResponse)." } + guard let json = rawResponse as? JSON, let intermediate = json["result"] as? JSON, let rawTargets = intermediate["service_node_states"] as? [JSON] else { throw LokiAPIError.randomSnodePoolUpdatingFailed } randomSnodePool = try Set(rawTargets.flatMap { rawTarget in guard let address = rawTarget["public_ip"] as? String, let port = rawTarget["storage_port"] as? Int, let idKey = rawTarget["pubkey_ed25519"] as? String, let encryptionKey = rawTarget["pubkey_x25519"] as? String, address != "0.0.0.0" else { print("[Loki] Failed to parse target from: \(rawTarget).") @@ -138,7 +136,8 @@ internal extension Promise { LokiAPI.failureCount[target] = 0 } case 406: - break // TODO: Handle clock out of sync + print("[Loki] The user's clock is out of sync with the service node network.") + throw LokiAPI.LokiAPIError.clockOutOfSync case 421: // The snode isn't associated with the given public key anymore print("[Loki] Invalidating swarm for: \(hexEncodedPublicKey).") diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index ebd024cd3..bb6a7ad0a 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -26,17 +26,12 @@ public final class LokiAPI : NSObject { // MARK: Types public typealias RawResponse = Any - public enum Error : LocalizedError { - /// Only applicable to snode targets as proof of work isn't required for P2P messaging. - case proofOfWorkCalculationFailed - case messageConversionFailed + @objc public class LokiAPIError : NSError { // Not called `Error` for Obj-C interoperablity - public var errorDescription: String? { - switch self { - case .proofOfWorkCalculationFailed: return NSLocalizedString("Failed to calculate proof of work.", comment: "") - case .messageConversionFailed: return "Failed to convert Signal message to Loki message." - } - } + @objc public static let proofOfWorkCalculationFailed = LokiAPIError(domain: "LokiAPIErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Failed to calculate proof of work." ]) + @objc public static let messageConversionFailed = LokiAPIError(domain: "LokiAPIErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Failed to construct message." ]) + @objc public static let clockOutOfSync = LokiAPIError(domain: "LokiAPIErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Your clock is out of sync with the service node network." ]) + @objc public static let randomSnodePoolUpdatingFailed = LokiAPIError(domain: "LokiAPIErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Failed to update random service node pool." ]) } @objc(LKDestination) @@ -136,7 +131,7 @@ public final class LokiAPI : NSObject { getDestinations() lastDeviceLinkUpdate[hexEncodedPublicKey] = Date() }.catch(on: DispatchQueue.global()) { error in - if (error as? LokiDotNetAPI.Error) == LokiDotNetAPI.Error.parsingFailed { + if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed { // Don't immediately re-fetch in case of failure due to a parsing error lastDeviceLinkUpdate[hexEncodedPublicKey] = Date() getDestinations() @@ -152,7 +147,7 @@ public final class LokiAPI : NSObject { } public static func sendSignalMessage(_ signalMessage: SignalMessage, onP2PSuccess: @escaping () -> Void) -> Promise> { - guard let lokiMessage = LokiMessage.from(signalMessage: signalMessage) else { return Promise(error: Error.messageConversionFailed) } + guard let lokiMessage = LokiMessage.from(signalMessage: signalMessage) else { return Promise(error: LokiAPIError.messageConversionFailed) } let notificationCenter = NotificationCenter.default let destination = lokiMessage.destination func sendLokiMessage(_ lokiMessage: LokiMessage, to target: LokiAPITarget) -> RawResponsePromise { diff --git a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift index 9bbf30cc1..0f7af4d16 100644 --- a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift @@ -12,14 +12,14 @@ public class LokiDotNetAPI : NSObject { private static let attachmentType = "network.loki" // MARK: Error - @objc public class Error : NSError { + @objc public class LokiDotNetAPIError : NSError { // Not called `Error` for Obj-C interoperablity - @objc public static let generic = Error(domain: "com.loki-project.loki-messenger", code: 1, userInfo: [ NSLocalizedDescriptionKey : "An error occurred." ]) - @objc public static let parsingFailed = Error(domain: "com.loki-project.loki-messenger", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Invalid file server response." ]) - @objc public static let signingFailed = Error(domain: "com.loki-project.loki-messenger", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Couldn't sign message." ]) - @objc public static let encryptionFailed = Error(domain: "com.loki-project.loki-messenger", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Couldn't encrypt file." ]) - @objc public static let decryptionFailed = Error(domain: "com.loki-project.loki-messenger", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Couldn't decrypt file." ]) - @objc public static let maxFileSizeExceeded = Error(domain: "com.loki-project.loki-messenger", code: 6, userInfo: [ NSLocalizedDescriptionKey : "Maximum file size exceeded." ]) + @objc public static let generic = LokiDotNetAPIError(domain: "LokiDotNetAPIErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "An error occurred." ]) + @objc public static let parsingFailed = LokiDotNetAPIError(domain: "LokiDotNetAPIErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Invalid file server response." ]) + @objc public static let signingFailed = LokiDotNetAPIError(domain: "LokiDotNetAPIErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Couldn't sign message." ]) + @objc public static let encryptionFailed = LokiDotNetAPIError(domain: "LokiDotNetAPIErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Couldn't encrypt file." ]) + @objc public static let decryptionFailed = LokiDotNetAPIError(domain: "LokiDotNetAPIErrorDomain", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Couldn't decrypt file." ]) + @objc public static let maxFileSizeExceeded = LokiDotNetAPIError(domain: "LokiDotNetAPIErrorDomain", code: 6, userInfo: [ NSLocalizedDescriptionKey : "Maximum file size exceeded." ]) } // MARK: Database @@ -52,7 +52,7 @@ public class LokiDotNetAPI : NSObject { let data: Data guard let unencryptedAttachmentData = try? attachment.readDataFromFile() else { print("[Loki] Couldn't read attachment from disk.") - return seal.reject(Error.generic) + return seal.reject(LokiDotNetAPIError.generic) } // Encrypt the attachment if needed if isEncryptionRequired { @@ -60,7 +60,7 @@ public class LokiDotNetAPI : NSObject { var digest = NSData() guard let encryptedAttachmentData = Cryptography.encryptAttachmentData(unencryptedAttachmentData, outKey: &encryptionKey, outDigest: &digest) else { print("[Loki] Couldn't encrypt attachment.") - return seal.reject(Error.encryptionFailed) + return seal.reject(LokiDotNetAPIError.encryptionFailed) } attachment.encryptionKey = encryptionKey as Data attachment.digest = digest as Data @@ -71,7 +71,7 @@ public class LokiDotNetAPI : NSObject { // Check the file size if needed let isLokiFileServer = (server == LokiFileServerAPI.server) if isLokiFileServer && data.count > LokiFileServerAPI.maxFileSize { - return seal.reject(Error.maxFileSizeExceeded) + return seal.reject(LokiDotNetAPIError.maxFileSizeExceeded) } // Create the request let url = "\(server)/files" @@ -90,7 +90,7 @@ public class LokiDotNetAPI : NSObject { // Parse the server ID & download URL guard let json = response as? JSON, let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else { print("[Loki] Couldn't parse attachment from: \(response).") - return seal.reject(Error.parsingFailed) + return seal.reject(LokiDotNetAPIError.parsingFailed) } // Update the attachment attachment.serverId = serverID @@ -125,7 +125,7 @@ public class LokiDotNetAPI : NSObject { let isSuccessful = (200...299) ~= statusCode guard isSuccessful else { print("[Loki] Couldn't upload attachment.") - return seal.reject(Error.generic) + return seal.reject(LokiDotNetAPIError.generic) } parseResponse(responseObject) }) @@ -166,7 +166,7 @@ public class LokiDotNetAPI : NSObject { return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in guard let json = rawResponse as? JSON, let base64EncodedChallenge = json["cipherText64"] as? String, let base64EncodedServerPublicKey = json["serverPubKey64"] as? String, let challenge = Data(base64Encoded: base64EncodedChallenge), var serverPublicKey = Data(base64Encoded: base64EncodedServerPublicKey) else { - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } // Discard the "05" prefix if needed if serverPublicKey.count == 33 { @@ -176,7 +176,7 @@ public class LokiDotNetAPI : NSObject { // The challenge is prefixed by the 16 bit IV guard let tokenAsData = try? DiffieHellman.decrypt(challenge, publicKey: serverPublicKey, privateKey: userKeyPair.privateKey), let token = String(bytes: tokenAsData, encoding: .utf8) else { - throw Error.decryptionFailed + throw LokiDotNetAPIError.decryptionFailed } return token } diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift index 6a0a9face..69cb55ee2 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift @@ -35,7 +35,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI { return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse -> Set in guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else { print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } return Set(data.flatMap { data -> [DeviceLink] in guard let annotations = data["annotations"] as? [JSON], !annotations.isEmpty else { return [] } @@ -159,11 +159,11 @@ public final class LokiFileServerAPI : LokiDotNetAPI { let isSuccessful = (200...299) ~= statusCode guard isSuccessful else { print("[Loki] Couldn't upload profile picture.") - return seal.reject(Error.generic) + return seal.reject(LokiDotNetAPIError.generic) } guard let json = responseObject as? JSON, let data = json["data"] as? JSON, let profilePicture = data["avatar_image"] as? JSON, let downloadURL = profilePicture["url"] as? String else { print("[Loki] Couldn't parse profile picture from: \(responseObject).") - return seal.reject(Error.parsingFailed) + return seal.reject(LokiDotNetAPIError.parsingFailed) } return seal.fulfill(downloadURL) }) diff --git a/SignalServiceKit/src/Loki/API/LokiMessage.swift b/SignalServiceKit/src/Loki/API/LokiMessage.swift index 637ffb621..08a3fb60d 100644 --- a/SignalServiceKit/src/Loki/API/LokiMessage.swift +++ b/SignalServiceKit/src/Loki/API/LokiMessage.swift @@ -58,7 +58,7 @@ public struct LokiMessage { result.nonce = nonce seal.fulfill(result) } else { - seal.reject(LokiAPI.Error.proofOfWorkCalculationFailed) + seal.reject(LokiAPI.LokiAPIError.proofOfWorkCalculationFailed) } } } diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift index a2a126874..68758a419 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatAPI.swift @@ -85,7 +85,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in guard let json = rawResponse as? JSON, let rawMessages = json["data"] as? [JSON] else { print("[Loki] Couldn't parse messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } return rawMessages.flatMap { message in let isDeleted = (message["is_deleted"] as? Int == 1) @@ -155,7 +155,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } public static func sendMessage(_ message: LokiPublicChatMessage, to channel: UInt64, on server: String) -> Promise { - guard let signedMessage = message.sign(with: userKeyPair.privateKey) else { return Promise(error: Error.signingFailed) } + guard let signedMessage = message.sign(with: userKeyPair.privateKey) else { return Promise(error: LokiDotNetAPIError.signingFailed) } return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise in print("[Loki] Sending message to public chat channel with ID: \(channel) on server: \(server).") let url = URL(string: "\(server)/channels/\(channel)/messages")! @@ -170,7 +170,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { guard let json = rawResponse as? JSON, let messageAsJSON = json["data"] as? JSON, let serverID = messageAsJSON["id"] as? UInt64, let body = messageAsJSON["text"] as? String, let dateAsString = messageAsJSON["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else { print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } let timestamp = UInt64(date.timeIntervalSince1970) * 1000 return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: userHexEncodedPublicKey, displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature) @@ -197,7 +197,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in guard let json = rawResponse as? JSON, let deletions = json["data"] as? [JSON] else { print("[Loki] Couldn't parse deleted messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } return deletions.flatMap { deletion in guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else { @@ -231,7 +231,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in guard let json = rawResponse as? JSON, let moderators = json["moderators"] as? [String] else { print("[Loki] Couldn't parse moderators for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } let moderatorAsSet = Set(moderators); if self.moderators.keys.contains(server) { @@ -274,7 +274,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in guard let json = rawResponse as? JSON, let users = json["data"] as? [JSON] else { print("[Loki] Couldn't parse user count for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } let userCount = users.count let storage = OWSPrimaryStorage.shared() @@ -298,7 +298,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else { print("[Loki] Couldn't parse display names for users: \(hexEncodedPublicKeys) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } storage.dbReadWriteConnection.readWrite { transaction in data.forEach { data in @@ -361,7 +361,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { let info = annotation["value"] as? JSON, let displayName = info["name"] as? String else { print("[Loki] Couldn't parse info for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") - throw Error.parsingFailed + throw LokiDotNetAPIError.parsingFailed } return LokiPublicChatInfo(displayName: displayName) } diff --git a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift index 66cb106f0..dee48d5a7 100644 --- a/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Public Chat/LokiPublicChatPoller.swift @@ -196,7 +196,7 @@ public final class LokiPublicChatPoller : NSObject { LokiAPI.lastDeviceLinkUpdate[$0] = Date() } }.catch(on: DispatchQueue.global()) { error in - if (error as? LokiDotNetAPI.Error) == LokiDotNetAPI.Error.parsingFailed { + if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed { // Don't immediately re-fetch in case of failure due to a parsing error hexEncodedPublicKeysToUpdate.forEach { LokiAPI.lastDeviceLinkUpdate[$0] = Date() diff --git a/SignalServiceKit/src/Loki/Crypto/DiffieHellman.swift b/SignalServiceKit/src/Loki/Crypto/DiffieHellman.swift index 41198b063..946f02aea 100644 --- a/SignalServiceKit/src/Loki/Crypto/DiffieHellman.swift +++ b/SignalServiceKit/src/Loki/Crypto/DiffieHellman.swift @@ -1,10 +1,17 @@ import CryptoSwift import Curve25519Kit -public enum DiffieHellman { +@objc public final class DiffieHellman : NSObject { + + @objc public class DiffieHellmanError : NSError { // Not called `Error` for Obj-C interoperablity + + @objc public static let decryptionFailed = DiffieHellmanError(domain: "DiffieHellmanErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Couldn't decrypt data." ]) + } public static let ivLength: Int32 = 16; + private override init() { } + public static func encrypt(_ plainTextData: Data, using symmetricKey: Data) throws -> Data { let iv = Randomness.generateRandomBytes(ivLength)! let ivBytes = [UInt8](iv) @@ -24,7 +31,7 @@ public enum DiffieHellman { public static func decrypt(_ encryptedData: Data, using symmetricKey: Data) throws -> Data { let symmetricKeyBytes = [UInt8](symmetricKey) - guard encryptedData.count >= ivLength else { throw "Couldn't decrypt data." } + guard encryptedData.count >= ivLength else { throw DiffieHellmanError.decryptionFailed } let ivBytes = [UInt8](encryptedData[..