diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index e69d2ad9ca..548d5ff33d 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -414,6 +414,8 @@ D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */; }; D0F3CC7B1DDE2859008148FA /* RequestEditMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */; }; D0F3CC7D1DDE289E008148FA /* ResolvePeerByName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */; }; + D0F53BE91E784A4800117362 /* ChangeAccountPhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */; }; + D0F53BEA1E784A4800117362 /* ChangeAccountPhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */; }; D0F7AB2C1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */; }; D0F7AB2D1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */; }; D0F7AB2F1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */; }; @@ -694,6 +696,7 @@ D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = ""; }; D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = ""; }; D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolvePeerByName.swift; sourceTree = ""; }; + D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeAccountPhoneNumber.swift; sourceTree = ""; }; D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = ""; }; D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = ""; }; D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = ""; }; @@ -1071,6 +1074,7 @@ D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */, D05A32E31E6F0B2E002760B4 /* RecentAccountSessions.swift */, D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */, + D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */, ); name = Settings; sourceTree = ""; @@ -1452,6 +1456,7 @@ D03B0D441D6319F900955575 /* CloudFileMediaResource.swift in Sources */, D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */, D01AC9211DD5E7E500E8160F /* RequestEditMessage.swift in Sources */, + D0F53BE91E784A4800117362 /* ChangeAccountPhoneNumber.swift in Sources */, D0E35A0E1DE4953E00BC6096 /* FetchHttpResource.swift in Sources */, D01AC91D1DD5DA5E00E8160F /* RequestMessageActionCallback.swift in Sources */, D00C7CEB1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift in Sources */, @@ -1668,6 +1673,7 @@ D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */, D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */, D018D3381E648ACF00C5E089 /* CreateChannel.swift in Sources */, + D0F53BEA1E784A4800117362 /* ChangeAccountPhoneNumber.swift in Sources */, D001F3EE1E128A1C007A8C60 /* AccountStateManager.swift in Sources */, D0B844351DAB91E0005F29E1 /* NBPhoneNumberDesc.m in Sources */, D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */, diff --git a/TelegramCore/ChangeAccountPhoneNumber.swift b/TelegramCore/ChangeAccountPhoneNumber.swift new file mode 100644 index 0000000000..e1a3a9c00c --- /dev/null +++ b/TelegramCore/ChangeAccountPhoneNumber.swift @@ -0,0 +1,120 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public struct ChangeAccountPhoneNumberData: Equatable { + public let type: SentAuthorizationCodeType + public let hash: String + public let timeout: Int32? + public let nextType: AuthorizationCodeNextType? + + public static func ==(lhs: ChangeAccountPhoneNumberData, rhs: ChangeAccountPhoneNumberData) -> Bool { + if lhs.type != rhs.type { + return false + } + if lhs.hash != rhs.hash { + return false + } + if lhs.timeout != rhs.timeout { + return false + } + if lhs.nextType != rhs.nextType { + return false + } + return true + } +} + +public enum RequestChangeAccountPhoneNumberVerificationError { + case invalidPhoneNumber + case limitExceeded + case phoneNumberOccupied + case generic +} + +public func requestChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String) -> Signal { + return account.network.request(Api.functions.account.sendChangePhoneCode(flags: 0, phoneNumber: phoneNumber, currentNumber: nil), automaticFloodWait: false) + |> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "PHONE_NUMBER_INVALID" { + return .invalidPhoneNumber + } else if error.errorDescription == "PHONE_NUMBER_OCCUPIED" { + return .phoneNumberOccupied + } else { + return .generic + } + } + |> map { sentCode -> ChangeAccountPhoneNumberData in + switch sentCode { + case let .sentCode(_, type, phoneCodeHash, nextType, timeout): + var parsedNextType: AuthorizationCodeNextType? + if let nextType = nextType { + parsedNextType = AuthorizationCodeNextType(apiType: nextType) + } + return ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType) + } + } +} + +public func requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal { + return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false) + |> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "PHONE_NUMBER_INVALID" { + return .invalidPhoneNumber + } else if error.errorDescription == "PHONE_NUMBER_OCCUPIED" { + return .phoneNumberOccupied + } else { + return .generic + } + } + |> map { sentCode -> ChangeAccountPhoneNumberData in + switch sentCode { + case let .sentCode(_, type, phoneCodeHash, nextType, timeout): + var parsedNextType: AuthorizationCodeNextType? + if let nextType = nextType { + parsedNextType = AuthorizationCodeNextType(apiType: nextType) + } + return ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType) + } + } +} + +public enum ChangeAccountPhoneNumberError { + case generic + case invalidCode + case codeExpired + case limitExceeded +} + +public func requestChangeAccountPhoneNumber(account: Account, phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal { + return account.network.request(Api.functions.account.changePhone(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode), automaticFloodWait: false) + |> mapError { error -> ChangeAccountPhoneNumberError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "PHONE_CODE_INVALID" { + return .invalidCode + } else if error.errorDescription == "PHONE_CODE_EXPIRED" { + return .codeExpired + } else { + return .generic + } + } + |> mapToSignal { result -> Signal in + return account.postbox.modify { modifier -> Void in + let user = TelegramUser(user: result) + updatePeers(modifier: modifier, peers: [user], update: { _, updated in + return updated + }) + } |> mapError { _ -> ChangeAccountPhoneNumberError in return .generic } + } +} diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index 3cefd7f1f9..65aaae5418 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -209,7 +209,7 @@ private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId: let removed = modifier.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex) assert(removed) if let state = modifier.getPeerChatState(peerId) as? SecretChatState { - var updatedState = state.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer) + var updatedState = state.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer).withUpdatedKeyFingerprint(SecretChatKeyFingerprint(digest: sha256Digest(key))) var layer: SecretChatLayer? switch updatedState.embeddedState { case .terminated, .handshake: diff --git a/TelegramCore/SecretChatState.swift b/TelegramCore/SecretChatState.swift index 55d22cfd7f..c44f3fd2c4 100644 --- a/TelegramCore/SecretChatState.swift +++ b/TelegramCore/SecretChatState.swift @@ -21,6 +21,24 @@ public struct SecretChatKeyFingerprint: Coding, Equatable { public let k2: Int64 public let k3: Int64 + init(digest: Data) { + assert(digest.count == 32) + var k0: Int64 = 0 + var k1: Int64 = 0 + var k2: Int64 = 0 + var k3: Int64 = 0 + digest.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + k0 = bytes.pointee + k1 = bytes.advanced(by: 1).pointee + k2 = bytes.advanced(by: 2).pointee + k3 = bytes.advanced(by: 3).pointee + } + self.k0 = k0 + self.k1 = k1 + self.k2 = k2 + self.k3 = k3 + } + public init(k0: Int64, k1: Int64, k2: Int64, k3: Int64) { self.k0 = k0 self.k1 = k1 @@ -36,10 +54,10 @@ public struct SecretChatKeyFingerprint: Coding, Equatable { } public func encode(_ encoder: Encoder) { - encoder.encodeInt64(self.k0, forKey: "") - encoder.encodeInt64(self.k1, forKey: "") - encoder.encodeInt64(self.k2, forKey: "") - encoder.encodeInt64(self.k3, forKey: "") + encoder.encodeInt64(self.k0, forKey: "k0") + encoder.encodeInt64(self.k1, forKey: "k1") + encoder.encodeInt64(self.k2, forKey: "k2") + encoder.encodeInt64(self.k3, forKey: "k3") } public static func ==(lhs: SecretChatKeyFingerprint, rhs: SecretChatKeyFingerprint) -> Bool { @@ -434,7 +452,11 @@ enum SecretChatEmbeddedState: Coding, Equatable { } } -final class SecretChatState: PeerChatState, Equatable { +public protocol SecretChatKeyState { + var keyFingerprint: SecretChatKeyFingerprint? { get } +} + +final class SecretChatState: PeerChatState, SecretChatKeyState, Equatable { let role: SecretChatRole let embeddedState: SecretChatEmbeddedState let keychain: SecretChatKeychain diff --git a/TelegramCore/UpdateSecretChat.swift b/TelegramCore/UpdateSecretChat.swift index d50c010387..4499e49bbb 100644 --- a/TelegramCore/UpdateSecretChat.swift +++ b/TelegramCore/UpdateSecretChat.swift @@ -43,7 +43,7 @@ func updateSecretChat(accountPeerId: PeerId, modifier: Modifier, chat: Api.Encry memcpy(&keyFingerprint, bytes.advanced(by: keyHash.count - 8), 8) } - var updatedState = currentState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer) + var updatedState = currentState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer).withUpdatedKeyFingerprint(SecretChatKeyFingerprint(digest: sha256Digest(key))) var layer: SecretChatLayer? switch updatedState.embeddedState {