diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 74d767d245..77a0106ef1 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -455,6 +455,81 @@ public struct AccountRunningImportantTasks: OptionSet { public static let pendingMessages = AccountRunningImportantTasks(rawValue: 1 << 1) } +private struct MasterNotificationKey { + let id: Data + let data: Data +} + +private func masterNotificationsKey(account: Account, ignoreDisabled: Bool) -> Signal { + if let key = account.masterNotificationKey.with({ $0 }) { + //return .single(key) + } + + return account.postbox.modify(ignoreDisabled: ignoreDisabled, { modifier -> MasterNotificationKey in + if let value = modifier.keychainEntryForKey("master-notification-secret"), !value.isEmpty { + let authKeyHash = sha1Digest(value) + let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count) + let keyData = MasterNotificationKey(id: authKeyId, data: value) + let _ = account.masterNotificationKey.swap(keyData) + return keyData + } else { + var secretData = Data(count: 256) + if !secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer) -> Bool in + let copyResult = SecRandomCopyBytes(nil, secretData.count, bytes) + return copyResult == errSecSuccess + }) { + assertionFailure() + } + + modifier.setKeychainEntry(secretData, forKey: "master-notification-secret") + let authKeyHash = sha1Digest(secretData) + let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count) + let keyData = MasterNotificationKey(id: authKeyId, data: secretData) + let _ = account.masterNotificationKey.swap(keyData) + return keyData + } + }) +} + +public func decryptedNotificationPayload(account: Account, data: Data) -> Signal { + return masterNotificationsKey(account: account, ignoreDisabled: true) + |> map { secret -> Data? in + if data.subdata(in: 0 ..< 8) != secret.id { + return nil + } + + let x = 8 + let msgKey = data.subdata(in: 8 ..< (8 + 16)) + let rawData = data.subdata(in: (8 + 16) ..< data.count) + let sha256_a = sha256Digest(msgKey + secret.data.subdata(in: x ..< (x + 36))) + let sha256_b = sha256Digest(secret.data.subdata(in: (40 + x) ..< (40 + x + 36)) + msgKey) + let aesKey = sha256_a.subdata(in: 0 ..< 8) + sha256_b.subdata(in: 8 ..< (8 + 16)) + sha256_a.subdata(in: 24 ..< (24 + 8)) + let aesIv = sha256_b.subdata(in: 0 ..< 8) + sha256_a.subdata(in: 8 ..< (8 + 16)) + sha256_b.subdata(in: 24 ..< (24 + 8)) + + guard let data = MTAesDecrypt(rawData, aesKey, aesIv), data.count > 4 else { + return nil + } + + var dataLength: Int32 = 0 + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + memcpy(&dataLength, bytes, 4) + } + + if dataLength < 0 || dataLength > data.count - 4 { + return nil + } + + let checkMsgKeyLarge = sha256Digest(secret.data.subdata(in: (88 + x) ..< (88 + x + 32)) + data) + let checkMsgKey = checkMsgKeyLarge.subdata(in: 8 ..< (8 + 16)) + + if checkMsgKey != msgKey { + return nil + } + + return data.subdata(in: 4 ..< (4 + Int(dataLength))) + } +} + public class Account { public let id: AccountRecordId public let basePath: String @@ -512,6 +587,8 @@ public class Account { return self._importantTasksRunning.get() } + fileprivate let masterNotificationKey = Atomic(value: nil) + var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods) { @@ -545,25 +622,25 @@ public class Account { let networkStateSignal = combineLatest(self.stateManager.isUpdating |> deliverOn(networkStateQueue), network.connectionStatus |> deliverOn(networkStateQueue)) |> map { isUpdating, connectionStatus -> AccountNetworkState in switch connectionStatus { - case .waitingForNetwork: - return .waitingForNetwork - case let .connecting(toProxy): - return .connecting(toProxy: toProxy) - case .updating: + case .waitingForNetwork: + return .waitingForNetwork + case let .connecting(toProxy): + return .connecting(toProxy: toProxy) + case .updating: + return .updating + case .online: + if isUpdating { return .updating - case .online: - if isUpdating { - return .updating - } else { - return .online - } + } else { + return .online + } } - } + } self.networkStateValue.set(networkStateSignal |> distinctUntilChanged) let appliedNotificationToken = self.notificationToken.get() |> distinctUntilChanged - |> mapToSignal { token -> Signal in + |> mapToSignal { token -> Signal in var tokenString = "" token.withUnsafeBytes { (bytes: UnsafePointer) -> Void in for i in 0 ..< token.count { @@ -576,12 +653,16 @@ public class Account { #if DEBUG appSandbox = .boolTrue #endif - return network.request(Api.functions.account.registerDevice(tokenType: 1, token: tokenString, appSandbox: appSandbox, secret: Buffer(), otherUids: [])) - |> retryRequest - |> mapToSignal { _ -> Signal in - return .complete() - } - } + + return masterNotificationsKey(account: self, ignoreDisabled: false) + |> mapToSignal { secret -> Signal in + return network.request(Api.functions.account.registerDevice(tokenType: 1, token: tokenString, appSandbox: appSandbox, secret: Buffer(data: secret.data), otherUids: [])) + |> retryRequest + |> mapToSignal { _ -> Signal in + return .complete() + } + } + } self.notificationTokenDisposable.set(appliedNotificationToken.start()) let appliedVoipToken = self.voipToken.get() @@ -600,12 +681,15 @@ public class Account { appSandbox = .boolTrue #endif - return network.request(Api.functions.account.registerDevice(tokenType: 9, token: tokenString, appSandbox: appSandbox, secret: Buffer(), otherUids: [])) - |> retryRequest - |> mapToSignal { _ -> Signal in - return .complete() - } - } + return masterNotificationsKey(account: self, ignoreDisabled: false) + |> mapToSignal { secret -> Signal in + return network.request(Api.functions.account.registerDevice(tokenType: 9, token: tokenString, appSandbox: appSandbox, secret: Buffer(data: secret.data), otherUids: [])) + |> retryRequest + |> mapToSignal { _ -> Signal in + return .complete() + } + } + } self.voipTokenDisposable.set(appliedVoipToken.start()) let serviceTasksMasterBecomeMaster = shouldBeServiceTaskMaster.get() @@ -640,7 +724,7 @@ public class Account { Logger.shared.log("Account", "Resigned master") return .never() } - } + } self.managedServiceViewsDisposable.set(serviceTasksMaster.start()) let pendingMessageManager = self.pendingMessageManager @@ -678,7 +762,7 @@ public class Account { result.formUnion(value) } return result - } + } self.managedOperationsDisposable.add(importantBackgroundOperationsRunning.start(next: { [weak self] value in if let strongSelf = self { @@ -784,10 +868,5 @@ public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: account.managedContactsDisposable.set(manageContacts(network: account.network, postbox: account.postbox).start()) account.managedStickerPacksDisposable.set(manageStickerPacks(network: account.network, postbox: account.postbox).start()) - - /*account.network.request(Api.functions.help.getScheme(version: 0)).start(next: { result in - if case let .scheme(text, _, _, _) = result { - print("\(text)") - } - })*/ } + diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index d369bcb899..24335e2556 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -150,12 +150,12 @@ func managedSecretChatOutgoingOperations(postbox: Postbox, network: Network) -> return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .noop(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds): return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .readMessageContents(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout,messageId): + case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, messageId): return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, timeout: timeout, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo): return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .resendOperations(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, fromSeqNo: fromSeqNo, toSeqNo: toSeqNo), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .screenshotMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, messageId): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .screenshotMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) case let .terminate(reportSpam): return requestTerminateSecretChat(postbox: postbox, network: network, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, reportSpam: reportSpam) } @@ -378,7 +378,7 @@ private enum BoxedDecryptedMessage { private enum SecretMessageAction { case deleteMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) - case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) + case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64], messageId: MessageId) case clearHistory(layer: SecretChatLayer, actionGloballyUniqueId: Int64) case resendOperations(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, fromSeqNo: Int32, toSeqNo: Int32) case reportLayerSupport(layer: SecretChatLayer, actionGloballyUniqueId: Int64, layerSupport: Int32) @@ -394,7 +394,7 @@ private enum SecretMessageAction { switch self { case let .deleteMessages(_, actionGloballyUniqueId, _): return actionGloballyUniqueId - case let .screenshotMessages(_, actionGloballyUniqueId, _): + case let .screenshotMessages(_, actionGloballyUniqueId, _, _): return actionGloballyUniqueId case let .clearHistory(_, actionGloballyUniqueId): return actionGloballyUniqueId @@ -423,6 +423,8 @@ private enum SecretMessageAction { switch self { case let .setMessageAutoremoveTimeout(_, _, _, messageId): return messageId + case let .screenshotMessages(_, _, _, messageId): + return messageId default: return nil } @@ -815,7 +817,7 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B case .layer73: return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds))) } - case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds): + case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, _): switch layer { case .layer8: let randomBytesData = malloc(15)! diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index 316cb442ee..5c8a33ee46 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -677,7 +677,7 @@ public final class PendingMessageManager { } } else if case .historyScreenshot = media.action { sentAsAction = true - let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .screenshotMessages(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, globallyUniqueIds: []), state: state) + let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .screenshotMessages(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, globallyUniqueIds: [], messageId: message.id), state: state) if updatedState != state { modifier.setPeerChatState(message.id.peerId, state: updatedState) } diff --git a/TelegramCore/SecretChatOutgoingOperation.swift b/TelegramCore/SecretChatOutgoingOperation.swift index efd3038ef5..f3c22df360 100644 --- a/TelegramCore/SecretChatOutgoingOperation.swift +++ b/TelegramCore/SecretChatOutgoingOperation.swift @@ -113,7 +113,7 @@ enum SecretChatOutgoingOperationContents: PostboxCoding { case sendMessage(layer: SecretChatLayer, id: MessageId, file: SecretChatOutgoingFile?) case readMessagesContent(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) case deleteMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) - case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64]) + case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64], messageId: MessageId) case clearHistory(layer: SecretChatLayer, actionGloballyUniqueId: Int64) case resendOperations(layer : SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, fromSeqNo: Int32, toSeqNo: Int32) case reportLayerSupport(layer: SecretChatLayer, actionGloballyUniqueId: Int64, layerSupport: Int32) @@ -136,7 +136,7 @@ enum SecretChatOutgoingOperationContents: PostboxCoding { case SecretChatOutgoingOperationValue.deleteMessages.rawValue: self = .deleteMessages(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u")) case SecretChatOutgoingOperationValue.screenshotMessages.rawValue: - self = .screenshotMessages(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u")) + self = .screenshotMessages(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u"), messageId: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0))) case SecretChatOutgoingOperationValue.clearHistory.rawValue: self = .clearHistory(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0)) case SecretChatOutgoingOperationValue.resendOperations.rawValue: @@ -191,11 +191,14 @@ enum SecretChatOutgoingOperationContents: PostboxCoding { encoder.encodeInt32(layer.rawValue, forKey: "l") encoder.encodeInt64(actionGloballyUniqueId, forKey: "i") encoder.encodeInt64Array(globallyUniqueIds, forKey: "u") - case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds): + case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, messageId): encoder.encodeInt32(SecretChatOutgoingOperationValue.screenshotMessages.rawValue, forKey: "r") encoder.encodeInt32(layer.rawValue, forKey: "l") encoder.encodeInt64(actionGloballyUniqueId, forKey: "i") encoder.encodeInt64Array(globallyUniqueIds, forKey: "u") + encoder.encodeInt64(messageId.peerId.toInt64(), forKey: "m.p") + encoder.encodeInt32(messageId.namespace, forKey: "m.n") + encoder.encodeInt32(messageId.id, forKey: "m.i") case let .clearHistory(layer, actionGloballyUniqueId): encoder.encodeInt32(SecretChatOutgoingOperationValue.clearHistory.rawValue, forKey: "r") encoder.encodeInt32(layer.rawValue, forKey: "l")