Swiftgram/NotificationService/AccountData.swift
Peter Iakovlev d0d8f7dcd4 Support for updated accounts API
Implemented Notification Service Extension
2019-01-29 14:04:17 +04:00

119 lines
4.5 KiB
Swift

import Foundation
import CommonCrypto
struct MasterNotificationKey: Codable {
let id: Data
let data: Data
}
struct AccountDatacenterKey: Codable {
let id: Int64
let data: Data
}
struct AccountDatacenterInfo: Codable {
let masterKey: AccountDatacenterKey
}
struct StoredAccountInfo: Codable {
let primaryId: Int32
let isTestingEnvironment: Bool
let datacenters: [Int32: AccountDatacenterInfo]
}
struct AccountData {
let id: Int64
let isTestingEnvironment: Bool
let basePath: String
let datacenterId: Int32
let datacenters: [Int32: AccountDatacenterInfo]
let notificationKey: MasterNotificationKey?
}
func loadAccountsData(rootPath: String) -> [Int64: AccountData] {
var result: [Int64: AccountData] = [:]
if let contents = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: rootPath), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants]) {
for url in contents {
let directoryName = url.lastPathComponent
if directoryName.hasPrefix("account-"), let id = UInt64(directoryName[directoryName.index(directoryName.startIndex, offsetBy: "account-".count)...]) {
var notificationKey: MasterNotificationKey?
if let data = try? Data(contentsOf: URL(fileURLWithPath: url.path + "/notificationsKey")), let value = try? JSONDecoder().decode(MasterNotificationKey.self, from: data) {
notificationKey = value
}
var storedInfo: StoredAccountInfo?
if let data = try? Data(contentsOf: URL(fileURLWithPath: url.path + "/storedInfo")), let value = try? JSONDecoder().decode(StoredAccountInfo.self, from: data) {
storedInfo = value
}
if let storedInfo = storedInfo {
result[Int64(bitPattern: id)] = AccountData(id: Int64(bitPattern: id), isTestingEnvironment: storedInfo.isTestingEnvironment, basePath: url.path, datacenterId: storedInfo.primaryId, datacenters: storedInfo.datacenters, notificationKey: notificationKey)
}
}
}
}
return result
}
private func sha256Digest(_ data: Data) -> Data {
let length = data.count
return data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
var result = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
result.withUnsafeMutableBytes { (destBytes: UnsafeMutablePointer<UInt8>) -> Void in
CC_SHA256(bytes, UInt32(length), destBytes)
}
return result
}
}
func decryptedNotificationPayload(accounts: [Int64: AccountData], data: Data) -> (AccountData, [AnyHashable: Any])? {
if data.count < 8 + 16 {
return nil
}
for (_, account) in accounts {
guard let notificationKey = account.notificationKey else {
continue
}
if data.subdata(in: 0 ..< 8) != notificationKey.id {
continue
}
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 + notificationKey.data.subdata(in: x ..< (x + 36)))
let sha256_b = sha256Digest(notificationKey.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<Int8>) -> Void in
memcpy(&dataLength, bytes, 4)
}
if dataLength < 0 || dataLength > data.count - 4 {
return nil
}
let checkMsgKeyLarge = sha256Digest(notificationKey.data.subdata(in: (88 + x) ..< (88 + x + 32)) + data)
let checkMsgKey = checkMsgKeyLarge.subdata(in: 8 ..< (8 + 16))
if checkMsgKey != msgKey {
return nil
}
let contentData = data.subdata(in: 4 ..< (4 + Int(dataLength)))
guard let result = try? JSONSerialization.jsonObject(with: contentData, options: []) else {
return nil
}
guard let dict = result as? [AnyHashable: Any] else {
return nil
}
return (account, dict)
}
return nil
}