Swiftgram/submodules/TelegramCore/Sources/UploadSecureIdFile.swift
2019-10-29 00:13:20 +04:00

138 lines
4.9 KiB
Swift

import Foundation
#if os(macOS)
import PostboxMac
import MtProtoKitMac
import SwiftSignalKitMac
#else
import Postbox
#if BUCK
import MtProtoKit
#else
import MtProtoKitDynamic
#endif
import SwiftSignalKit
#endif
public struct UploadedSecureIdFile: Equatable {
let id: Int64
let parts: Int32
let md5Checksum: String
public let fileHash: Data
let encryptedSecret: Data
}
public enum UploadSecureIdFileResult {
case progress(Float)
case result(UploadedSecureIdFile, Data)
}
public enum UploadSecureIdFileError {
case generic
}
private struct EncryptedSecureIdFile {
let data: Data
let hash: Data
let encryptedSecret: Data
}
private func encryptedSecureIdFile(context: SecureIdAccessContext, data: Data) -> EncryptedSecureIdFile? {
guard let fileSecret = generateSecureSecretData() else {
return nil
}
let paddedFileData = paddedSecureIdData(data)
let fileHash = sha256Digest(paddedFileData)
let fileSecretHash = sha512Digest(fileSecret + fileHash)
let fileKey = fileSecretHash.subdata(in: 0 ..< 32)
let fileIv = fileSecretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedFileData = encryptSecureData(key: fileKey, iv: fileIv, data: paddedFileData, decrypt: false) else {
return nil
}
let secretHash = sha512Digest(context.secret + fileHash)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let encryptedSecretData = encryptSecureData(key: secretKey, iv: secretIv, data: fileSecret, decrypt: false) else {
return nil
}
return EncryptedSecureIdFile(data: encryptedFileData, hash: fileHash, encryptedSecret: encryptedSecretData)
}
func decryptedSecureIdFileSecret(context: SecureIdAccessContext, fileHash: Data, encryptedSecret: Data) -> Data? {
let secretHash = sha512Digest(context.secret + fileHash)
let secretKey = secretHash.subdata(in: 0 ..< 32)
let secretIv = secretHash.subdata(in: 32 ..< (32 + 16))
guard let fileSecret = encryptSecureData(key: secretKey, iv: secretIv, data: encryptedSecret, decrypt: true) else {
return nil
}
guard verifySecureSecret(fileSecret) else {
return nil
}
return fileSecret
}
func decryptedSecureIdFile(context: SecureIdAccessContext, encryptedData: Data, fileHash: Data, encryptedSecret: Data) -> Data? {
guard let fileSecret = decryptedSecureIdFileSecret(context: context, fileHash: fileHash, encryptedSecret: encryptedSecret) else {
return nil
}
let fileSecretHash = sha512Digest(fileSecret + fileHash)
let fileKey = fileSecretHash.subdata(in: 0 ..< 32)
let fileIv = fileSecretHash.subdata(in: 32 ..< (32 + 16))
guard let paddedFileData = encryptSecureData(key: fileKey, iv: fileIv, data: encryptedData, decrypt: true) else {
return nil
}
let checkFileHash = sha256Digest(paddedFileData)
if fileHash != checkFileHash {
return nil
}
guard let unpaddedFileData = unpaddedSecureIdData(paddedFileData) else {
return nil
}
return unpaddedFileData
}
public func uploadSecureIdFile(context: SecureIdAccessContext, postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadSecureIdFileResult, UploadSecureIdFileError> {
return postbox.mediaBox.resourceData(resource)
|> mapError { _ -> UploadSecureIdFileError in
return .generic
}
|> mapToSignal { next -> Signal<UploadSecureIdFileResult, UploadSecureIdFileError> in
if !next.complete {
return .complete()
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: next.path)) else {
return .fail(.generic)
}
guard let encryptedData = encryptedSecureIdFile(context: context, data: data) else {
return .fail(.generic)
}
return multipartUpload(network: network, postbox: postbox, source: .data(encryptedData.data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false)
|> mapError { _ -> UploadSecureIdFileError in
return .generic
}
|> mapToSignal { result -> Signal<UploadSecureIdFileResult, UploadSecureIdFileError> in
switch result {
case let .progress(value):
return .single(.progress(value))
case let .inputFile(file):
if case let .inputFile(id, parts, _, md5Checksum) = file {
return .single(.result(UploadedSecureIdFile(id: id, parts: parts, md5Checksum: md5Checksum, fileHash: encryptedData.hash, encryptedSecret: encryptedData.encryptedSecret), encryptedData.data))
} else {
return .fail(.generic)
}
default:
return .fail(.generic)
}
}
}
}