mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Refactoring
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer, mode: SecretChatEncryptionMode) -> (aesKey: Data, aesIv: Data) {
|
||||
switch mode {
|
||||
case .v1:
|
||||
let x: Int = 0
|
||||
|
||||
var sha1AData = Data()
|
||||
sha1AData.count = 16 + 32
|
||||
sha1AData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memcpy(bytes, msgKey, 16)
|
||||
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: x), 32)
|
||||
}
|
||||
let sha1A = MTSha1(sha1AData)!
|
||||
|
||||
var sha1BData = Data()
|
||||
sha1BData.count = 16 + 16 + 16
|
||||
sha1BData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memcpy(bytes, key.key.memory.advanced(by: 32 + x), 16)
|
||||
memcpy(bytes.advanced(by: 16), msgKey, 16)
|
||||
memcpy(bytes.advanced(by: 16 + 16), key.key.memory.advanced(by: 48 + x), 16)
|
||||
}
|
||||
let sha1B = MTSha1(sha1BData)!
|
||||
|
||||
var sha1CData = Data()
|
||||
sha1CData.count = 32 + 16
|
||||
sha1CData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memcpy(bytes, key.key.memory.advanced(by: 64 + x), 32)
|
||||
memcpy(bytes.advanced(by: 32), msgKey, 16)
|
||||
}
|
||||
let sha1C = MTSha1(sha1CData)!
|
||||
|
||||
var sha1DData = Data()
|
||||
sha1DData.count = 16 + 32
|
||||
sha1DData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memcpy(bytes, msgKey, 16)
|
||||
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: 96 + x), 32)
|
||||
}
|
||||
let sha1D = MTSha1(sha1DData)!
|
||||
|
||||
var aesKey = Data()
|
||||
aesKey.count = 8 + 12 + 12
|
||||
aesKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes, sha1A, 8)
|
||||
}
|
||||
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes.advanced(by: 8), sha1B.advanced(by: 8), 12)
|
||||
}
|
||||
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes.advanced(by: 8 + 12), sha1C.advanced(by: 4), 12)
|
||||
}
|
||||
}
|
||||
|
||||
var aesIv = Data()
|
||||
aesIv.count = 12 + 8 + 4 + 8
|
||||
aesIv.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes, sha1A.advanced(by: 8), 12)
|
||||
}
|
||||
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes.advanced(by: 12), sha1B, 8)
|
||||
}
|
||||
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes.advanced(by: 12 + 8), sha1C.advanced(by: 16), 4)
|
||||
}
|
||||
sha1D.withUnsafeBytes { (sha1D: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(bytes.advanced(by: 12 + 8 + 4), sha1D, 8)
|
||||
}
|
||||
}
|
||||
return (aesKey, aesIv)
|
||||
case let .v2(role):
|
||||
var xValue: Int
|
||||
switch role {
|
||||
case .creator:
|
||||
xValue = 0
|
||||
case .participant:
|
||||
xValue = 8
|
||||
}
|
||||
|
||||
var sha256_a_data = Data()
|
||||
sha256_a_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||
sha256_a_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: xValue), count: 36)
|
||||
|
||||
let sha256_a = MTSha256(sha256_a_data)!
|
||||
|
||||
var sha256_b_data = Data()
|
||||
sha256_b_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 40 + xValue), count: 36)
|
||||
sha256_b_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||
|
||||
let sha256_b = MTSha256(sha256_b_data)!
|
||||
|
||||
var aesKey = Data()
|
||||
aesKey.append(sha256_a.subdata(in: 0 ..< (0 + 8)))
|
||||
aesKey.append(sha256_b.subdata(in: 8 ..< (8 + 16)))
|
||||
aesKey.append(sha256_a.subdata(in: 24 ..< (24 + 8)))
|
||||
|
||||
var aesIv = Data()
|
||||
aesIv.append(sha256_b.subdata(in: 0 ..< (0 + 8)))
|
||||
aesIv.append(sha256_a.subdata(in: 8 ..< (8 + 16)))
|
||||
aesIv.append(sha256_b.subdata(in: 24 ..< (24 + 8)))
|
||||
|
||||
return (aesKey, aesIv)
|
||||
}
|
||||
}
|
||||
|
||||
func withDecryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> MemoryBuffer? {
|
||||
assert(parameters.key.key.length == 256)
|
||||
|
||||
if data.length < 4 + 16 + 16 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let msgKey = data.memory.advanced(by: 8)
|
||||
|
||||
switch parameters.mode {
|
||||
case .v1:
|
||||
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: parameters.mode)
|
||||
|
||||
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
||||
|
||||
if decryptedData.count < 4 * 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var payloadLength: Int32 = 0
|
||||
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&payloadLength, bytes, 4)
|
||||
}
|
||||
|
||||
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
||||
if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)!
|
||||
let msgKeyMatches = calculatedMsgKeyData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Bool in
|
||||
return memcmp(bytes.advanced(by: calculatedMsgKeyData.count - 16), msgKey, 16) == 0
|
||||
}
|
||||
|
||||
if !msgKeyMatches {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
|
||||
}
|
||||
return MemoryBuffer(data: result)
|
||||
case let .v2(role):
|
||||
let senderRole: SecretChatRole
|
||||
switch role {
|
||||
case .creator:
|
||||
senderRole = .participant
|
||||
case .participant:
|
||||
senderRole = .creator
|
||||
}
|
||||
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: .v2(role: senderRole))
|
||||
|
||||
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
||||
|
||||
if decryptedData.count < 4 * 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var payloadLength: Int32 = 0
|
||||
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&payloadLength, bytes, 4)
|
||||
}
|
||||
|
||||
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
||||
|
||||
let xValue: Int
|
||||
switch role {
|
||||
case .creator:
|
||||
xValue = 8
|
||||
case .participant:
|
||||
xValue = 0
|
||||
}
|
||||
|
||||
var keyLargeData = Data()
|
||||
keyLargeData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
|
||||
keyLargeData.append(decryptedData)
|
||||
|
||||
let keyLarge = MTSha256(keyLargeData)!
|
||||
let localMessageKey = keyLarge.subdata(in: 8 ..< (8 + 16))
|
||||
|
||||
let msgKeyData = Data(bytes: msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||
|
||||
if Int(payloadLength) <= 0 || Int(payloadLength) > decryptedData.count - 4 || paddingLength < 12 || paddingLength > 1024 {
|
||||
|
||||
if localMessageKey != msgKeyData {
|
||||
Logger.shared.log("SecretChatEncryption", "message key doesn't match (length check)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if localMessageKey != msgKeyData {
|
||||
Logger.shared.log("SecretChatEncryption", "message key doesn't match")
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
|
||||
}
|
||||
return MemoryBuffer(data: result)
|
||||
}
|
||||
}
|
||||
|
||||
enum SecretChatEncryptionMode {
|
||||
case v1
|
||||
case v2(role: SecretChatRole)
|
||||
}
|
||||
|
||||
struct SecretChatEncryptionParameters {
|
||||
let key: SecretChatKey
|
||||
let mode: SecretChatEncryptionMode
|
||||
}
|
||||
|
||||
func encryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> Data {
|
||||
var payloadLength: Int32 = Int32(data.length)
|
||||
var payloadData = Data()
|
||||
withUnsafeBytes(of: &payloadLength, { bytes -> Void in
|
||||
payloadData.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 4)
|
||||
})
|
||||
payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length)
|
||||
|
||||
switch parameters.mode {
|
||||
case .v1:
|
||||
var msgKey = MTSha1(payloadData)!
|
||||
msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data())
|
||||
|
||||
var randomBuf = malloc(16)!
|
||||
defer {
|
||||
free(randomBuf)
|
||||
}
|
||||
let randomBytes = randomBuf.assumingMemoryBound(to: UInt8.self)
|
||||
arc4random_buf(randomBuf, 16)
|
||||
|
||||
var randomIndex = 0
|
||||
while payloadData.count % 16 != 0 {
|
||||
payloadData.append(randomBytes.advanced(by: randomIndex), count: 1)
|
||||
randomIndex += 1
|
||||
}
|
||||
|
||||
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
||||
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
|
||||
}
|
||||
|
||||
let encryptedData = MTAesEncrypt(payloadData, aesKey, aesIv)!
|
||||
var encryptedPayload = Data()
|
||||
var keyFingerprint: Int64 = parameters.key.fingerprint
|
||||
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
||||
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
||||
})
|
||||
encryptedPayload.append(msgKey)
|
||||
encryptedPayload.append(encryptedData)
|
||||
return encryptedPayload
|
||||
case let .v2(role):
|
||||
var randomBytes = Data(count: 128)
|
||||
let randomBytesCount = randomBytes.count
|
||||
randomBytes.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
|
||||
arc4random_buf(bytes, randomBytesCount)
|
||||
}
|
||||
|
||||
var decryptedData = payloadData
|
||||
var take = 0
|
||||
while take < 12 {
|
||||
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||
take += 1
|
||||
}
|
||||
|
||||
while decryptedData.count % 16 != 0 {
|
||||
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||
take += 1
|
||||
}
|
||||
|
||||
var remainingCount = Int(arc4random_uniform(UInt32(72 + 1 - take)))
|
||||
while remainingCount % 16 != 0 {
|
||||
remainingCount -= 1
|
||||
}
|
||||
|
||||
for _ in 0 ..< remainingCount {
|
||||
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||
take += 1
|
||||
}
|
||||
|
||||
var xValue: Int
|
||||
switch role {
|
||||
case .creator:
|
||||
xValue = 0
|
||||
case .participant:
|
||||
xValue = 8
|
||||
}
|
||||
|
||||
var keyData = Data()
|
||||
keyData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
|
||||
|
||||
keyData.append(decryptedData)
|
||||
|
||||
let keyLarge = MTSha256(keyData)!
|
||||
|
||||
let msgKey = keyLarge.subdata(in: 8 ..< (8 + 16))
|
||||
|
||||
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
||||
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
|
||||
}
|
||||
|
||||
let encryptedData = MTAesEncrypt(decryptedData, aesKey, aesIv)!
|
||||
var encryptedPayload = Data()
|
||||
var keyFingerprint: Int64 = parameters.key.fingerprint
|
||||
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
||||
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
||||
})
|
||||
encryptedPayload.append(msgKey)
|
||||
encryptedPayload.append(encryptedData)
|
||||
return encryptedPayload
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
func validatedEncryptionConfig(postbox: Postbox, network: Network) -> Signal<SecretChatEncryptionConfig, NoError> {
|
||||
return network.request(Api.functions.messages.getDhConfig(version: 0, randomLength: 0))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<SecretChatEncryptionConfig, NoError> in
|
||||
switch result {
|
||||
case let .dhConfig(g, p, version, _):
|
||||
if !MTCheckIsSafeG(UInt32(g)) {
|
||||
Logger.shared.log("SecretChatEncryptionConfig", "Invalid g")
|
||||
return .complete()
|
||||
}
|
||||
|
||||
if !MTCheckMod(network.encryptionProvider, p.makeData(), UInt32(g), network.context.keychain) {
|
||||
Logger.shared.log("SecretChatEncryptionConfig", "Invalid p or g")
|
||||
return .complete()
|
||||
}
|
||||
|
||||
if !MTCheckIsSafePrime(network.encryptionProvider, p.makeData(), network.context.keychain) {
|
||||
Logger.shared.log("SecretChatEncryptionConfig", "Invalid p")
|
||||
return .never()
|
||||
}
|
||||
return .single(SecretChatEncryptionConfig(g: g, p: MemoryBuffer(p), version: version))
|
||||
case .dhConfigNotModified(_):
|
||||
assertionFailure()
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
extension SecretChatFileReference {
|
||||
convenience init?(_ file: Api.EncryptedFile) {
|
||||
switch file {
|
||||
case let .encryptedFile(id, accessHash, size, dcId, keyFingerprint):
|
||||
self.init(id: id, accessHash: accessHash, size: size, datacenterId: dcId, keyFingerprint: keyFingerprint)
|
||||
case .encryptedFileEmpty:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
private func keyFingerprintFromBytes(_ bytes: Buffer) -> Int64 {
|
||||
if let memory = bytes.data, bytes.size >= 4 {
|
||||
var fingerprint: Int64 = 0
|
||||
memcpy(&fingerprint, memory, 8)
|
||||
return fingerprint
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
extension SecretChatIncomingEncryptedOperation {
|
||||
convenience init(message: Api.EncryptedMessage) {
|
||||
switch message {
|
||||
case let .encryptedMessage(randomId, chatId, date, bytes, file):
|
||||
self.init(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), globallyUniqueId: randomId, timestamp: date, type: .message, keyFingerprint: keyFingerprintFromBytes(bytes), contents: MemoryBuffer(bytes), mediaFileReference: SecretChatFileReference(file))
|
||||
case let .encryptedMessageService(randomId, chatId, date, bytes):
|
||||
self.init(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), globallyUniqueId: randomId, timestamp: date, type: .service, keyFingerprint: keyFingerprintFromBytes(bytes), contents: MemoryBuffer(bytes), mediaFileReference: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
private let topSupportedLayer: SecretChatSequenceBasedLayer = .layer101
|
||||
|
||||
func secretChatCommonSupportedLayer(remoteLayer: Int32) -> SecretChatSequenceBasedLayer {
|
||||
switch remoteLayer {
|
||||
case 46:
|
||||
return .layer46
|
||||
case 73:
|
||||
return .layer73
|
||||
case 101:
|
||||
return .layer101
|
||||
default:
|
||||
return topSupportedLayer
|
||||
}
|
||||
}
|
||||
|
||||
func secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(transaction: Transaction, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||
switch state.embeddedState {
|
||||
case .basicLayer:
|
||||
var updatedState = state
|
||||
updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .reportLayerSupport(layer: .layer8, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
|
||||
return updatedState
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
var updatedState = state
|
||||
updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .reportLayerSupport(layer: sequenceState.layerNegotiationState.activeLayer.secretChatLayer, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedLocallyRequestedLayer(topSupportedLayer.rawValue))))
|
||||
return updatedState
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
func secretChatCheckLayerNegotiationIfNeeded(transaction: Transaction, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||
switch state.embeddedState {
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
if sequenceState.layerNegotiationState.activeLayer != topSupportedLayer {
|
||||
var updatedState = state
|
||||
|
||||
if let remotelyRequestedLayer = sequenceState.layerNegotiationState.remotelyRequestedLayer {
|
||||
let updatedSequenceState = sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedActiveLayer(secretChatCommonSupportedLayer(remoteLayer: remotelyRequestedLayer)))
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(updatedSequenceState))
|
||||
}
|
||||
|
||||
if (sequenceState.layerNegotiationState.locallyRequestedLayer ?? 0) < topSupportedLayer.rawValue {
|
||||
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(transaction: transaction, peerId: peerId, state: updatedState)
|
||||
}
|
||||
|
||||
return updatedState
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
case .basicLayer:
|
||||
return state
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
extension SecretChatOutgoingFileReference {
|
||||
init?(_ apiFile: Api.InputEncryptedFile) {
|
||||
switch apiFile {
|
||||
case let .inputEncryptedFile(id, accessHash):
|
||||
self = .remote(id: id, accessHash: accessHash)
|
||||
case let .inputEncryptedFileBigUploaded(id, parts, keyFingerprint):
|
||||
self = .uploadedLarge(id: id, partCount: parts, keyFingerprint: keyFingerprint)
|
||||
case let .inputEncryptedFileUploaded(id, parts, md5Checksum, keyFingerprint):
|
||||
self = .uploadedRegular(id: id, partCount: parts, md5Digest: md5Checksum, keyFingerprint: keyFingerprint)
|
||||
case .inputEncryptedFileEmpty:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var apiInputFile: Api.InputEncryptedFile {
|
||||
switch self {
|
||||
case let .remote(id, accessHash):
|
||||
return .inputEncryptedFile(id: id, accessHash: accessHash)
|
||||
case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint):
|
||||
return .inputEncryptedFileUploaded(id: id, parts: partCount, md5Checksum: md5Digest, keyFingerprint: keyFingerprint)
|
||||
case let .uploadedLarge(id, partCount, keyFingerprint):
|
||||
return .inputEncryptedFileBigUploaded(id: id, parts: partCount, keyFingerprint: keyFingerprint)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
import EncryptionProvider
|
||||
|
||||
private let keyUseCountThreshold: Int32 = 100
|
||||
|
||||
func secretChatInitiateRekeySessionIfNeeded(transaction: Transaction, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||
switch state.embeddedState {
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
if let _ = sequenceState.rekeyState {
|
||||
return state
|
||||
}
|
||||
let tagLocalIndex = transaction.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
||||
let canonicalIndex = sequenceState.canonicalOutgoingOperationIndex(tagLocalIndex)
|
||||
if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalIndex), key.useCount >= keyUseCountThreshold {
|
||||
let sessionId = arc4random64()
|
||||
let aBytes = malloc(256)!
|
||||
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
|
||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: sessionId, data: .requesting))))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func secretChatAdvanceRekeySessionIfNeeded(encryptionProvider: EncryptionProvider, transaction: Transaction, peerId: PeerId, state: SecretChatState, action: SecretChatRekeyServiceAction) -> SecretChatState {
|
||||
switch state.embeddedState {
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
switch action {
|
||||
case let .pfsAbortSession(rekeySessionId):
|
||||
if let rekeySession = sequenceState.rekeyState, rekeySession.id == rekeySessionId {
|
||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(nil)))
|
||||
}
|
||||
case let .pfsAcceptKey(rekeySessionId, gB, remoteKeyFingerprint):
|
||||
if let rekeySession = sequenceState.rekeyState, rekeySession.id == rekeySessionId {
|
||||
switch rekeySession.data {
|
||||
case let .requested(a, config):
|
||||
var gValue: Int32 = config.g.byteSwapped
|
||||
let p = config.p.makeData()
|
||||
|
||||
let aData = a.makeData()
|
||||
if !MTCheckIsSafeGAOrB(encryptionProvider, gB.makeData(), p) {
|
||||
return state.withUpdatedEmbeddedState(.terminated)
|
||||
}
|
||||
|
||||
var key = MTExp(encryptionProvider, gB.makeData(), aData, p)!
|
||||
|
||||
if key.count > 256 {
|
||||
key.count = 256
|
||||
} else {
|
||||
while key.count < 256 {
|
||||
key.insert(0, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
let keyHash = MTSha1(key)!
|
||||
|
||||
var keyFingerprint: Int64 = 0
|
||||
keyHash.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&keyFingerprint, bytes.advanced(by: keyHash.count - 8), 8)
|
||||
}
|
||||
|
||||
assert(remoteKeyFingerprint == keyFingerprint)
|
||||
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
|
||||
|
||||
let keyValidityOperationIndex = transaction.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
||||
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex)
|
||||
|
||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(nil))).withUpdatedKeychain(state.keychain.withUpdatedKey(fingerprint: keyFingerprint, { _ in
|
||||
return SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .sequenceBasedIndexRange(fromCanonicalIndex: keyValidityOperationCanonicalIndex), useCount: 0)
|
||||
}))
|
||||
default:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .pfsCommitKey(rekeySessionId, keyFingerprint):
|
||||
if let rekeySession = sequenceState.rekeyState, rekeySession.id == rekeySessionId {
|
||||
if case let .accepted(key, localKeyFingerprint) = rekeySession.data, keyFingerprint == localKeyFingerprint {
|
||||
let keyValidityOperationIndex = transaction.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
||||
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex)
|
||||
|
||||
let updatedState = state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(nil))).withUpdatedKeychain(state.keychain.withUpdatedKey(fingerprint: keyFingerprint, { _ in
|
||||
return SecretChatKey(fingerprint: keyFingerprint, key: key, validity: .sequenceBasedIndexRange(fromCanonicalIndex: keyValidityOperationCanonicalIndex), useCount: 0)
|
||||
}))
|
||||
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
|
||||
|
||||
return updatedState
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
case let .pfsRequestKey(rekeySessionId, gA):
|
||||
var acceptSession = true
|
||||
if let rekeySession = sequenceState.rekeyState {
|
||||
switch rekeySession.data {
|
||||
case .requesting, .requested:
|
||||
if rekeySessionId < rekeySession.id {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
|
||||
} else {
|
||||
acceptSession = false
|
||||
}
|
||||
case .accepting, .accepted:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if acceptSession {
|
||||
let bBytes = malloc(256)!
|
||||
let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||
|
||||
let rekeySession = SecretChatRekeySessionState(id: rekeySessionId, data: .accepting)
|
||||
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
|
||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(rekeySession)))
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return state
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
struct SecretChatRequestData {
|
||||
let g: Int32
|
||||
let p: MemoryBuffer
|
||||
let a: MemoryBuffer
|
||||
}
|
||||
|
||||
func updateSecretChat(encryptionProvider: EncryptionProvider, accountPeerId: PeerId, transaction: Transaction, mediaBox: MediaBox, chat: Api.EncryptedChat, requestData: SecretChatRequestData?) {
|
||||
let currentPeer = transaction.getPeer(chat.peerId) as? TelegramSecretChat
|
||||
let currentState = transaction.getPeerChatState(chat.peerId) as? SecretChatState
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.secretChatSettings) as? SecretChatSettings ?? SecretChatSettings.defaultSettings
|
||||
assert((currentPeer == nil) == (currentState == nil))
|
||||
switch chat {
|
||||
case let .encryptedChat(_, _, _, adminId, _, gAOrB, remoteKeyFingerprint):
|
||||
if let currentPeer = currentPeer, let currentState = currentState, adminId == accountPeerId.id {
|
||||
if case let .handshake(handshakeState) = currentState.embeddedState, case let .requested(_, p, a) = handshakeState {
|
||||
let pData = p.makeData()
|
||||
let aData = a.makeData()
|
||||
|
||||
if !MTCheckIsSafeGAOrB(encryptionProvider, gAOrB.makeData(), pData) {
|
||||
var updatedState = currentState
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.terminated)
|
||||
transaction.setPeerChatState(chat.peerId, state: updatedState)
|
||||
return
|
||||
}
|
||||
|
||||
var key = MTExp(encryptionProvider, gAOrB.makeData(), aData, pData)!
|
||||
|
||||
if key.count > 256 {
|
||||
key.count = 256
|
||||
} else {
|
||||
while key.count < 256 {
|
||||
key.insert(0, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
let keyHash = MTSha1(key)!
|
||||
|
||||
var keyFingerprint: Int64 = 0
|
||||
keyHash.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&keyFingerprint, bytes.advanced(by: keyHash.count - 8), 8)
|
||||
}
|
||||
|
||||
var updatedState = currentState
|
||||
updatedState = updatedState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)]))
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer46, locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: transaction.operationLogGetNextEntryLocalIndex(peerId: currentPeer.id, tag: OperationLogTags.SecretIncomingDecrypted), baseOutgoingOperationIndex: transaction.operationLogGetNextEntryLocalIndex(peerId: currentPeer.id, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)))
|
||||
|
||||
updatedState = updatedState.withUpdatedKeyFingerprint(SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key))))
|
||||
|
||||
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(transaction: transaction, peerId: currentPeer.id, state: updatedState)
|
||||
|
||||
transaction.setPeerChatState(currentPeer.id, state: updatedState)
|
||||
updatePeers(transaction: transaction, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
} else {
|
||||
Logger.shared.log("State", "got encryptedChat, but chat is not in handshake state")
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("State", "got encryptedChat, but peer or state don't exist or account is not creator")
|
||||
}
|
||||
case let .encryptedChatDiscarded(flags, _):
|
||||
if let currentPeer = currentPeer, let currentState = currentState {
|
||||
let isRemoved = (flags & (1 << 0)) != 0
|
||||
|
||||
let state = currentState.withUpdatedEmbeddedState(.terminated)
|
||||
let peer = currentPeer.withUpdatedEmbeddedState(state.embeddedState.peerState)
|
||||
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in return updated })
|
||||
transaction.setPeerChatState(peer.id, state: state)
|
||||
transaction.operationLogRemoveAllEntries(peerId: peer.id, tag: OperationLogTags.SecretOutgoing)
|
||||
|
||||
if isRemoved {
|
||||
let peerId = currentPeer.id
|
||||
clearHistory(transaction: transaction, mediaBox: mediaBox, peerId: peerId, namespaces: .all)
|
||||
transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded)
|
||||
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(peerId).rawValue)
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("State", "got encryptedChatDiscarded, but peer doesn't exist")
|
||||
}
|
||||
case .encryptedChatEmpty(_):
|
||||
break
|
||||
case let .encryptedChatRequested(_, folderId, _, accessHash, date, adminId, participantId, gA):
|
||||
if currentPeer == nil && participantId == accountPeerId.id {
|
||||
if settings.acceptOnThisDevice {
|
||||
let state = SecretChatState(role: .participant, embeddedState: .handshake(.accepting), keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
|
||||
|
||||
let bBytes = malloc(256)!
|
||||
let randomStatus = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||
if randomStatus == 0 {
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: chat.peerId, operation: .initialHandshakeAccept(gA: MemoryBuffer(gA), accessHash: accessHash, b: b), state: state)
|
||||
transaction.setPeerChatState(chat.peerId, state: updatedState)
|
||||
|
||||
let peer = TelegramSecretChat(id: chat.peerId, creationDate: date, regularPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), accessHash: accessHash, role: updatedState.role, embeddedState: updatedState.embeddedState.peerState, messageAutoremoveTimeout: nil)
|
||||
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in return updated })
|
||||
if folderId != nil {
|
||||
transaction.updatePeerChatListInclusion(peer.id, inclusion: .ifHasMessagesOrOneOf(groupId: Namespaces.PeerGroup.archive, pinningIndex: nil, minTimestamp: date))
|
||||
}
|
||||
|
||||
transaction.resetIncomingReadStates([peer.id: [
|
||||
Namespaces.Message.SecretIncoming: .indexBased(maxIncomingReadIndex: MessageIndex.lowerBound(peerId: peer.id), maxOutgoingReadIndex: MessageIndex.lowerBound(peerId: peer.id), count: 0, markedUnread: false),
|
||||
Namespaces.Message.Local: .indexBased(maxIncomingReadIndex: MessageIndex.lowerBound(peerId: peer.id), maxOutgoingReadIndex: MessageIndex.lowerBound(peerId: peer.id), count: 0, markedUnread: false)
|
||||
]
|
||||
])
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("State", "accepting secret chats disabled on this device")
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
Logger.shared.log("State", "got encryptedChatRequested, but peer already exists or this account is creator")
|
||||
}
|
||||
case let .encryptedChatWaiting(_, accessHash, date, adminId, participantId):
|
||||
if let requestData = requestData, currentPeer == nil && adminId == accountPeerId.id {
|
||||
let state = SecretChatState(role: .creator, embeddedState: .handshake(.requested(g: requestData.g, p: requestData.p, a: requestData.a)), keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
|
||||
let peer = TelegramSecretChat(id: chat.peerId, creationDate: date, regularPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: participantId), accessHash: accessHash, role: state.role, embeddedState: state.embeddedState.peerState, messageAutoremoveTimeout: nil)
|
||||
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in return updated })
|
||||
transaction.setPeerChatState(peer.id, state: state)
|
||||
transaction.resetIncomingReadStates([peer.id: [
|
||||
Namespaces.Message.SecretIncoming: .indexBased(maxIncomingReadIndex: MessageIndex.lowerBound(peerId: peer.id), maxOutgoingReadIndex: MessageIndex.lowerBound(peerId: peer.id), count: 0, markedUnread: false),
|
||||
Namespaces.Message.Local: .indexBased(maxIncomingReadIndex: MessageIndex.lowerBound(peerId: peer.id), maxOutgoingReadIndex: MessageIndex.lowerBound(peerId: peer.id), count: 0, markedUnread: false)
|
||||
]
|
||||
])
|
||||
} else {
|
||||
Logger.shared.log("State", "got encryptedChatWaiting, but peer already exists or this account is not creator")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user