Refactoring

This commit is contained in:
Ali
2021-03-24 03:18:22 +04:00
parent 2f9189d220
commit 9e4853d9a7
49 changed files with 0 additions and 66 deletions

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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")
}
}
}