SafeStore.swift 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2018-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. import Gzip
  23. import ThreemaFramework
  24. @objc class SafeStore: NSObject {
  25. private static let SAFE_DEFAULT_SERVER: String = "https://safe-%02hhx.threema.ch"
  26. private var safeConfigManager: SafeConfigManagerProtocol
  27. private var serverApiConnector: ServerAPIConnector
  28. @objc public let masterKeyLength: Int = 64
  29. private let backupIdLength: Int = 32
  30. private let encryptionKeyLength: Int = 32
  31. enum SafeError: Error {
  32. case invalidMasterKey
  33. case inavlidData
  34. case restoreError(message: String)
  35. case restoreFailed(message: String)
  36. }
  37. init(safeConfigManager: SafeConfigManagerProtocol, serverApiConnector: ServerAPIConnector) {
  38. self.safeConfigManager = safeConfigManager
  39. self.serverApiConnector = serverApiConnector
  40. }
  41. //NSObject thereby not the whole SafeConfigManagerProtocol interface must be like @objc
  42. @objc convenience init(safeConfigManagerAsObject safeConfigManager: NSObject, serverApiConnector: ServerAPIConnector) {
  43. self.init(safeConfigManager: safeConfigManager as! SafeConfigManagerProtocol, serverApiConnector: serverApiConnector)
  44. }
  45. //MARK: - keys and encryption
  46. func createKey(identity: String, password: String) -> [UInt8]? {
  47. let pPassword = UnsafeMutablePointer<Int8>(strdup(password))
  48. let pSalt = UnsafeMutablePointer<Int8>(strdup(identity))
  49. var pOut = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: self.masterKeyLength)
  50. defer {
  51. pPassword?.deallocate()
  52. pSalt?.deallocate()
  53. pOut.deallocate()
  54. }
  55. if(getDerivedKey(pPassword, pSalt, pOut) != 0) {
  56. return nil
  57. }
  58. return Array(UnsafeMutableBufferPointer(start: pOut, count: self.masterKeyLength))
  59. }
  60. func getBackupId(key: [UInt8]) -> [UInt8]? {
  61. if key.count == self.masterKeyLength {
  62. return Array(key[0..<self.backupIdLength])
  63. }
  64. return nil
  65. }
  66. func getEncryptionKey(key: [UInt8]) -> [UInt8]? {
  67. if key.count == self.masterKeyLength {
  68. return Array(key[self.masterKeyLength - self.encryptionKeyLength..<self.masterKeyLength])
  69. }
  70. return nil
  71. }
  72. func encryptBackupData(key: [UInt8], data: [UInt8]) throws -> [UInt8] {
  73. guard key.count == self.masterKeyLength else {
  74. throw SafeError.invalidMasterKey
  75. }
  76. guard data.count != 0 else {
  77. throw SafeError.inavlidData
  78. }
  79. let backupId = getBackupId(key: key)
  80. let encryptionKey = getEncryptionKey(key: key)
  81. guard backupId != nil && encryptionKey != nil else {
  82. throw SafeError.invalidMasterKey
  83. }
  84. let decryptedData: Data = Data(data)
  85. let compressedData: Data = try decryptedData.gzipped()
  86. if let nonce: Data = generateRandomBytes() {
  87. let crypto: NaClCrypto = NaClCrypto()
  88. let encryptedData: Data = crypto.symmetricEncryptData(compressedData, withKey: Data(bytes: encryptionKey!, count: encryptionKey!.count), nonce: nonce)
  89. var encryptedBackup = Data()
  90. encryptedBackup.append(nonce)
  91. encryptedBackup.append(encryptedData)
  92. return Array(encryptedBackup)
  93. }
  94. else {
  95. throw SafeError.inavlidData
  96. }
  97. }
  98. func decryptBackupData(key: [UInt8], data: [UInt8]) throws -> [UInt8] {
  99. guard key.count == self.masterKeyLength else {
  100. throw SafeError.invalidMasterKey
  101. }
  102. guard data.count != 0 else {
  103. throw SafeError.inavlidData
  104. }
  105. let backupId = getBackupId(key: key)
  106. let encryptionKey = getEncryptionKey(key: key)
  107. guard backupId != nil && encryptionKey != nil else {
  108. throw SafeError.invalidMasterKey
  109. }
  110. let nonce = data[0...23]
  111. let encryptedData = data[24...data.count-1]
  112. let crypto: NaClCrypto = NaClCrypto()
  113. let decryptedData = crypto.symmetricDecryptData(Data(encryptedData), withKey: Data(encryptionKey!), nonce: Data(nonce))!
  114. let unpressedData = try decryptedData.gunzipped()
  115. return Array(unpressedData)
  116. }
  117. public static func dataToHexString(_ data: [UInt8]) -> String {
  118. return data.map { String(format: "%02hhx", $0) }.joined(separator: "")
  119. }
  120. public static func hexStringToData(_ hexString: String) -> Data {
  121. var hex = hexString
  122. var data = Data()
  123. while(hex.count > 0) {
  124. let subIndex = hex.index(hex.startIndex, offsetBy: 2)
  125. let c = String(hex[..<subIndex])
  126. hex = String(hex[subIndex...])
  127. var ch: UInt32 = 0
  128. Scanner(string: c).scanHexInt32(&ch)
  129. var char = UInt8(ch)
  130. data.append(&char, count: 1)
  131. }
  132. return data
  133. }
  134. public func sha1(data: Data) -> [UInt8] {
  135. var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
  136. data.withUnsafeBytes {
  137. _ = CC_SHA1($0, CC_LONG(data.count), &digest)
  138. }
  139. return digest
  140. }
  141. private func generateRandomBytes() -> Data? {
  142. var keyData = Data(count: 24)
  143. let result = keyData.withUnsafeMutableBytes {
  144. SecRandomCopyBytes(kSecRandomDefault, 24, $0)
  145. }
  146. if result == errSecSuccess {
  147. return keyData
  148. } else {
  149. return nil
  150. }
  151. }
  152. func isDateOlderThenDays(date: Date?, days: Int) -> Bool {
  153. return date == nil || (date != nil && (date!.addingTimeInterval(TimeInterval(86400 * days)) < Date()))
  154. }
  155. //MARK: - safe server
  156. func getSafeServerToDisplay() -> String {
  157. if let server = self.safeConfigManager.getServer() {
  158. do {
  159. let regexDefaultServer = try NSRegularExpression(pattern: "https://safe-[0-9a-z]{2}.threema.ch")
  160. let regexResult = regexDefaultServer.matches(in: server, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: server.count))
  161. if regexResult.count > 0 {
  162. return BundleUtil.localizedString(forKey: "safe_use_default_server")
  163. }
  164. } catch let error {
  165. print("regex faild to check default server: \(error.localizedDescription)")
  166. }
  167. return self.safeConfigManager.getCustomServer() != nil ? self.safeConfigManager.getCustomServer()! : server
  168. } else {
  169. return BundleUtil.localizedString(forKey: "safe_use_default_server")
  170. }
  171. }
  172. func getSafeServer(key: [UInt8]) -> URL? {
  173. return self.safeConfigManager.getServer() != nil ? URL(string: self.safeConfigManager.getServer()!) : self.getSafeDefaultServer(key: key)!
  174. }
  175. func getSafeDefaultServer(key: [UInt8]) -> URL? {
  176. guard let backupId = getBackupId(key: key) else {
  177. return nil
  178. }
  179. return URL(string: String(format: SafeStore.SAFE_DEFAULT_SERVER, backupId[0]))
  180. }
  181. /// Compose URL like https://user:password@host.com
  182. /// - returns: Server Url with credentials
  183. @objc func composeSafeServerAuth(server: String?, user: String?, password: String?) -> URL? {
  184. guard let server = server, !server.lowercased().starts(with: "http://") else {
  185. return nil
  186. }
  187. let httpProtocol = "https://"
  188. var url: String!
  189. if !server.lowercased().starts(with: httpProtocol) {
  190. url = httpProtocol
  191. url.append(server)
  192. }
  193. else {
  194. url = server
  195. }
  196. let userEncoded = user?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
  197. let passwordEncoded = password?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
  198. if userEncoded != nil || passwordEncoded != nil {
  199. var concatUrl = url[...String.Index(utf16Offset: httpProtocol.count - 1, in: httpProtocol)]
  200. concatUrl.append(contentsOf: userEncoded != nil ? userEncoded! : "")
  201. concatUrl.append(contentsOf: ":")
  202. concatUrl.append(contentsOf: passwordEncoded != nil ? passwordEncoded! : "")
  203. concatUrl.append(contentsOf: "@")
  204. concatUrl.append(contentsOf: url[String.Index(utf16Offset: httpProtocol.count, in: httpProtocol)...])
  205. return URL(string: String(concatUrl))
  206. }
  207. return URL(string: url)
  208. }
  209. /// Extract and return server url, user and password from https://user:password@host.com
  210. /// - returns: User, Password and Server Url without credentials
  211. func extractSafeServerAuth(server: URL) -> (user: String?, password: String?, server: URL) {
  212. let httpProtocol = "https://"
  213. guard server.absoluteString.starts(with: httpProtocol) else {
  214. return (user: nil, password: nil, server: server)
  215. }
  216. var user: String?
  217. var password: String?
  218. var serverUrl: URL?
  219. if server.user != nil || server.password != nil {
  220. user = server.user?.removingPercentEncoding
  221. password = server.password?.removingPercentEncoding
  222. if let startServerUrl = server.absoluteString.firstIndex(of: "@") {
  223. serverUrl = URL(string: httpProtocol + server.absoluteString[String.Index(utf16Offset: startServerUrl.utf16Offset(in: server.absoluteString)+1, in: server.absoluteString)...].description)
  224. } else {
  225. serverUrl = server
  226. }
  227. } else {
  228. serverUrl = server
  229. }
  230. return (user: user, password: password, server: serverUrl!)
  231. }
  232. //MARK: - back up and restore data
  233. func backupData() -> [UInt8]? {
  234. // get identity
  235. if MyIdentityStore.shared().keySecret() == nil {
  236. return nil
  237. }
  238. let jUser = SafeJsonParser.SafeBackupData.User(privatekey: MyIdentityStore.shared().keySecret().base64EncodedString())
  239. jUser.nickname = MyIdentityStore.shared().pushFromName
  240. // get identity profile picture and its settings
  241. if let profilePicture = MyIdentityStore.shared().profilePicture {
  242. if let imageData = profilePicture["ProfilePicture"] as? Data {
  243. jUser.profilePic = downscaleImageAsBase64(data: imageData, max: 400)
  244. }
  245. }
  246. switch UserSettings.shared().sendProfilePicture {
  247. case SendProfilePictureAll:
  248. jUser.profilePicRelease = ["*"]
  249. case SendProfilePictureContacts:
  250. if let identities = UserSettings.shared().profilePictureContactList {
  251. jUser.profilePicRelease = identities.map({ (identity) -> String in
  252. return "\(identity)"
  253. })
  254. }
  255. default:
  256. jUser.profilePicRelease = nil
  257. }
  258. // get identity linking, backup only email or mobile no is verified
  259. var jLinks = [SafeJsonParser.SafeBackupData.User.Link]()
  260. if let mobileNo = MyIdentityStore.shared().linkedMobileNo,
  261. !MyIdentityStore.shared().linkMobileNoPending {
  262. jLinks.append(SafeJsonParser.SafeBackupData.User.Link(type: "mobile", value: mobileNo))
  263. }
  264. if let email = MyIdentityStore.shared().linkedEmail,
  265. !MyIdentityStore.shared().linkEmailPending {
  266. jLinks.append(SafeJsonParser.SafeBackupData.User.Link(type: "email", value: email))
  267. }
  268. if jLinks.count > 0 {
  269. jUser.links = jLinks
  270. }
  271. // get contacts
  272. var jContacts = [SafeJsonParser.SafeBackupData.Contact]()
  273. for item in ContactStore.shared().allContacts() {
  274. // do not backup me as contact
  275. if let contact = item as? Contact,
  276. contact.identity != MyIdentityStore.shared()?.identity {
  277. let jContact = SafeJsonParser.SafeBackupData.Contact(identity: contact.identity, verification: Int(truncating: contact.verificationLevel))
  278. jContact.publickey = contact.verificationLevel == 2 ? contact.publicKey.base64EncodedString() : nil
  279. jContact.workVerified = contact.workContact != 0
  280. // function to hide contacts is not implemented in iOS; hidden could be nil
  281. // till then set hidden to false
  282. jContact.hidden = false
  283. if let firstname = contact.firstName {
  284. jContact.firstname = firstname
  285. }
  286. if let lastname = contact.lastName {
  287. jContact.lastname = lastname
  288. }
  289. if let nickname = contact.publicNickname {
  290. jContact.nickname = nickname
  291. }
  292. jContacts.append(jContact)
  293. }
  294. }
  295. // get groups
  296. var jGroups = [SafeJsonParser.SafeBackupData.Group]()
  297. let entityManager = EntityManager()
  298. let groupConversations = entityManager.entityFetcher.allGroupConversations()
  299. for item in groupConversations! {
  300. if let groupConversation = item as? Conversation {
  301. if groupConversation.isGroup() {
  302. if let group = GroupProxy(for: groupConversation, entityManager: entityManager) {
  303. let id = SafeStore.dataToHexString(Array(group.groupId))
  304. var creator: String? = nil
  305. if group.isOwnGroup() {
  306. creator = MyIdentityStore.shared().identity
  307. } else if let groupCreator = group.creator {
  308. creator = groupCreator.identity
  309. }
  310. let name = group.name
  311. let members = Array(group.memberIdsIncludingSelf) as! [String]
  312. if creator != nil {
  313. let jGroup = SafeJsonParser.SafeBackupData.Group(id: id, creator: creator!, groupname: name!, members: members, deleted: false)
  314. jGroups.append(jGroup)
  315. }
  316. }
  317. }
  318. }
  319. }
  320. // get settings
  321. let jSettings = SafeJsonParser.SafeBackupData.Settings()
  322. jSettings.syncContacts = UserSettings.shared().syncContacts
  323. jSettings.blockUnknown = UserSettings.shared().blockUnknown
  324. jSettings.readReceipts = UserSettings.shared().sendReadReceipts
  325. jSettings.sendTyping = UserSettings.shared().sendTypingIndicator
  326. jSettings.threemaCalls = UserSettings.shared().enableThreemaCall
  327. jSettings.relayThreemaCalls = UserSettings.shared().alwaysRelayCalls
  328. jSettings.blockedContacts = UserSettings.shared().blacklist.map { (identity) -> String in
  329. return (identity as! String)
  330. }
  331. jSettings.syncExcludedIds = UserSettings.shared().syncExclusionList.map { (identity) -> String in
  332. return (identity as! String)
  333. }
  334. let parser = SafeJsonParser()
  335. var safeBackupData = parser.getSafeBackupData()
  336. safeBackupData.user = jUser;
  337. if jContacts.count > 0 {
  338. safeBackupData.contacts = jContacts
  339. }
  340. if jGroups.count > 0 {
  341. safeBackupData.groups = jGroups
  342. }
  343. safeBackupData.settings = jSettings
  344. //print(parser.getJsonAsString(from: safeBackupData)!)
  345. return parser.getJsonAsBytes(from: safeBackupData)!
  346. }
  347. func restoreData(identity: String, data: [UInt8], onlyIdentity: Bool, completionHandler: @escaping (SafeError?) -> Swift.Void) throws {
  348. //print(String(bytes: data, encoding: .utf8)!)
  349. //Check backup version
  350. let parser = SafeJsonParser()
  351. var safeBackupData: SafeJsonParser.SafeBackupData
  352. do {
  353. safeBackupData = try parser.getSafeBackupData(from: Data(data))
  354. } catch let error {
  355. throw SafeError.restoreFailed(message: error.localizedDescription)
  356. }
  357. guard safeBackupData.info.version == 1 else {
  358. throw SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_version_mismatch"))
  359. }
  360. // Restore identity store
  361. guard let privateKey = safeBackupData.user?.privatekey,
  362. let secretKey = Data(base64Encoded: privateKey) else {
  363. DDLogError("Private key could not be restored")
  364. throw SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_no_backup_found"))
  365. }
  366. MyIdentityStore.shared().restore(fromBackup: identity, withSecretKey: secretKey, onCompletion: {
  367. MyIdentityStore.shared().storeInKeychain()
  368. // Store identity in keychain
  369. self.serverApiConnector.update(MyIdentityStore.shared(), onCompletion: { () in
  370. MyIdentityStore.shared().storeInKeychain()
  371. if let nickname = safeBackupData.user?.nickname {
  372. MyIdentityStore.shared().pushFromName = nickname
  373. }
  374. // Use MDM configuration for linking ID, if exists. Otherwise get linking configuration from Threema Safe backup
  375. let mdmSetup = MDMSetup(setup: true)!
  376. if mdmSetup.existsMdmKey(MDM_KEY_LINKED_PHONE) || mdmSetup.existsMdmKey(MDM_KEY_LINKED_EMAIL) || mdmSetup.readonlyProfile() {
  377. if let createIDPhone = MyIdentityStore.shared()?.createIDPhone,
  378. createIDPhone.count > 0 {
  379. let normalizer: PhoneNumberNormalizer = PhoneNumberNormalizer.sharedInstance()
  380. var prettyMobileNo: NSString? = nil
  381. if let mobileNo = normalizer.phoneNumber(toE164: MyIdentityStore.shared()?.createIDPhone, withDefaultRegion: PhoneNumberNormalizer.userRegion(), prettyFormat: &prettyMobileNo),
  382. mobileNo.count > 0 {
  383. self.link(mobileNo: mobileNo)
  384. }
  385. }
  386. if let createIDEmail = MyIdentityStore.shared()?.createIDEmail,
  387. createIDEmail.count > 0 {
  388. self.link(email: createIDEmail)
  389. }
  390. }
  391. else {
  392. if let links = safeBackupData.user?.links,
  393. links.count > 0 {
  394. links.forEach { (link) in
  395. if link.type == "mobile" {
  396. if var linkMobile = link.value {
  397. if !linkMobile.starts(with: "+") {
  398. linkMobile = "+\(linkMobile)"
  399. }
  400. let numbers = self.localizedMobileNo("+\(linkMobile)")
  401. if let mobileNo = numbers.mobileNo {
  402. self.link(mobileNo: mobileNo)
  403. }
  404. }
  405. }
  406. if link.type == "email",
  407. let email = link.value {
  408. self.link(email: email)
  409. }
  410. }
  411. }
  412. }
  413. // Restore profile picture
  414. if let profilePic = safeBackupData.user?.profilePic,
  415. let profilePicData = Data(base64Encoded: profilePic) {
  416. let profilePicture: NSMutableDictionary = MyIdentityStore.shared().profilePicture != nil ? MyIdentityStore.shared().profilePicture : NSMutableDictionary(dictionary: ["ProfilePicture": profilePicData])
  417. MyIdentityStore.shared().profilePicture = profilePicture
  418. }
  419. if onlyIdentity {
  420. self.setProfilePictureRequestList()
  421. completionHandler(nil)
  422. } else {
  423. self.restoreUserSettings(safeBackupData: safeBackupData)
  424. self.restoreContactsAndGroups(identity: identity, safeBackupData: safeBackupData, completionHandler: completionHandler)
  425. }
  426. }, onError: { (error) in
  427. DDLogError("Safe restore error:update identity store failed")
  428. completionHandler(SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_no_backup_found")))
  429. })
  430. }) { (error) in
  431. DDLogError("Safe restore error:update restore identity store failed")
  432. completionHandler(SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_no_backup_found")))
  433. }
  434. }
  435. private func restoreUserSettings(safeBackupData: SafeJsonParser.SafeBackupData) {
  436. let userSettings = UserSettings.shared()!
  437. if let profilePicRelease = safeBackupData.user?.profilePicRelease {
  438. if profilePicRelease.count == 1 && profilePicRelease[0] == "*" {
  439. userSettings.sendProfilePicture = SendProfilePictureAll
  440. }
  441. else if profilePicRelease.count == 1 && profilePicRelease[0] == nil {
  442. userSettings.sendProfilePicture = SendProfilePictureNone
  443. } else if profilePicRelease.count > 0 {
  444. userSettings.sendProfilePicture = SendProfilePictureContacts
  445. userSettings.profilePictureContactList = profilePicRelease as [Any]
  446. } else {
  447. userSettings.sendProfilePicture = SendProfilePictureNone
  448. }
  449. } else {
  450. userSettings.sendProfilePicture = SendProfilePictureNone
  451. }
  452. // Restore settings, contacts and groups
  453. let mdmSetup = MDMSetup(setup: true)!
  454. let settings = safeBackupData.settings!
  455. userSettings.safeIntroShown = true
  456. if !mdmSetup.existsMdmKey(MDM_KEY_CONTACT_SYNC) {
  457. userSettings.syncContacts = settings.syncContacts
  458. }
  459. userSettings.blockUnknown = settings.blockUnknown ?? false
  460. userSettings.sendReadReceipts = settings.readReceipts ?? true
  461. userSettings.sendTypingIndicator = settings.sendTyping ?? true
  462. userSettings.enableThreemaCall = settings.threemaCalls ?? true
  463. userSettings.alwaysRelayCalls = settings.relayThreemaCalls ?? false
  464. if let blockedContacts = settings.blockedContacts {
  465. userSettings.blacklist = NSOrderedSet(array: blockedContacts)
  466. }
  467. if let syncExcludedIds = settings.syncExcludedIds {
  468. userSettings.syncExclusionList = syncExcludedIds
  469. }
  470. }
  471. private func restoreContactsAndGroups(identity: String, safeBackupData: SafeJsonParser.SafeBackupData, completionHandler: @escaping (SafeError?) -> Swift.Void) {
  472. let entityManager = EntityManager()
  473. if let bContacts = safeBackupData.contacts {
  474. var fetchIdentities = [String]()
  475. bContacts.forEach { (bContact) in
  476. if let identity = bContact.identity {
  477. fetchIdentities.append(identity)
  478. }
  479. }
  480. self.serverApiConnector.fetchBulkIdentityInfo(fetchIdentities, onCompletion: { (identities, publicKeys, featureMasks, states, types) in
  481. var index = 0
  482. for id in identities! {
  483. var bContact: SafeJsonParser.SafeBackupData.Contact?
  484. if bContacts.contains(where: { (c) -> Bool in
  485. if let identity = c.identity,
  486. identity.uppercased() == id as! String {
  487. bContact = c
  488. return true
  489. }
  490. return false
  491. }) {
  492. // Do not restore me as contact
  493. if let bContact = bContact,
  494. let contactIdentity = bContact.identity,
  495. contactIdentity.uppercased() != identity.uppercased() {
  496. if let publicKey = publicKeys?[index] as? Data {
  497. // check is contact already stored, could be when Threema MDM sync was running (it's a bug, should not before restore is finished)
  498. if let contact = entityManager.entityFetcher.contact(forId: bContact.identity) {
  499. entityManager.performSyncBlockAndSafe({
  500. contact.verificationLevel = Int32(bContact.verification ?? 0) as NSNumber
  501. contact.firstName = bContact.firstname
  502. contact.lastName = bContact.lastname
  503. contact.publicNickname = bContact.nickname
  504. })
  505. }
  506. else {
  507. entityManager.performSyncBlockAndSafe({
  508. if let contact = entityManager.entityCreator.contact() {
  509. contact.identity = bContact.identity?.uppercased()
  510. contact.verificationLevel = Int32(bContact.verification ?? 0) as NSNumber
  511. contact.firstName = bContact.firstname
  512. contact.lastName = bContact.lastname
  513. contact.publicNickname = bContact.nickname
  514. // function to hide contacts is not implemented in iOS; hidden could be nil
  515. // till then set hidden to false
  516. contact.hidden = 0
  517. if let workVerified = bContact.workVerified {
  518. contact.workContact = workVerified ? 1 : 0
  519. } else {
  520. contact.workContact = 0
  521. }
  522. contact.publicKey = publicKey
  523. if let featureMasks = featureMasks,
  524. let featureMask = featureMasks[index] as? Int {
  525. contact.setFeatureMask(NSNumber(integerLiteral: featureMask))
  526. }
  527. if let states = states,
  528. let state = states[index] as? Int {
  529. contact.state = NSNumber(integerLiteral: state)
  530. }
  531. if let types = types,
  532. let type = types[index] as? Int {
  533. if type == 1 && contact.identity != nil {
  534. let workIdentities = NSMutableOrderedSet.init(orderedSet: UserSettings.shared().workIdentities)
  535. if !workIdentities.contains(contact.identity!) {
  536. workIdentities.add(contact.identity!)
  537. UserSettings.shared().workIdentities = workIdentities
  538. }
  539. }
  540. }
  541. }
  542. })
  543. }
  544. }
  545. }
  546. }
  547. index += 1
  548. }
  549. if index > 0 {
  550. self.setProfilePictureRequestList()
  551. self.restoreGroups(identity: identity, safeBackupData: safeBackupData, entityManager: entityManager)
  552. }
  553. completionHandler(nil)
  554. }) { (error) in
  555. if let error = error {
  556. DDLogError("Safe error while request identities:\(error.localizedDescription)")
  557. completionHandler(SafeError.restoreError(message: BundleUtil.localizedString(forKey: "safe_restore_error")))
  558. }
  559. }
  560. }
  561. }
  562. private func restoreGroups(identity: String, safeBackupData: SafeJsonParser.SafeBackupData, entityManager: EntityManager) {
  563. if let bGroups = safeBackupData.groups {
  564. for bGroup in bGroups {
  565. if let creatorIdentity = bGroup.creator {
  566. var creatorContact: Contact?
  567. if creatorIdentity.uppercased() != identity.uppercased() {
  568. creatorContact = entityManager.entityFetcher.contact(forId: creatorIdentity.uppercased())
  569. }
  570. if creatorIdentity.uppercased() == identity.uppercased() || creatorContact != nil {
  571. var conversation: Conversation?
  572. if let groupId = bGroup.id,
  573. let groupMembers = bGroup.members {
  574. entityManager.performSyncBlockAndSafe({
  575. conversation = entityManager.entityCreator.conversation()
  576. conversation?.groupId = SafeStore.hexStringToData(groupId)
  577. if groupMembers.contains(where: { (member) -> Bool in
  578. member.uppercased().contains(identity.uppercased())
  579. }) {
  580. conversation?.groupMyIdentity = identity.uppercased()
  581. }
  582. conversation?.contact = creatorContact
  583. conversation?.groupName = bGroup.groupname
  584. })
  585. if let conversation = conversation {
  586. //sync restored group
  587. if let group = GroupProxy(for: conversation, entityManager: entityManager) {
  588. group.adminAddMembers(fromBackup: Set(groupMembers), entityManager: entityManager)
  589. //sync only group is active
  590. if (group.isSelfMember()) {
  591. if creatorIdentity.uppercased() == identity.uppercased() {
  592. group.syncGroupInfoToAll()
  593. } else {
  594. GroupProxy.sendSyncRequest(withGroupId: group.groupId, creator: group.creator.identity)
  595. }
  596. }
  597. }
  598. }
  599. }
  600. else {
  601. DDLogWarn("Safe restore group id or members missing")
  602. }
  603. }
  604. else {
  605. DDLogWarn("Safe restore group creator not found")
  606. }
  607. } else {
  608. DDLogWarn("Safe restore group has no creator")
  609. }
  610. }
  611. }
  612. }
  613. private func setProfilePictureRequestList() {
  614. if let userSettings = UserSettings.shared() {
  615. var profilePicRequest = [String]()
  616. let entityManager = EntityManager()
  617. if let contacts = entityManager.entityFetcher.allContacts() as? [Contact] {
  618. for contact in contacts {
  619. if contact.identity != "ECHOECHO" && contact.identity != MyIdentityStore.shared()?.identity {
  620. profilePicRequest.append(contact.identity)
  621. }
  622. }
  623. }
  624. userSettings.profilePictureRequestList = profilePicRequest
  625. }
  626. }
  627. private func downscaleImageAsBase64(data: Data, max: CGFloat) -> String? {
  628. if let image: UIImage = UIImage(data: data) {
  629. // downscale profile picture to max. size and quality 60, if is necessary
  630. if image.size.height > max || image.size.width > max {
  631. var newHeight: CGFloat
  632. var newWidth: CGFloat
  633. if image.size.height > image.size.width {
  634. newHeight = max
  635. newWidth = (newHeight / image.size.height) * image.size.width
  636. } else {
  637. newWidth = max
  638. newHeight = (newWidth / image.size.width) * image.size.height
  639. }
  640. let resizedImage = resizeImage(image: image, size: CGSize(width: newWidth, height: newHeight))!
  641. let resizedData = resizedImage.jpegData(compressionQuality: 0.6)
  642. return resizedData?.base64EncodedString()
  643. } else {
  644. return data.base64EncodedString()
  645. }
  646. } else {
  647. return nil;
  648. }
  649. }
  650. private func resizeImage(image: UIImage, size: CGSize) -> UIImage? {
  651. UIGraphicsBeginImageContext(size)
  652. image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
  653. let newImage = UIGraphicsGetImageFromCurrentImageContext()
  654. UIGraphicsEndImageContext()
  655. return newImage
  656. }
  657. func localizedMobileNo(_ mobileNo: String) -> (mobileNo: String?, prettyMobileNo: String?) {
  658. if mobileNo.count > 0 {
  659. let normalizer: PhoneNumberNormalizer = PhoneNumberNormalizer.sharedInstance()
  660. var prettyMobileNo: NSString?
  661. let localMobileNo = normalizer.phoneNumber(toE164: mobileNo, withDefaultRegion: PhoneNumberNormalizer.userRegion(), prettyFormat: &prettyMobileNo)
  662. return (mobileNo: localMobileNo, prettyMobileNo: prettyMobileNo as String?)
  663. }
  664. return (mobileNo: nil, prettyMobileNo: nil)
  665. }
  666. private func link(mobileNo: String) {
  667. self.serverApiConnector.linkMobileNo(with: MyIdentityStore.shared(), mobileNo: mobileNo, onCompletion: { (linked) in
  668. DDLogInfo("Safe restore linking mobile no with identity successfull")
  669. }, onError: { (error) in
  670. DDLogError("Safe restore linking mobile no with identity failed")
  671. })
  672. }
  673. private func link(email: String) {
  674. self.serverApiConnector.linkEmail(with: MyIdentityStore.shared(), email: email, onCompletion: { (linked) in
  675. DDLogInfo("Safe restore linking email with identity successfull")
  676. }, onError: { (error) in
  677. DDLogError("Safe restore linking email with identity failed")
  678. })
  679. }
  680. }